mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Add ProjectCode support to build orders (#4808)
* Add "project_code" field to Build model * Add "project_code" field to Build model * build javascript updates (cherry picked from commit3e27a3b739
) * Update table filters (cherry picked from commit196c675585
) * Adds API filtering * Bump API version * Hide project code field from build form if project codes not enabled (cherry picked from commit4e210e3dfa
) * refactoring to attempt to fix circular imports * Upgrade django-test-migrations package * Fix broken import * Further fixes for unit tests * Update unit tests for migration files * Fix typo in build.js * Migration test updates - Need to specify MPTT stuff * Fix build.js * Fix migration order * Update API version
This commit is contained in:
parent
c8365ccd0c
commit
00bb740216
@ -2,17 +2,23 @@
|
||||
|
||||
|
||||
# InvenTree API version
|
||||
INVENTREE_API_VERSION = 120
|
||||
INVENTREE_API_VERSION = 121
|
||||
|
||||
"""
|
||||
Increment this API version number whenever there is a significant change to the API that any clients need to know about
|
||||
|
||||
v121 -> 2023-06-14 : https://github.com/inventree/InvenTree/pull/4808
|
||||
- Adds "ProjectCode" link to Build model
|
||||
|
||||
v120 -> 2023-06-07 : https://github.com/inventree/InvenTree/pull/4855
|
||||
- Major overhaul of the build order API
|
||||
- Adds new BuildLine model
|
||||
|
||||
v120 -> 2023-06-12 : https://github.com/inventree/InvenTree/pull/4804
|
||||
- Adds 'project_code' field to build order API endpoints
|
||||
|
||||
v119 -> 2023-06-01 : https://github.com/inventree/InvenTree/pull/4898
|
||||
- Add Metadata to: Part test templates, Part parameters, Part category parameter templates, BOM item substitute, Part relateds, Stock item test result
|
||||
- Add Metadata to: Part test templates, Part parameters, Part category parameter templates, BOM item substitute, Related Parts, Stock item test result
|
||||
|
||||
v118 -> 2023-06-01 : https://github.com/inventree/InvenTree/pull/4935
|
||||
- Adds extra fields for the PartParameterTemplate model
|
||||
@ -30,6 +36,7 @@ v115 - > 2023-05-18 : https://github.com/inventree/InvenTree/pull/4846
|
||||
|
||||
v114 -> 2023-05-16 : https://github.com/inventree/InvenTree/pull/4825
|
||||
- Adds "delivery_date" to shipments
|
||||
>>>>>>> inventree/master
|
||||
|
||||
v113 -> 2023-05-13 : https://github.com/inventree/InvenTree/pull/4800
|
||||
- Adds API endpoints for scrapping a build output
|
||||
|
@ -14,7 +14,6 @@ from allauth_2fa.middleware import (AllauthTwoFactorMiddleware,
|
||||
from error_report.middleware import ExceptionProcessor
|
||||
from rest_framework.authtoken.models import Token
|
||||
|
||||
from common.models import InvenTreeSetting
|
||||
from InvenTree.urls import frontendpatterns
|
||||
|
||||
logger = logging.getLogger("inventree")
|
||||
@ -123,6 +122,9 @@ class Check2FAMiddleware(BaseRequire2FAMiddleware):
|
||||
"""Check if user is required to have MFA enabled."""
|
||||
def require_2fa(self, request):
|
||||
"""Use setting to check if MFA should be enforced for frontend page."""
|
||||
|
||||
from common.models import InvenTreeSetting
|
||||
|
||||
try:
|
||||
if url_matcher.resolve(request.path[1:]):
|
||||
return InvenTreeSetting.get_setting('LOGIN_ENFORCE_MFA')
|
||||
|
@ -21,7 +21,7 @@ from rest_framework.serializers import DecimalField
|
||||
from rest_framework.utils import model_meta
|
||||
from taggit.serializers import TaggitSerializer
|
||||
|
||||
from common.models import InvenTreeSetting
|
||||
import common.models as common_models
|
||||
from common.settings import currency_code_default, currency_code_mappings
|
||||
from InvenTree.fields import InvenTreeRestURLField, InvenTreeURLField
|
||||
from InvenTree.helpers_model import download_image_from_url
|
||||
@ -724,7 +724,7 @@ class RemoteImageMixin(metaclass=serializers.SerializerMetaclass):
|
||||
if not url:
|
||||
return
|
||||
|
||||
if not InvenTreeSetting.get_setting('INVENTREE_DOWNLOAD_FROM_URL'):
|
||||
if not common_models.InvenTreeSetting.get_setting('INVENTREE_DOWNLOAD_FROM_URL'):
|
||||
raise ValidationError(_("Downloading images from remote URL is not enabled"))
|
||||
|
||||
try:
|
||||
|
@ -233,6 +233,11 @@ class ExchangeRateMixin:
|
||||
Rate.objects.bulk_create(items)
|
||||
|
||||
|
||||
class InvenTreeTestCase(ExchangeRateMixin, UserMixin, TestCase):
|
||||
"""Testcase with user setup buildin."""
|
||||
pass
|
||||
|
||||
|
||||
class InvenTreeAPITestCase(ExchangeRateMixin, UserMixin, APITestCase):
|
||||
"""Base class for running InvenTree API tests."""
|
||||
|
||||
@ -408,8 +413,3 @@ class InvenTreeAPITestCase(ExchangeRateMixin, UserMixin, APITestCase):
|
||||
data.append(entry)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
class InvenTreeTestCase(ExchangeRateMixin, UserMixin, TestCase):
|
||||
"""Testcase with user setup buildin."""
|
||||
pass
|
||||
|
@ -31,8 +31,8 @@ from allauth_2fa.views import TwoFactorRemove
|
||||
from djmoney.contrib.exchange.models import ExchangeBackend, Rate
|
||||
from user_sessions.views import SessionDeleteOtherView, SessionDeleteView
|
||||
|
||||
from common.models import ColorTheme, InvenTreeSetting
|
||||
from common.settings import currency_code_default, currency_codes
|
||||
import common.models as common_models
|
||||
import common.settings as common_settings
|
||||
from part.models import PartCategory
|
||||
from users.models import RuleSet, check_user_role
|
||||
|
||||
@ -514,10 +514,10 @@ class SettingsView(TemplateView):
|
||||
"""Add data for template."""
|
||||
ctx = super().get_context_data(**kwargs).copy()
|
||||
|
||||
ctx['settings'] = InvenTreeSetting.objects.all().order_by('key')
|
||||
ctx['settings'] = common_models.InvenTreeSetting.objects.all().order_by('key')
|
||||
|
||||
ctx["base_currency"] = currency_code_default()
|
||||
ctx["currencies"] = currency_codes
|
||||
ctx["base_currency"] = common_settings.currency_code_default()
|
||||
ctx["currencies"] = common_settings.currency_codes
|
||||
|
||||
ctx["rates"] = Rate.objects.filter(backend="InvenTreeExchange")
|
||||
|
||||
@ -622,8 +622,8 @@ class AppearanceSelectView(RedirectView):
|
||||
def get_user_theme(self):
|
||||
"""Get current user color theme."""
|
||||
try:
|
||||
user_theme = ColorTheme.objects.filter(user=self.request.user).get()
|
||||
except ColorTheme.DoesNotExist:
|
||||
user_theme = common_models.ColorTheme.objects.filter(user=self.request.user).get()
|
||||
except common_models.ColorTheme.DoesNotExist:
|
||||
user_theme = None
|
||||
|
||||
return user_theme
|
||||
@ -637,7 +637,7 @@ class AppearanceSelectView(RedirectView):
|
||||
|
||||
# Create theme entry if user did not select one yet
|
||||
if not user_theme:
|
||||
user_theme = ColorTheme()
|
||||
user_theme = common_models.ColorTheme()
|
||||
user_theme.user = request.user
|
||||
|
||||
user_theme.name = theme
|
||||
|
@ -16,6 +16,7 @@ from InvenTree.helpers import str2bool, isNull, DownloadFile
|
||||
from InvenTree.status_codes import BuildStatus, BuildStatusGroups
|
||||
from InvenTree.mixins import CreateAPI, RetrieveUpdateDestroyAPI, ListCreateAPI
|
||||
|
||||
import common.models
|
||||
import build.admin
|
||||
import build.serializers
|
||||
from build.models import Build, BuildLine, BuildItem, BuildOrderAttachment
|
||||
@ -89,6 +90,21 @@ class BuildFilter(rest_filters.FilterSet):
|
||||
lookup_expr="iexact"
|
||||
)
|
||||
|
||||
project_code = rest_filters.ModelChoiceFilter(
|
||||
queryset=common.models.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 BuildList(APIDownloadMixin, ListCreateAPI):
|
||||
"""API endpoint for accessing a list of Build objects.
|
||||
@ -114,11 +130,13 @@ class BuildList(APIDownloadMixin, ListCreateAPI):
|
||||
'completed',
|
||||
'issued_by',
|
||||
'responsible',
|
||||
'project_code',
|
||||
'priority',
|
||||
]
|
||||
|
||||
ordering_field_aliases = {
|
||||
'reference': ['reference_int', 'reference'],
|
||||
'project_code': ['project_code__code'],
|
||||
}
|
||||
|
||||
ordering = '-reference'
|
||||
@ -129,6 +147,7 @@ class BuildList(APIDownloadMixin, ListCreateAPI):
|
||||
'part__name',
|
||||
'part__IPN',
|
||||
'part__description',
|
||||
'project_code__code',
|
||||
'priority',
|
||||
]
|
||||
|
||||
|
20
InvenTree/build/migrations/0048_build_project_code.py
Normal file
20
InvenTree/build/migrations/0048_build_project_code.py
Normal file
@ -0,0 +1,20 @@
|
||||
# Generated by Django 3.2.19 on 2023-05-14 09:22
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('common', '0019_projectcode_metadata'),
|
||||
('build', '0047_auto_20230606_1058'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='build',
|
||||
name='project_code',
|
||||
field=models.ForeignKey(blank=True, help_text='Project code for this build order', null=True, on_delete=django.db.models.deletion.SET_NULL, to='common.projectcode', verbose_name='Project Code'),
|
||||
),
|
||||
]
|
@ -32,9 +32,10 @@ import InvenTree.models
|
||||
import InvenTree.ready
|
||||
import InvenTree.tasks
|
||||
|
||||
import common.models
|
||||
from common.notifications import trigger_notification
|
||||
from plugin.events import trigger_event
|
||||
|
||||
import common.notifications
|
||||
import part.models
|
||||
import stock.models
|
||||
import users.models
|
||||
@ -301,6 +302,14 @@ class Build(MPTTModel, InvenTree.models.InvenTreeBarcodeMixin, InvenTree.models.
|
||||
help_text=_('Priority of this build order')
|
||||
)
|
||||
|
||||
project_code = models.ForeignKey(
|
||||
common.models.ProjectCode,
|
||||
on_delete=models.SET_NULL,
|
||||
blank=True, null=True,
|
||||
verbose_name=_('Project Code'),
|
||||
help_text=_('Project code for this build order'),
|
||||
)
|
||||
|
||||
def sub_builds(self, cascade=True):
|
||||
"""Return all Build Order objects under this one."""
|
||||
if cascade:
|
||||
@ -547,7 +556,7 @@ class Build(MPTTModel, InvenTree.models.InvenTreeBarcodeMixin, InvenTree.models.
|
||||
}
|
||||
}
|
||||
|
||||
common.notifications.trigger_notification(
|
||||
trigger_notification(
|
||||
build,
|
||||
'build.completed',
|
||||
targets=targets,
|
||||
|
@ -23,6 +23,7 @@ from InvenTree.status_codes import StockStatus
|
||||
from stock.models import generate_batch_code, StockItem, StockLocation
|
||||
from stock.serializers import StockItemSerializerBrief, LocationSerializer
|
||||
|
||||
from common.serializers import ProjectCodeSerializer
|
||||
import part.filters
|
||||
from part.serializers import BomItemSerializer, PartSerializer, PartBriefSerializer
|
||||
from users.serializers import OwnerSerializer
|
||||
@ -49,6 +50,8 @@ class BuildSerializer(InvenTreeModelSerializer):
|
||||
'parent',
|
||||
'part',
|
||||
'part_detail',
|
||||
'project_code',
|
||||
'project_code_detail',
|
||||
'overdue',
|
||||
'reference',
|
||||
'sales_order',
|
||||
@ -90,6 +93,8 @@ class BuildSerializer(InvenTreeModelSerializer):
|
||||
|
||||
barcode_hash = serializers.CharField(read_only=True)
|
||||
|
||||
project_code_detail = ProjectCodeSerializer(source='project_code', many=False, read_only=True)
|
||||
|
||||
@staticmethod
|
||||
def annotate_queryset(queryset):
|
||||
"""Add custom annotations to the BuildSerializer queryset, performing database queries as efficiently as possible.
|
||||
|
@ -108,6 +108,7 @@ src="{% static 'img/blank_image.png' %}"
|
||||
<td>{% trans "Build Description" %}</td>
|
||||
<td>{{ build.title }}</td>
|
||||
</tr>
|
||||
{% include "project_code_data.html" with instance=build %}
|
||||
{% include "barcode_data.html" with instance=build %}
|
||||
</table>
|
||||
|
||||
|
@ -19,22 +19,15 @@ class TestForwardMigrations(MigratorTestCase):
|
||||
name='Widget',
|
||||
description='Buildable Part',
|
||||
active=True,
|
||||
level=0, lft=0, rght=0, tree_id=0,
|
||||
)
|
||||
|
||||
with self.assertRaises(TypeError):
|
||||
# Cannot set the 'assembly' field as it hasn't been added to the db schema
|
||||
Part.objects.create(
|
||||
name='Blorb',
|
||||
description='ABCDE',
|
||||
assembly=True
|
||||
)
|
||||
|
||||
Build = self.old_state.apps.get_model('build', 'build')
|
||||
|
||||
Build.objects.create(
|
||||
part=buildable_part,
|
||||
title='A build of some stuff',
|
||||
quantity=50
|
||||
quantity=50,
|
||||
)
|
||||
|
||||
def test_items_exist(self):
|
||||
@ -67,7 +60,8 @@ class TestReferenceMigration(MigratorTestCase):
|
||||
|
||||
part = Part.objects.create(
|
||||
name='Part',
|
||||
description='A test part'
|
||||
description='A test part',
|
||||
level=0, lft=0, rght=0, tree_id=0,
|
||||
)
|
||||
|
||||
Build = self.old_state.apps.get_model('build', 'build')
|
||||
|
@ -525,7 +525,7 @@ settings_api_urls = [
|
||||
path(r'<int:pk>/', NotificationUserSettingsDetail.as_view(), name='api-notification-setting-detail'),
|
||||
|
||||
# Notification Settings List
|
||||
re_path(r'^.*$', NotificationUserSettingsList.as_view(), name='api-notifcation-setting-list'),
|
||||
re_path(r'^.*$', NotificationUserSettingsList.as_view(), name='api-notification-setting-list'),
|
||||
])),
|
||||
|
||||
# Global settings
|
||||
|
@ -8,8 +8,8 @@ from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.models import Group
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
import common.models
|
||||
import InvenTree.helpers
|
||||
from common.models import NotificationEntry, NotificationMessage
|
||||
from InvenTree.ready import isImportingData
|
||||
from plugin import registry
|
||||
from plugin.models import NotificationUserSetting, PluginConfig
|
||||
@ -247,7 +247,7 @@ class UIMessageNotification(SingleNotificationMethod):
|
||||
|
||||
def send(self, target):
|
||||
"""Send a UI notification to a user."""
|
||||
NotificationMessage.objects.create(
|
||||
common.models.NotificationMessage.objects.create(
|
||||
target_object=self.obj,
|
||||
source_object=target,
|
||||
user=target,
|
||||
@ -338,7 +338,7 @@ def trigger_notification(obj, category=None, obj_ref='pk', **kwargs):
|
||||
# Check if we have notified recently...
|
||||
delta = timedelta(days=1)
|
||||
|
||||
if NotificationEntry.check_recent(category, obj_ref_value, delta):
|
||||
if common.models.NotificationEntry.check_recent(category, obj_ref_value, delta):
|
||||
logger.info(f"Notification '{category}' has recently been sent for '{str(obj)}' - SKIPPING")
|
||||
return
|
||||
|
||||
@ -398,7 +398,7 @@ def trigger_notification(obj, category=None, obj_ref='pk', **kwargs):
|
||||
logger.error(error)
|
||||
|
||||
# Set delivery flag
|
||||
NotificationEntry.notify(category, obj_ref_value)
|
||||
common.models.NotificationEntry.notify(category, obj_ref_value)
|
||||
else:
|
||||
logger.info(f"No possible users for notification '{category}'")
|
||||
|
||||
|
@ -6,9 +6,7 @@ from django.urls import reverse
|
||||
from flags.state import flag_state
|
||||
from rest_framework import serializers
|
||||
|
||||
from common.models import (InvenTreeSetting, InvenTreeUserSetting,
|
||||
NewsFeedEntry, NotesImage, NotificationMessage,
|
||||
ProjectCode)
|
||||
import common.models as common_models
|
||||
from InvenTree.helpers import get_objectreference
|
||||
from InvenTree.helpers_model import construct_absolute_url
|
||||
from InvenTree.serializers import (InvenTreeImageSerializerField,
|
||||
@ -64,7 +62,7 @@ class GlobalSettingsSerializer(SettingsSerializer):
|
||||
class Meta:
|
||||
"""Meta options for GlobalSettingsSerializer."""
|
||||
|
||||
model = InvenTreeSetting
|
||||
model = common_models.InvenTreeSetting
|
||||
fields = [
|
||||
'pk',
|
||||
'key',
|
||||
@ -85,7 +83,7 @@ class UserSettingsSerializer(SettingsSerializer):
|
||||
class Meta:
|
||||
"""Meta options for UserSettingsSerializer."""
|
||||
|
||||
model = InvenTreeUserSetting
|
||||
model = common_models.InvenTreeUserSetting
|
||||
fields = [
|
||||
'pk',
|
||||
'key',
|
||||
@ -148,7 +146,7 @@ class NotificationMessageSerializer(InvenTreeModelSerializer):
|
||||
class Meta:
|
||||
"""Meta options for NotificationMessageSerializer."""
|
||||
|
||||
model = NotificationMessage
|
||||
model = common_models.NotificationMessage
|
||||
fields = [
|
||||
'pk',
|
||||
'target',
|
||||
@ -209,7 +207,7 @@ class NewsFeedEntrySerializer(InvenTreeModelSerializer):
|
||||
class Meta:
|
||||
"""Meta options for NewsFeedEntrySerializer."""
|
||||
|
||||
model = NewsFeedEntry
|
||||
model = common_models.NewsFeedEntry
|
||||
fields = [
|
||||
'pk',
|
||||
'feed_id',
|
||||
@ -243,7 +241,7 @@ class NotesImageSerializer(InvenTreeModelSerializer):
|
||||
class Meta:
|
||||
"""Meta options for NotesImageSerializer."""
|
||||
|
||||
model = NotesImage
|
||||
model = common_models.NotesImage
|
||||
fields = [
|
||||
'pk',
|
||||
'image',
|
||||
@ -265,7 +263,7 @@ class ProjectCodeSerializer(InvenTreeModelSerializer):
|
||||
class Meta:
|
||||
"""Meta options for ProjectCodeSerializer."""
|
||||
|
||||
model = ProjectCode
|
||||
model = common_models.ProjectCode
|
||||
fields = [
|
||||
'pk',
|
||||
'code',
|
||||
|
@ -226,7 +226,7 @@ class SettingsTest(InvenTreeTestCase):
|
||||
|
||||
cache.clear()
|
||||
|
||||
# Generate a number of new usesr
|
||||
# Generate a number of new users
|
||||
for idx in range(5):
|
||||
get_user_model().objects.create(
|
||||
username=f"User_{idx}",
|
||||
@ -417,7 +417,7 @@ class UserSettingsApiTest(InvenTreeAPITestCase):
|
||||
|
||||
self.assertTrue(str2bool(response.data['value']))
|
||||
|
||||
# Assign some falsey values
|
||||
# Assign some false(ish) values
|
||||
for v in ['false', False, '0', 'n', 'FalSe']:
|
||||
self.patch(
|
||||
url,
|
||||
@ -535,7 +535,7 @@ class NotificationUserSettingsApiTest(InvenTreeAPITestCase):
|
||||
|
||||
def test_api_list(self):
|
||||
"""Test list URL."""
|
||||
url = reverse('api-notifcation-setting-list')
|
||||
url = reverse('api-notification-setting-list')
|
||||
|
||||
self.get(url, expected_code=200)
|
||||
|
||||
@ -583,7 +583,7 @@ class PluginSettingsApiTest(PluginMixin, InvenTreeAPITestCase):
|
||||
|
||||
# Failure mode tests
|
||||
|
||||
# Non - exsistant plugin
|
||||
# Non-existent plugin
|
||||
url = reverse('api-plugin-setting-detail', kwargs={'plugin': 'doesnotexist', 'key': 'doesnotmatter'})
|
||||
response = self.get(url, expected_code=404)
|
||||
self.assertIn("Plugin 'doesnotexist' not installed", str(response.data))
|
||||
@ -729,7 +729,7 @@ class WebhookMessageTests(TestCase):
|
||||
|
||||
|
||||
class NotificationTest(InvenTreeAPITestCase):
|
||||
"""Tests for NotificationEntriy."""
|
||||
"""Tests for NotificationEntry."""
|
||||
|
||||
fixtures = [
|
||||
'users',
|
||||
@ -785,7 +785,7 @@ class NotificationTest(InvenTreeAPITestCase):
|
||||
messages = NotificationMessage.objects.all()
|
||||
|
||||
# As there are three staff users (including the 'test' user) we expect 30 notifications
|
||||
# However, one user is marked as i nactive
|
||||
# However, one user is marked as inactive
|
||||
self.assertEqual(messages.count(), 20)
|
||||
|
||||
# Only 10 messages related to *this* user
|
||||
|
@ -468,7 +468,7 @@ class SupplierPartTest(InvenTreeAPITestCase):
|
||||
self.assertIsNone(sp.availability_updated)
|
||||
self.assertEqual(sp.available, 0)
|
||||
|
||||
# Now, *update* the availabile quantity via the API
|
||||
# Now, *update* the available quantity via the API
|
||||
self.patch(
|
||||
reverse('api-supplier-part-detail', kwargs={'pk': sp.pk}),
|
||||
{
|
||||
|
@ -48,7 +48,8 @@ class TestManufacturerField(MigratorTestCase):
|
||||
# Create an initial part
|
||||
part = Part.objects.create(
|
||||
name='Screw',
|
||||
description='A single screw'
|
||||
description='A single screw',
|
||||
level=0, tree_id=0, lft=0, rght=0
|
||||
)
|
||||
|
||||
# Create a company to act as the supplier
|
||||
|
@ -13,7 +13,7 @@ from rest_framework import status
|
||||
from rest_framework.exceptions import ValidationError
|
||||
from rest_framework.response import Response
|
||||
|
||||
from common.models import InvenTreeSetting, ProjectCode
|
||||
import common.models as common_models
|
||||
from common.settings import settings
|
||||
from company.models import SupplierPart
|
||||
from generic.states import StatusView
|
||||
@ -139,7 +139,7 @@ class OrderFilter(rest_filters.FilterSet):
|
||||
return queryset.exclude(status__in=self.Meta.model.get_status_class().OPEN)
|
||||
|
||||
project_code = rest_filters.ModelChoiceFilter(
|
||||
queryset=ProjectCode.objects.all(),
|
||||
queryset=common_models.ProjectCode.objects.all(),
|
||||
field_name='project_code'
|
||||
)
|
||||
|
||||
@ -1457,7 +1457,7 @@ class OrderCalendarExport(ICalFeed):
|
||||
else:
|
||||
ordertype_title = _('Unknown')
|
||||
|
||||
return f'{InvenTreeSetting.get_setting("INVENTREE_COMPANY_NAME")} {ordertype_title}'
|
||||
return f'{common_models.InvenTreeSetting.get_setting("INVENTREE_COMPANY_NAME")} {ordertype_title}'
|
||||
|
||||
def product_id(self, obj):
|
||||
"""Return calendar product id."""
|
||||
|
@ -22,6 +22,7 @@ from djmoney.contrib.exchange.models import convert_money
|
||||
from djmoney.money import Money
|
||||
from mptt.models import TreeForeignKey
|
||||
|
||||
import common.models as common_models
|
||||
import InvenTree.helpers
|
||||
import InvenTree.ready
|
||||
import InvenTree.tasks
|
||||
@ -29,7 +30,6 @@ import InvenTree.validators
|
||||
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
|
||||
@ -231,7 +231,11 @@ 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'))
|
||||
project_code = models.ForeignKey(
|
||||
common_models.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'))
|
||||
|
||||
|
@ -13,11 +13,11 @@ 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
|
||||
import stock.serializers
|
||||
from common.serializers import ProjectCodeSerializer
|
||||
from company.serializers import (CompanyBriefSerializer, ContactSerializer,
|
||||
SupplierPartSerializer)
|
||||
from InvenTree.helpers import (extract_serial_numbers, hash_barcode, normalize,
|
||||
@ -73,7 +73,7 @@ class AbstractOrderSerializer(serializers.Serializer):
|
||||
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)
|
||||
project_code_detail = 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)
|
||||
|
@ -54,7 +54,7 @@ class PluginDetailAPITest(PluginMixin, InvenTreeAPITestCase):
|
||||
).data
|
||||
self.assertEqual(data['success'], True)
|
||||
|
||||
# valid - github url and packagename
|
||||
# valid - github url and package name
|
||||
data = self.post(
|
||||
url,
|
||||
{
|
||||
|
@ -14,6 +14,7 @@
|
||||
FullCalendar,
|
||||
getFormFieldValue,
|
||||
getTableData,
|
||||
global_settings,
|
||||
handleFormErrors,
|
||||
handleFormSuccess,
|
||||
imageHoverIcon,
|
||||
@ -64,7 +65,7 @@
|
||||
|
||||
|
||||
function buildFormFields() {
|
||||
return {
|
||||
let fields = {
|
||||
reference: {
|
||||
icon: 'fa-hashtag',
|
||||
},
|
||||
@ -76,6 +77,9 @@ function buildFormFields() {
|
||||
},
|
||||
title: {},
|
||||
quantity: {},
|
||||
project_code: {
|
||||
icon: 'fa-list',
|
||||
},
|
||||
priority: {},
|
||||
parent: {
|
||||
filters: {
|
||||
@ -111,6 +115,12 @@ function buildFormFields() {
|
||||
icon: 'fa-users',
|
||||
},
|
||||
};
|
||||
|
||||
if (!global_settings.PROJECT_CODES_ENABLED) {
|
||||
delete fields.project_code;
|
||||
}
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -2020,6 +2030,18 @@ function loadBuildTable(table, options) {
|
||||
title: '{% trans "Description" %}',
|
||||
switchable: true,
|
||||
},
|
||||
{
|
||||
field: 'project_code',
|
||||
title: '{% trans "Project Code" %}',
|
||||
sortable: true,
|
||||
switchable: global_settings.PROJECT_CODES_ENABLED,
|
||||
visible: global_settings.PROJECT_CODES_ENABLED,
|
||||
formatter: function(value, row) {
|
||||
if (row.project_code_detail) {
|
||||
return `<span title='${row.project_code_detail.description}'>${row.project_code_detail.code}</span>`;
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'priority',
|
||||
title: '{% trans "Priority" %}',
|
||||
|
@ -440,7 +440,7 @@ function getPluginTableFilters() {
|
||||
// Return a dictionary of filters for the "build" table
|
||||
function getBuildTableFilters() {
|
||||
|
||||
return {
|
||||
let filters = {
|
||||
status: {
|
||||
title: '{% trans "Build status" %}',
|
||||
options: buildCodes,
|
||||
@ -477,6 +477,13 @@ function getBuildTableFilters() {
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
if (global_settings.PROJECT_CODES_ENABLED) {
|
||||
filters['has_project_code'] = constructHasProjectCodeFilter();
|
||||
filters['project_code'] = constructProjectCodeFilter();
|
||||
}
|
||||
|
||||
return filters;
|
||||
}
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user