2
0
mirror of https://github.com/inventree/InvenTree.git synced 2026-07-04 06:00:38 +00:00

Project code active (#12250)

* Add 'active' field to the ProjectCode model

- Allows retiring of old project codes without deleting

* Update UI table

* Refactor ProjectCodeField

* Add unit test

* Bump API version and CHANGELOG
This commit is contained in:
Oliver
2026-06-25 14:26:53 +10:00
committed by GitHub
parent 3f36537391
commit e5fa67ca9f
16 changed files with 112 additions and 38 deletions
+2
View File
@@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added ### Added
- [#12250](https://github.com/inventree/InvenTree/pull/12250) adds "active" field to the ProjectCode model and API endpoints
### Changed ### Changed
### Removed ### Removed
@@ -1,11 +1,14 @@
"""InvenTree API version information.""" """InvenTree API version information."""
# InvenTree API version # InvenTree API version
INVENTREE_API_VERSION = 512 INVENTREE_API_VERSION = 513
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about.""" """Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
INVENTREE_API_TEXT = """ INVENTREE_API_TEXT = """
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 v512 -> 2026-06-20 : https://github.com/inventree/InvenTree/pull/12022
- Adds optional "merge" field to each item in the Stock Transfer API endpoint - 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 - When merge is enabled, transferred stock is combined into compatible existing stock at the destination
+2 -1
View File
@@ -115,7 +115,8 @@ class BarcodeScanResultAdmin(admin.ModelAdmin):
class ProjectCodeAdmin(admin.ModelAdmin): class ProjectCodeAdmin(admin.ModelAdmin):
"""Admin settings for ProjectCode.""" """Admin settings for ProjectCode."""
list_display = ('code', 'description') list_display = ('code', 'description', 'active')
list_filter = ('active',)
search_fields = ('code', 'description') search_fields = ('code', 'description')
+1 -1
View File
@@ -491,7 +491,7 @@ class ProjectCodeList(DataExportViewMixin, ListCreateAPI):
filter_backends = SEARCH_ORDER_FILTER filter_backends = SEARCH_ORDER_FILTER
ordering_fields = ['code'] ordering_fields = ['code']
filterset_fields = ['active']
search_fields = ['code', 'description'] search_fields = ['code', 'description']
@@ -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",
),
),
]
+6
View File
@@ -181,6 +181,12 @@ class ProjectCode(InvenTree.models.InvenTreeMetadataModel):
help_text=_('Project description'), help_text=_('Project description'),
) )
active = models.BooleanField(
default=True,
verbose_name=_('Active'),
help_text=_('Is this project code active?'),
)
responsible = models.ForeignKey( responsible = models.ForeignKey(
users.models.Owner, users.models.Owner,
on_delete=models.SET_NULL, on_delete=models.SET_NULL,
+8 -1
View File
@@ -417,7 +417,14 @@ class ProjectCodeSerializer(DataImportExportSerializerMixin, InvenTreeModelSeria
"""Meta options for ProjectCodeSerializer.""" """Meta options for ProjectCodeSerializer."""
model = common_models.ProjectCode model = common_models.ProjectCode
fields = ['pk', 'code', 'description', 'responsible', 'responsible_detail'] fields = [
'pk',
'code',
'description',
'active',
'responsible',
'responsible_detail',
]
responsible_detail = OwnerSerializer( responsible_detail = OwnerSerializer(
source='responsible', read_only=True, allow_null=True source='responsible', read_only=True, allow_null=True
+16
View File
@@ -1752,6 +1752,22 @@ class ProjectCodesTest(InvenTreeAPITestCase):
str(response.data['code']), 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): def test_write_access(self):
"""Test that non-staff users have read-only access.""" """Test that non-staff users have read-only access."""
# By default user has staff access, can create a new project code # By default user has staff access, can create a new project code
+2 -5
View File
@@ -5,7 +5,6 @@ import {
IconCircleCheck, IconCircleCheck,
IconInfoCircle, IconInfoCircle,
IconLink, IconLink,
IconList,
IconSitemap, IconSitemap,
IconTruckDelivery, IconTruckDelivery,
IconUsersGroup IconUsersGroup
@@ -36,7 +35,7 @@ import {
} from '../hooks/UseGenerator'; } from '../hooks/UseGenerator';
import { useGlobalSettingsState } from '../states/SettingsStates'; import { useGlobalSettingsState } from '../states/SettingsStates';
import { RenderPartColumn } from '../tables/ColumnRenderers'; import { RenderPartColumn } from '../tables/ColumnRenderers';
import { TagsField } from './CommonFields'; import { ProjectCodeField, TagsField } from './CommonFields';
/** /**
* Field set for BuildOrder forms * Field set for BuildOrder forms
@@ -93,9 +92,7 @@ export function useBuildOrderFields({
}, },
title: {}, title: {},
quantity: {}, quantity: {},
project_code: { project_code: ProjectCodeField(),
icon: <IconList />
},
priority: {}, priority: {},
parent: { parent: {
icon: <IconSitemap />, icon: <IconSitemap />,
+12
View File
@@ -1,5 +1,6 @@
import type { ApiFormFieldType } from '@lib/types/Forms'; import type { ApiFormFieldType } from '@lib/types/Forms';
import { t } from '@lingui/core/macro'; import { t } from '@lingui/core/macro';
import { IconList } from '@tabler/icons-react';
export function TagsField({ export function TagsField({
label, label,
@@ -17,3 +18,14 @@ export function TagsField({
placeholder: placeholder ?? t`Select tags` 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: <IconList />
};
}
+4 -5
View File
@@ -5,7 +5,6 @@ import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
import { ModelType } from '@lib/enums/ModelType'; import { ModelType } from '@lib/enums/ModelType';
import { apiUrl } from '@lib/functions/Api'; import { apiUrl } from '@lib/functions/Api';
import type { ApiFormFieldSet, ApiFormFieldType } from '@lib/types/Forms'; import type { ApiFormFieldSet, ApiFormFieldType } from '@lib/types/Forms';
import { t } from '@lingui/core/macro';
import type { import type {
StatusCodeInterface, StatusCodeInterface,
StatusCodeListInterface StatusCodeListInterface
@@ -13,6 +12,7 @@ import type {
import { useApi } from '../contexts/ApiContext'; import { useApi } from '../contexts/ApiContext';
import { useGlobalStatusState } from '../states/GlobalStatusState'; import { useGlobalStatusState } from '../states/GlobalStatusState';
import { useUserState } from '../states/UserState'; import { useUserState } from '../states/UserState';
import { ProjectCodeField } from './CommonFields';
export function projectCodeFields(): ApiFormFieldSet { export function projectCodeFields(): ApiFormFieldSet {
return { return {
@@ -20,7 +20,8 @@ export function projectCodeFields(): ApiFormFieldSet {
description: {}, description: {},
responsible: { responsible: {
icon: <IconUsers /> icon: <IconUsers />
} },
active: {}
}; };
} }
@@ -90,9 +91,7 @@ export function extraLineItemFields(): ApiFormFieldSet {
quantity: {}, quantity: {},
price: {}, price: {},
price_currency: {}, price_currency: {},
project_code: { project_code: ProjectCodeField(),
description: t`Select project code for this line item`
},
notes: {}, notes: {},
link: {} link: {}
}; };
@@ -20,7 +20,6 @@ import {
IconHash, IconHash,
IconInfoCircle, IconInfoCircle,
IconLink, IconLink,
IconList,
IconNotes, IconNotes,
IconSitemap, IconSitemap,
IconUser, IconUser,
@@ -57,7 +56,7 @@ import {
useSerialNumberGenerator useSerialNumberGenerator
} from '../hooks/UseGenerator'; } from '../hooks/UseGenerator';
import { useGlobalSettingsState } from '../states/SettingsStates'; 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 * Construct a set of fields for creating / editing a PurchaseOrderLineItem instance
*/ */
@@ -191,9 +190,7 @@ export function usePurchaseOrderLineItemFields({
value: autoPricing, value: autoPricing,
onValueChange: setAutoPricing onValueChange: setAutoPricing
}, },
project_code: { project_code: ProjectCodeField(),
description: t`Select project code for this line item`
},
target_date: { target_date: {
icon: <IconCalendar /> icon: <IconCalendar />
}, },
@@ -271,9 +268,7 @@ export function usePurchaseOrderFields({
} }
}, },
supplier_reference: {}, supplier_reference: {},
project_code: { project_code: ProjectCodeField(),
icon: <IconList />
},
order_currency: { order_currency: {
icon: <IconCoins /> icon: <IconCoins />
}, },
+3 -5
View File
@@ -23,7 +23,7 @@ import { Thumbnail } from '../components/images/Thumbnail';
import { useCreateApiFormModal } from '../hooks/UseForm'; import { useCreateApiFormModal } from '../hooks/UseForm';
import { useGlobalSettingsState } from '../states/SettingsStates'; import { useGlobalSettingsState } from '../states/SettingsStates';
import { StatusFilterOptions } from '../tables/Filter'; import { StatusFilterOptions } from '../tables/Filter';
import { TagsField } from './CommonFields'; import { ProjectCodeField, TagsField } from './CommonFields';
export function useReturnOrderFields({ export function useReturnOrderFields({
duplicateOrderId duplicateOrderId
@@ -44,7 +44,7 @@ export function useReturnOrderFields({
} }
}, },
customer_reference: {}, customer_reference: {},
project_code: {}, project_code: ProjectCodeField(),
order_currency: {}, order_currency: {},
start_date: { start_date: {
icon: <IconCalendar /> icon: <IconCalendar />
@@ -138,9 +138,7 @@ export function useReturnOrderLineItemFields({
}, },
price: {}, price: {},
price_currency: {}, price_currency: {},
project_code: { project_code: ProjectCodeField(),
description: t`Select project code for this line item`
},
target_date: {}, target_date: {},
notes: {}, notes: {},
link: {} link: {}
+3 -5
View File
@@ -31,7 +31,7 @@ import { useCreateApiFormModal, useEditApiFormModal } from '../hooks/UseForm';
import { useGlobalSettingsState } from '../states/SettingsStates'; import { useGlobalSettingsState } from '../states/SettingsStates';
import { useUserState } from '../states/UserState'; import { useUserState } from '../states/UserState';
import { RenderPartColumn } from '../tables/ColumnRenderers'; import { RenderPartColumn } from '../tables/ColumnRenderers';
import { TagsField } from './CommonFields'; import { ProjectCodeField, TagsField } from './CommonFields';
export function useSalesOrderFields({ export function useSalesOrderFields({
duplicateOrderId duplicateOrderId
@@ -57,7 +57,7 @@ export function useSalesOrderFields({
} }
}, },
customer_reference: {}, customer_reference: {},
project_code: {}, project_code: ProjectCodeField(),
order_currency: {}, order_currency: {},
start_date: { start_date: {
icon: <IconCalendar /> icon: <IconCalendar />
@@ -194,9 +194,7 @@ export function useSalesOrderLineItemFields({
value: partCurrency, value: partCurrency,
onValueChange: setPartCurrency onValueChange: setPartCurrency
}, },
project_code: { project_code: ProjectCodeField(),
description: t`Select project code for this line item`
},
target_date: {}, target_date: {},
notes: {}, notes: {},
link: {} link: {}
@@ -10,7 +10,7 @@ import type { TableFieldRowProps } from '../components/forms/fields/TableField';
import { useCreateApiFormModal } from '../hooks/UseForm'; import { useCreateApiFormModal } from '../hooks/UseForm';
import { useGlobalSettingsState } from '../states/SettingsStates'; import { useGlobalSettingsState } from '../states/SettingsStates';
import { RenderPartColumn } from '../tables/ColumnRenderers'; import { RenderPartColumn } from '../tables/ColumnRenderers';
import { TagsField } from './CommonFields'; import { ProjectCodeField, TagsField } from './CommonFields';
export function useTransferOrderFields({ export function useTransferOrderFields({
duplicateOrderId duplicateOrderId
@@ -23,7 +23,7 @@ export function useTransferOrderFields({
const fields: ApiFormFieldSet = { const fields: ApiFormFieldSet = {
reference: {}, reference: {},
description: {}, description: {},
project_code: {}, project_code: ProjectCodeField(),
start_date: { start_date: {
icon: <IconCalendar /> icon: <IconCalendar />
}, },
@@ -91,9 +91,7 @@ export function useTransferOrderLineItemFields({
}, },
reference: {}, reference: {},
quantity: {}, quantity: {},
project_code: { project_code: ProjectCodeField(),
description: t`Select project code for this line item`
},
target_date: {}, target_date: {},
notes: {}, notes: {},
link: {} link: {}
@@ -11,6 +11,7 @@ import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
import { UserRoles } from '@lib/enums/Roles'; import { UserRoles } from '@lib/enums/Roles';
import { apiUrl } from '@lib/functions/Api'; import { apiUrl } from '@lib/functions/Api';
import useTable from '@lib/hooks/UseTable'; import useTable from '@lib/hooks/UseTable';
import type { TableFilter } from '@lib/index';
import type { TableColumn } from '@lib/types/Tables'; import type { TableColumn } from '@lib/types/Tables';
import { projectCodeFields } from '../../forms/CommonForms'; import { projectCodeFields } from '../../forms/CommonForms';
import { import {
@@ -19,7 +20,11 @@ import {
useEditApiFormModal useEditApiFormModal
} from '../../hooks/UseForm'; } from '../../hooks/UseForm';
import { useUserState } from '../../states/UserState'; import { useUserState } from '../../states/UserState';
import { DescriptionColumn, ResponsibleColumn } from '../ColumnRenderers'; import {
BooleanColumn,
DescriptionColumn,
ResponsibleColumn
} from '../ColumnRenderers';
import { InvenTreeTable } from '../InvenTreeTable'; import { InvenTreeTable } from '../InvenTreeTable';
/** /**
@@ -37,6 +42,9 @@ export default function ProjectCodeTable() {
sortable: true sortable: true
}, },
DescriptionColumn({}), DescriptionColumn({}),
BooleanColumn({
accessor: 'active'
}),
ResponsibleColumn({}) ResponsibleColumn({})
]; ];
}, []); }, []);
@@ -89,6 +97,17 @@ export default function ProjectCodeTable() {
[user] [user]
); );
const tableFilters: TableFilter[] = useMemo(() => {
return [
{
name: 'active',
label: t`Active`,
description: t`Show active items`,
type: 'boolean'
}
];
}, []);
const tableActions = useMemo(() => { const tableActions = useMemo(() => {
return [ return [
<AddItemButton <AddItemButton
@@ -111,6 +130,7 @@ export default function ProjectCodeTable() {
props={{ props={{
rowActions: rowActions, rowActions: rowActions,
tableActions: tableActions, tableActions: tableActions,
tableFilters: tableFilters,
enableDownload: true enableDownload: true
}} }}
/> />