diff --git a/CHANGELOG.md b/CHANGELOG.md index a7970539d254..c7d0e204f904 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [#10887](https://github.com/inventree/InvenTree/pull/10887) adds the ability to auto-allocate tracked items against specific build outputs. Currently, this will only allocate items where the serial number of the tracked item matches the serial number of the build output, but in future this may be extended to allow for more flexible allocation rules. - [#11372](https://github.com/inventree/InvenTree/pull/11372) adds backup metadata setter and restore metadata validator functions to ensure common footguns are harder to trigger when using the backup and restore functionality. - [#11374](https://github.com/inventree/InvenTree/pull/11374) adds `updated_at` field on purchase, sales and return orders. +- [#11459](https://github.com/inventree/InvenTree/pull/11459) adds parameter support for the StockItem model ### Changed diff --git a/docs/docs/concepts/parameters.md b/docs/docs/concepts/parameters.md index ca44989dab64..b4e76a490282 100644 --- a/docs/docs/concepts/parameters.md +++ b/docs/docs/concepts/parameters.md @@ -40,9 +40,9 @@ Parameter templates are created and edited via the [admin interface](../settings To create a template: - Navigate to the "Settings" page -- Click on the "Part Parameters" tab +- Click on the "Parameters" tab - Click on the "New Parameter" button -- Fill out the `Create Part Parameter Template` form: `Name` (required) and `Units` (optional) fields +- Fill out the `Create Parameter Template` form: `Name` (required) and `Units` (optional) fields - Click on the "Submit" button. An existing template can be edited by clicking on the "Edit" button associated with that template: @@ -53,9 +53,9 @@ An existing template can be edited by clicking on the "Edit" button associated w After [creating a template](#create-template) or using the existing templates, you can add parameters to any part. -To add a parameter, navigate to a specific part detail page, click on the "Parameters" tab then click on the "New Parameters" button, the `Create Part Parameter` form will be displayed: +To add a parameter, navigate to a specific part detail page, click on the "Parameters" tab then click on the "New Parameters" button, the `Create Parameter` form will be displayed: -{{ image("part/create_part_parameter.png", "Create Part Parameter Form") }} +{{ image("part/create_part_parameter.png", "Create Parameter Form") }} Select the parameter `Template` you would like to use for this parameter, fill-out the `Data` field (value of this specific parameter) and click the "Submit" button. @@ -132,7 +132,7 @@ The in-built conversion functionality means that parameter values can be input i ### Incompatible Units -If a part parameter is created with a value which is incompatible with the units specified for the template, it will be rejected: +If a parameter is created with a value which is incompatible with the units specified for the template, it will be rejected: {{ image("part/part_invalid_units.png", "Invalid Parameter Units") }} @@ -151,4 +151,4 @@ Selection Lists can be used to add a large number of predefined values to a para It is possible that plugins lock selection lists to ensure a known state. -Administration of lists can be done through the Part Parameter section in the [Admin Center](../settings/admin.md#admin-center) or via the API. +Administration of lists can be done through the Parameters section in the [Admin Center](../settings/admin.md#admin-center) or via the API. diff --git a/docs/docs/concepts/units.md b/docs/docs/concepts/units.md index ce7101245539..9e1c1a50d7a9 100644 --- a/docs/docs/concepts/units.md +++ b/docs/docs/concepts/units.md @@ -8,7 +8,7 @@ Support for real-world "physical" units of measure is implemented using the [pin - Ensures consistent use of real units for your inventory management - Convert between compatible units of measure from suppliers -- Enforce use of compatible units when creating part parameters +- Enforce use of compatible units when creating parameters - Enable custom units as required ### Unit Conversion @@ -61,7 +61,7 @@ The [supplier part](../part/index.md/#supplier-parts) model uses real-world unit ### Parameter -The [parameter template](../concepts/parameters.md#parameter-templates) model can specify units of measure, and part parameters can be specified against these templates with compatible units +The [parameter template](../concepts/parameters.md#parameter-templates) model can specify units of measure, and parameters can be specified against these templates with compatible units ## Custom Units diff --git a/docs/docs/concepts/user_interface.md b/docs/docs/concepts/user_interface.md index 665b9fcf02f7..b9a171d4c06b 100644 --- a/docs/docs/concepts/user_interface.md +++ b/docs/docs/concepts/user_interface.md @@ -202,7 +202,7 @@ This will display the data in a calendar format: ## Parametric Views -Some [table views](#table-views) can be switched to a parametric view, which provides a visual representation of data based on specific parameters or attributes. The parametric view allows users to easily see and interact with data that is organized by certain characteristics, such as categories, types, or other relevant attributes. +Some [table views](#table-views) can be switched to a parametric view, which provides a visual representation of data based on specific [parameters attributes](./parameters.md). The parametric view allows users to easily see and interact with data that is organized by certain characteristics, such as categories, types, or other relevant attributes. To switch to the "parametric view" (for a table which supports it), click on the "parametric view" button located above and to the right of the table view: diff --git a/docs/docs/part/create.md b/docs/docs/part/create.md index d9b984e0e679..6d5796839061 100644 --- a/docs/docs/part/create.md +++ b/docs/docs/part/create.md @@ -20,7 +20,7 @@ New parts can be created manually by selecting the *Create Part* option from the {{ image("part/part_create_form.png", "New part form") }} -Fill out the required part parameters and then press *Submit* to create the new part. If there are any form errors, you must fix these before the form can be successfully submitted. +Fill out the required part attributes and then press *Submit* to create the new part. If there are any form errors, you must fix these before the form can be successfully submitted. Once the form is completed, the browser window is redirected to the new part detail page. diff --git a/docs/docs/part/index.md b/docs/docs/part/index.md index e4848ece1f44..076da58b2551 100644 --- a/docs/docs/part/index.md +++ b/docs/docs/part/index.md @@ -81,7 +81,7 @@ Parts can be locked to prevent them from being modified. This is useful for part - Locked parts cannot be deleted - BOM items cannot be created, edited, or deleted when they are part of a locked assembly -- Part parameters linked to a locked part cannot be created, edited or deleted +- Parameters linked to a locked part cannot be created, edited or deleted ## Active Parts diff --git a/src/backend/InvenTree/InvenTree/api_version.py b/src/backend/InvenTree/InvenTree/api_version.py index a41015e398b9..d69f6da4e157 100644 --- a/src/backend/InvenTree/InvenTree/api_version.py +++ b/src/backend/InvenTree/InvenTree/api_version.py @@ -1,11 +1,14 @@ """InvenTree API version information.""" # InvenTree API version -INVENTREE_API_VERSION = 460 +INVENTREE_API_VERSION = 461 """Increment this API version number whenever there is a significant change to the API that any clients need to know about.""" INVENTREE_API_TEXT = """ +v461 -> 2026-03-04 : https://github.com/inventree/InvenTree/pull/11459 + - Adds "parameter" support for StockItem API endpoints + v460 -> 2026-02-25 : https://github.com/inventree/InvenTree/pull/11374 - Adds "updated_at" field to PurchaseOrder, SalesOrder and ReturnOrder API endpoints - Adds "updated_before" and "updated_after" date filters to all three order list endpoints diff --git a/src/backend/InvenTree/stock/models.py b/src/backend/InvenTree/stock/models.py index b9f2c298a266..0a536434b4f6 100644 --- a/src/backend/InvenTree/stock/models.py +++ b/src/backend/InvenTree/stock/models.py @@ -400,6 +400,7 @@ class StockItemReportContext(report.mixins.BaseReportContext): class StockItem( InvenTree.models.PluginValidationMixin, + InvenTree.models.InvenTreeParameterMixin, InvenTree.models.InvenTreeAttachmentMixin, InvenTree.models.InvenTreeBarcodeMixin, InvenTree.models.InvenTreeNotesMixin, diff --git a/src/backend/InvenTree/stock/serializers.py b/src/backend/InvenTree/stock/serializers.py index 11ac344e1e49..ee1d3c3fd9dd 100644 --- a/src/backend/InvenTree/stock/serializers.py +++ b/src/backend/InvenTree/stock/serializers.py @@ -378,6 +378,7 @@ class Meta: 'stale', 'tracking_items', 'tags', + 'parameters', # Detail fields (FK relationships) 'supplier_part_detail', 'part_detail', @@ -657,6 +658,8 @@ def annotate_queryset(queryset): tags = common.filters.enable_tags_filter() + parameters = common.filters.enable_parameters_filter() + class SerializeStockItemSerializer(serializers.Serializer): """A DRF serializer for "serializing" a StockItem. diff --git a/src/frontend/src/pages/stock/LocationDetail.tsx b/src/frontend/src/pages/stock/LocationDetail.tsx index 1cdd66d5b579..0ea6b5b804ec 100644 --- a/src/frontend/src/pages/stock/LocationDetail.tsx +++ b/src/frontend/src/pages/stock/LocationDetail.tsx @@ -49,6 +49,7 @@ import { useInstance } from '../../hooks/UseInstance'; import { useStockAdjustActions } from '../../hooks/UseStockAdjustActions'; import { useUserState } from '../../states/UserState'; import { PartListTable } from '../../tables/part/PartTable'; +import ParametricStockItemTable from '../../tables/stock/ParametricStockItemTable'; import { StockItemTable } from '../../tables/stock/StockItemTable'; import StockLocationParametricTable from '../../tables/stock/StockLocationParametricTable'; import { StockLocationTable } from '../../tables/stock/StockLocationTable'; @@ -171,6 +172,7 @@ export default function Stock() { }, [location, instanceQuery]); const [sublocationView, setSublocationView] = useState('table'); + const [stockView, setStockView] = useState('table'); const locationPanels: PanelType[] = useMemo(() => { return [ @@ -206,20 +208,36 @@ export default function Stock() { } ] }), - { - name: 'stock-items', + SegmentedControlPanel({ + name: 'stockitems', label: t`Stock Items`, icon: , - content: ( - - ) - }, + hidden: !user.hasViewPermission(ModelType.stockitem), + selection: stockView, + onChange: setStockView, + options: [ + { + value: 'table', + label: t`Table View`, + icon: , + content: ( + + ) + }, + { + value: 'parametric', + label: t`Parametric View`, + icon: , + content: + } + ] + }), { name: 'default_parts', label: t`Default Parts`, @@ -241,7 +259,7 @@ export default function Stock() { hidden: !location.pk }) ]; - }, [sublocationView, location, id]); + }, [sublocationView, stockView, location, id]); const editLocation = useEditApiFormModal({ url: ApiEndpoints.stock_location_list, diff --git a/src/frontend/src/pages/stock/StockDetail.tsx b/src/frontend/src/pages/stock/StockDetail.tsx index 6b39b8396496..e026a5cd334b 100644 --- a/src/frontend/src/pages/stock/StockDetail.tsx +++ b/src/frontend/src/pages/stock/StockDetail.tsx @@ -61,6 +61,7 @@ import AttachmentPanel from '../../components/panels/AttachmentPanel'; import NotesPanel from '../../components/panels/NotesPanel'; import type { PanelType } from '../../components/panels/Panel'; import { PanelGroup } from '../../components/panels/PanelGroup'; +import ParametersPanel from '../../components/panels/ParametersPanel'; import LocateItemButton from '../../components/plugins/LocateItemButton'; import { StatusRenderer } from '../../components/render/StatusRenderer'; import OrderPartsWizard from '../../components/wizards/OrderPartsWizard'; @@ -617,6 +618,11 @@ export default function StockDetail() { ) }, + ParametersPanel({ + model_type: ModelType.stockitem, + model_id: stockitem.pk, + hidden: !stockitem.pk + }), AttachmentPanel({ model_type: ModelType.stockitem, model_id: stockitem.pk diff --git a/src/frontend/src/tables/stock/ParametricStockItemTable.tsx b/src/frontend/src/tables/stock/ParametricStockItemTable.tsx new file mode 100644 index 000000000000..0c0161d3d1ea --- /dev/null +++ b/src/frontend/src/tables/stock/ParametricStockItemTable.tsx @@ -0,0 +1,59 @@ +import { ApiEndpoints, ModelType } from '@lib/index'; +import type { TableFilter } from '@lib/types/Filters'; +import type { TableColumn } from '@lib/types/Tables'; +import { t } from '@lingui/core/macro'; +import { useMemo } from 'react'; +import { + DescriptionColumn, + IPNColumn, + PartColumn, + StockColumn +} from '../ColumnRenderers'; +import ParametricDataTable from '../general/ParametricDataTable'; + +export default function ParametricStockItemTable({ + locationId +}: Readonly<{ + locationId?: any; +}>) { + const customFilters: TableFilter[] = useMemo(() => { + // Custom filters for the parametric stock item table + return []; + }, []); + + const customColumns: TableColumn[] = useMemo(() => { + return [ + PartColumn({ + part: 'part_detail', + switchable: false + }), + IPNColumn({}), + DescriptionColumn({ + accessor: 'part_detail.description' + }), + StockColumn({ + accessor: '', + title: t`Stock`, + sortable: true, + ordering: 'stock' + }) + ]; + }, []); + + return ( + + ); +} diff --git a/src/frontend/tests/pages/pui_stock.spec.ts b/src/frontend/tests/pages/pui_stock.spec.ts index 7bebed2b0081..1dcf67564b2e 100644 --- a/src/frontend/tests/pages/pui_stock.spec.ts +++ b/src/frontend/tests/pages/pui_stock.spec.ts @@ -5,7 +5,9 @@ import { loadTab, navigate, openFilterDrawer, - setTableChoiceFilter + setTableChoiceFilter, + showParametricView, + showTableView } from '../helpers.js'; import { doCachedLogin } from '../login.js'; @@ -36,6 +38,10 @@ test('Stock - Basic Tests', async ({ browser }) => { await loadTab(page, 'Test Results'); await page.getByText('395c6d5586e5fb656901d047be27e1f7').waitFor(); await loadTab(page, 'Installed Items'); + + await loadTab(page, 'Parameters'); + await loadTab(page, 'Attachments'); + await loadTab(page, 'Notes'); }); test('Stock - Location Tree', async ({ browser }) => { @@ -479,7 +485,15 @@ test('Stock - Location', async ({ browser }) => { await loadTab(page, 'Default Parts'); await loadTab(page, 'Stock Items'); + + await showParametricView(page); + await showTableView(page); + await loadTab(page, 'Sublocations'); + + await showParametricView(page); + await showTableView(page); + await loadTab(page, 'Location Details'); await page.getByLabel('action-menu-barcode-actions').click();