diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 3b3fa1e979..440b246f13 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,7 +1,7 @@ # Dockerfile for the InvenTree devcontainer # This container is used for development of the InvenTree project, and includes all necessary dependencies for both backend and frontend development. -FROM mcr.microsoft.com/devcontainers/python:3.14-trixie@sha256:66af9ee0a89e76f83b5c54a0cfdc19966ec8e443e294707f8f9b45f465b42cf1 +FROM mcr.microsoft.com/devcontainers/python:3.12-trixie@sha256:5440cb68898d190ad6c6e8a4634ce89d0645bea47f9c8beb75612bb8e3983711 # InvenTree paths ENV INVENTREE_HOME="/home/inventree" diff --git a/CHANGELOG.md b/CHANGELOG.md index 410d9a6054..4bea76d17c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- [#12250](https://github.com/inventree/InvenTree/pull/12250) adds "active" field to the ProjectCode model and API endpoints + ### Changed ### Removed diff --git a/src/backend/InvenTree/InvenTree/api_version.py b/src/backend/InvenTree/InvenTree/api_version.py index 3b88b40d2d..c51796f4da 100644 --- a/src/backend/InvenTree/InvenTree/api_version.py +++ b/src/backend/InvenTree/InvenTree/api_version.py @@ -1,16 +1,19 @@ """InvenTree API version information.""" # InvenTree API version -INVENTREE_API_VERSION = 513 +INVENTREE_API_VERSION = 514 """Increment this API version number whenever there is a significant change to the API that any clients need to know about.""" INVENTREE_API_TEXT = """ -v513 -> 2026-06-25 : https://github.com/inventree/InvenTree/pull/11971 +v514 -> 2026-06-25 : https://github.com/inventree/InvenTree/pull/11971 - Removes direct "notes" field from any models which previously supported markdown notes - Adds a generic "Note" model which can be attached to any model type via a generic foreign key relationship - Allow multiple notes to be attached to a single object, and for notes to be created / edited / deleted via the API +v513 -> 2026-06-25 : https://github.com/inventree/InvenTree/pull/12250 + - Adds "active" field to the ProjectCode model and API endpoints + v512 -> 2026-06-20 : https://github.com/inventree/InvenTree/pull/12022 - Adds optional "merge" field to each item in the Stock Transfer API endpoint - When merge is enabled, transferred stock is combined into compatible existing stock at the destination diff --git a/src/backend/InvenTree/common/admin.py b/src/backend/InvenTree/common/admin.py index 896adee9fa..271e22453b 100644 --- a/src/backend/InvenTree/common/admin.py +++ b/src/backend/InvenTree/common/admin.py @@ -124,7 +124,8 @@ class BarcodeScanResultAdmin(admin.ModelAdmin): class ProjectCodeAdmin(admin.ModelAdmin): """Admin settings for ProjectCode.""" - list_display = ('code', 'description') + list_display = ('code', 'description', 'active') + list_filter = ('active',) search_fields = ('code', 'description') diff --git a/src/backend/InvenTree/common/api.py b/src/backend/InvenTree/common/api.py index e6c8b576c0..2c71bb9b3e 100644 --- a/src/backend/InvenTree/common/api.py +++ b/src/backend/InvenTree/common/api.py @@ -512,7 +512,7 @@ class ProjectCodeList(DataExportViewMixin, ListCreateAPI): filter_backends = SEARCH_ORDER_FILTER ordering_fields = ['code'] - + filterset_fields = ['active'] search_fields = ['code', 'description'] diff --git a/src/backend/InvenTree/common/migrations/0045_projectcode_active.py b/src/backend/InvenTree/common/migrations/0045_projectcode_active.py new file mode 100644 index 0000000000..c0a8461972 --- /dev/null +++ b/src/backend/InvenTree/common/migrations/0045_projectcode_active.py @@ -0,0 +1,22 @@ +# Generated by Django 5.2.15 on 2026-06-25 03:13 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("common", "0044_notificationmessage_charfield_pk"), + ] + + operations = [ + migrations.AddField( + model_name="projectcode", + name="active", + field=models.BooleanField( + default=True, + help_text="Is this project code active?", + verbose_name="Active", + ), + ), + ] diff --git a/src/backend/InvenTree/common/models.py b/src/backend/InvenTree/common/models.py index ba63de4c87..696ea2bd16 100644 --- a/src/backend/InvenTree/common/models.py +++ b/src/backend/InvenTree/common/models.py @@ -183,6 +183,12 @@ class ProjectCode(InvenTree.models.InvenTreeMetadataModel): help_text=_('Project description'), ) + active = models.BooleanField( + default=True, + verbose_name=_('Active'), + help_text=_('Is this project code active?'), + ) + responsible = models.ForeignKey( users.models.Owner, on_delete=models.SET_NULL, diff --git a/src/backend/InvenTree/common/serializers.py b/src/backend/InvenTree/common/serializers.py index b89df50b37..0020eac8c0 100644 --- a/src/backend/InvenTree/common/serializers.py +++ b/src/backend/InvenTree/common/serializers.py @@ -417,7 +417,14 @@ class ProjectCodeSerializer(DataImportExportSerializerMixin, InvenTreeModelSeria """Meta options for ProjectCodeSerializer.""" model = common_models.ProjectCode - fields = ['pk', 'code', 'description', 'responsible', 'responsible_detail'] + fields = [ + 'pk', + 'code', + 'description', + 'active', + 'responsible', + 'responsible_detail', + ] responsible_detail = OwnerSerializer( source='responsible', read_only=True, allow_null=True diff --git a/src/backend/InvenTree/common/tests.py b/src/backend/InvenTree/common/tests.py index 8e9a6165c9..69d39d3a6f 100644 --- a/src/backend/InvenTree/common/tests.py +++ b/src/backend/InvenTree/common/tests.py @@ -1813,6 +1813,22 @@ class ProjectCodesTest(InvenTreeAPITestCase): str(response.data['code']), ) + def test_filter_active(self): + """Test that the 'active' field can be filtered via the API.""" + # Mark one code as inactive + code = ProjectCode.objects.first() + code.active = False + code.save() + + active_count = ProjectCode.objects.filter(active=True).count() + inactive_count = ProjectCode.objects.filter(active=False).count() + + response = self.get(self.url, data={'active': True}, expected_code=200) + self.assertEqual(len(response.data), active_count) + + response = self.get(self.url, data={'active': False}, expected_code=200) + self.assertEqual(len(response.data), inactive_count) + def test_write_access(self): """Test that non-staff users have read-only access.""" # By default user has staff access, can create a new project code diff --git a/src/frontend/src/forms/BuildForms.tsx b/src/frontend/src/forms/BuildForms.tsx index 3fd3acbc41..2ff3c0bd53 100644 --- a/src/frontend/src/forms/BuildForms.tsx +++ b/src/frontend/src/forms/BuildForms.tsx @@ -5,7 +5,6 @@ import { IconCircleCheck, IconInfoCircle, IconLink, - IconList, IconSitemap, IconTruckDelivery, IconUsersGroup @@ -36,7 +35,7 @@ import { } from '../hooks/UseGenerator'; import { useGlobalSettingsState } from '../states/SettingsStates'; import { RenderPartColumn } from '../tables/ColumnRenderers'; -import { TagsField } from './CommonFields'; +import { ProjectCodeField, TagsField } from './CommonFields'; /** * Field set for BuildOrder forms @@ -93,9 +92,7 @@ export function useBuildOrderFields({ }, title: {}, quantity: {}, - project_code: { - icon: - }, + project_code: ProjectCodeField(), priority: {}, parent: { icon: , diff --git a/src/frontend/src/forms/CommonFields.tsx b/src/frontend/src/forms/CommonFields.tsx index 9fcd5fd25b..07f16e0bb9 100644 --- a/src/frontend/src/forms/CommonFields.tsx +++ b/src/frontend/src/forms/CommonFields.tsx @@ -1,5 +1,6 @@ import type { ApiFormFieldType } from '@lib/types/Forms'; import { t } from '@lingui/core/macro'; +import { IconList } from '@tabler/icons-react'; export function TagsField({ label, @@ -17,3 +18,14 @@ export function TagsField({ placeholder: placeholder ?? t`Select tags` }; } + +export function ProjectCodeField(): ApiFormFieldType { + return { + filters: { + active: true + }, + label: t`Project Code`, + description: t`Select project code for this item`, + icon: + }; +} diff --git a/src/frontend/src/forms/CommonForms.tsx b/src/frontend/src/forms/CommonForms.tsx index 6fb95914b5..91fcd26ce4 100644 --- a/src/frontend/src/forms/CommonForms.tsx +++ b/src/frontend/src/forms/CommonForms.tsx @@ -5,7 +5,6 @@ import { ApiEndpoints } from '@lib/enums/ApiEndpoints'; import { ModelType } from '@lib/enums/ModelType'; import { apiUrl } from '@lib/functions/Api'; import type { ApiFormFieldSet, ApiFormFieldType } from '@lib/types/Forms'; -import { t } from '@lingui/core/macro'; import type { StatusCodeInterface, StatusCodeListInterface @@ -13,6 +12,7 @@ import type { import { useApi } from '../contexts/ApiContext'; import { useGlobalStatusState } from '../states/GlobalStatusState'; import { useUserState } from '../states/UserState'; +import { ProjectCodeField } from './CommonFields'; export function projectCodeFields(): ApiFormFieldSet { return { @@ -20,7 +20,8 @@ export function projectCodeFields(): ApiFormFieldSet { description: {}, responsible: { icon: - } + }, + active: {} }; } @@ -90,9 +91,7 @@ export function extraLineItemFields(): ApiFormFieldSet { quantity: {}, price: {}, price_currency: {}, - project_code: { - description: t`Select project code for this line item` - }, + project_code: ProjectCodeField(), notes: {}, link: {} }; diff --git a/src/frontend/src/forms/PurchaseOrderForms.tsx b/src/frontend/src/forms/PurchaseOrderForms.tsx index 4faf5756ad..3dce873eb6 100644 --- a/src/frontend/src/forms/PurchaseOrderForms.tsx +++ b/src/frontend/src/forms/PurchaseOrderForms.tsx @@ -20,7 +20,6 @@ import { IconHash, IconInfoCircle, IconLink, - IconList, IconNotes, IconSitemap, IconUser, @@ -57,7 +56,7 @@ import { useSerialNumberGenerator } from '../hooks/UseGenerator'; import { useGlobalSettingsState } from '../states/SettingsStates'; -import { TagsField } from './CommonFields'; +import { ProjectCodeField, TagsField } from './CommonFields'; /* * Construct a set of fields for creating / editing a PurchaseOrderLineItem instance */ @@ -191,9 +190,7 @@ export function usePurchaseOrderLineItemFields({ value: autoPricing, onValueChange: setAutoPricing }, - project_code: { - description: t`Select project code for this line item` - }, + project_code: ProjectCodeField(), target_date: { icon: }, @@ -271,9 +268,7 @@ export function usePurchaseOrderFields({ } }, supplier_reference: {}, - project_code: { - icon: - }, + project_code: ProjectCodeField(), order_currency: { icon: }, diff --git a/src/frontend/src/forms/ReturnOrderForms.tsx b/src/frontend/src/forms/ReturnOrderForms.tsx index ea919f5da7..3377ff1f22 100644 --- a/src/frontend/src/forms/ReturnOrderForms.tsx +++ b/src/frontend/src/forms/ReturnOrderForms.tsx @@ -23,7 +23,7 @@ import { Thumbnail } from '../components/images/Thumbnail'; import { useCreateApiFormModal } from '../hooks/UseForm'; import { useGlobalSettingsState } from '../states/SettingsStates'; import { StatusFilterOptions } from '../tables/Filter'; -import { TagsField } from './CommonFields'; +import { ProjectCodeField, TagsField } from './CommonFields'; export function useReturnOrderFields({ duplicateOrderId @@ -44,7 +44,7 @@ export function useReturnOrderFields({ } }, customer_reference: {}, - project_code: {}, + project_code: ProjectCodeField(), order_currency: {}, start_date: { icon: @@ -138,9 +138,7 @@ export function useReturnOrderLineItemFields({ }, price: {}, price_currency: {}, - project_code: { - description: t`Select project code for this line item` - }, + project_code: ProjectCodeField(), target_date: {}, notes: {}, link: {} diff --git a/src/frontend/src/forms/SalesOrderForms.tsx b/src/frontend/src/forms/SalesOrderForms.tsx index 125ca7f894..a863a51852 100644 --- a/src/frontend/src/forms/SalesOrderForms.tsx +++ b/src/frontend/src/forms/SalesOrderForms.tsx @@ -31,7 +31,7 @@ import { useCreateApiFormModal, useEditApiFormModal } from '../hooks/UseForm'; import { useGlobalSettingsState } from '../states/SettingsStates'; import { useUserState } from '../states/UserState'; import { RenderPartColumn } from '../tables/ColumnRenderers'; -import { TagsField } from './CommonFields'; +import { ProjectCodeField, TagsField } from './CommonFields'; export function useSalesOrderFields({ duplicateOrderId @@ -57,7 +57,7 @@ export function useSalesOrderFields({ } }, customer_reference: {}, - project_code: {}, + project_code: ProjectCodeField(), order_currency: {}, start_date: { icon: @@ -194,9 +194,7 @@ export function useSalesOrderLineItemFields({ value: partCurrency, onValueChange: setPartCurrency }, - project_code: { - description: t`Select project code for this line item` - }, + project_code: ProjectCodeField(), target_date: {}, notes: {}, link: {} diff --git a/src/frontend/src/forms/TransferOrderForms.tsx b/src/frontend/src/forms/TransferOrderForms.tsx index 3935d89b92..638028603d 100644 --- a/src/frontend/src/forms/TransferOrderForms.tsx +++ b/src/frontend/src/forms/TransferOrderForms.tsx @@ -10,7 +10,7 @@ import type { TableFieldRowProps } from '../components/forms/fields/TableField'; import { useCreateApiFormModal } from '../hooks/UseForm'; import { useGlobalSettingsState } from '../states/SettingsStates'; import { RenderPartColumn } from '../tables/ColumnRenderers'; -import { TagsField } from './CommonFields'; +import { ProjectCodeField, TagsField } from './CommonFields'; export function useTransferOrderFields({ duplicateOrderId @@ -23,7 +23,7 @@ export function useTransferOrderFields({ const fields: ApiFormFieldSet = { reference: {}, description: {}, - project_code: {}, + project_code: ProjectCodeField(), start_date: { icon: }, @@ -91,9 +91,7 @@ export function useTransferOrderLineItemFields({ }, reference: {}, quantity: {}, - project_code: { - description: t`Select project code for this line item` - }, + project_code: ProjectCodeField(), target_date: {}, notes: {}, link: {} diff --git a/src/frontend/src/tables/settings/ProjectCodeTable.tsx b/src/frontend/src/tables/settings/ProjectCodeTable.tsx index d6cdddf9e8..e20e3210ed 100644 --- a/src/frontend/src/tables/settings/ProjectCodeTable.tsx +++ b/src/frontend/src/tables/settings/ProjectCodeTable.tsx @@ -11,6 +11,7 @@ import { ApiEndpoints } from '@lib/enums/ApiEndpoints'; import { UserRoles } from '@lib/enums/Roles'; import { apiUrl } from '@lib/functions/Api'; import useTable from '@lib/hooks/UseTable'; +import type { TableFilter } from '@lib/index'; import type { TableColumn } from '@lib/types/Tables'; import { projectCodeFields } from '../../forms/CommonForms'; import { @@ -19,7 +20,11 @@ import { useEditApiFormModal } from '../../hooks/UseForm'; import { useUserState } from '../../states/UserState'; -import { DescriptionColumn, ResponsibleColumn } from '../ColumnRenderers'; +import { + BooleanColumn, + DescriptionColumn, + ResponsibleColumn +} from '../ColumnRenderers'; import { InvenTreeTable } from '../InvenTreeTable'; /** @@ -37,6 +42,9 @@ export default function ProjectCodeTable() { sortable: true }, DescriptionColumn({}), + BooleanColumn({ + accessor: 'active' + }), ResponsibleColumn({}) ]; }, []); @@ -89,6 +97,17 @@ export default function ProjectCodeTable() { [user] ); + const tableFilters: TableFilter[] = useMemo(() => { + return [ + { + name: 'active', + label: t`Active`, + description: t`Show active items`, + type: 'boolean' + } + ]; + }, []); + const tableActions = useMemo(() => { return [