2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-12-16 17:28:11 +00:00

Merge branch 'generic-parameters' of https://github.com/schrodingersgat/inventree into pr/SchrodingersGat/10699

This commit is contained in:
Matthias Mair
2025-11-26 03:03:18 +01:00
22 changed files with 243 additions and 101 deletions

View File

@@ -603,8 +603,6 @@ class BulkUpdateMixin(BulkOperationMixin):
class ParameterListMixin:
"""Mixin class which supports filtering against parametric fields."""
parameter_model_class = None
def filter_queryset(self, queryset):
"""Perform filtering against parametric fields."""
import common.filters
@@ -616,11 +614,15 @@ class ParameterListMixin:
queryset, self.request.query_params
)
serializer_class = (
getattr(self, 'serializer_class', None) or self.get_serializer_class()
)
model_class = serializer_class.Meta.model
# Apply ordering based on query parameter
queryset = common.filters.order_by_parameter(
queryset,
self.parameter_model_class,
self.request.query_params.get('ordering', None),
queryset, model_class, self.request.query_params.get('ordering', None)
)
return queryset

View File

@@ -349,7 +349,6 @@ class BuildList(
- POST: Create a new Build object
"""
parameter_model_class = Build
output_options = BuildListOutputOptions
filterset_class = BuildFilter
filter_backends = SEARCH_ORDER_FILTER_ALIAS

View File

@@ -2442,7 +2442,7 @@ class ParameterTemplate(
choice_set.add(choice)
def validate_unique(self, exclude=None):
"""Ensure that PartParameterTemplates cannot be created with the same name.
"""Ensure that ParameterTemplates cannot be created with the same name.
This test should be case-insensitive (which the unique caveat does not cover).
"""

View File

@@ -60,7 +60,6 @@ class CompanyList(CompanyMixin, ParameterListMixin, DataExportViewMixin, ListCre
- POST: Create a new Company object
"""
parameter_model_class = Company
filter_backends = SEARCH_ORDER_FILTER
filterset_fields = [
@@ -201,7 +200,6 @@ class ManufacturerPartList(
- POST: Create a new ManufacturerPart object
"""
parameter_model_class = ManufacturerPart
filterset_class = ManufacturerPartFilter
filter_backends = SEARCH_ORDER_FILTER
output_options = ManufacturerOutputOptions
@@ -352,7 +350,6 @@ class SupplierPartList(
- POST: Create a new SupplierPart object
"""
parameter_model_class = SupplierPart
filterset_class = SupplierPartFilter
filter_backends = SEARCH_ORDER_FILTER_ALIAS
output_options = SupplierPartOutputOptions

View File

@@ -392,7 +392,6 @@ class PurchaseOrderList(
- POST: Create a new PurchaseOrder object
"""
parameter_model_class = models.PurchaseOrder
filterset_class = PurchaseOrderFilter
filter_backends = SEARCH_ORDER_FILTER_ALIAS
output_options = PurchaseOrderOutputOptions
@@ -863,7 +862,6 @@ class SalesOrderList(
- POST: Create a new SalesOrder
"""
parameter_model_class = models.SalesOrder
filterset_class = SalesOrderFilter
filter_backends = SEARCH_ORDER_FILTER_ALIAS
output_options = SalesOrderOutputOptions
@@ -1537,7 +1535,6 @@ class ReturnOrderList(
):
"""API endpoint for accessing a list of ReturnOrder objects."""
parameter_model_class = models.ReturnOrder
filterset_class = ReturnOrderFilter
filter_backends = SEARCH_ORDER_FILTER_ALIAS

View File

@@ -1076,7 +1076,6 @@ class PartList(
):
"""API endpoint for accessing a list of Part objects, or creating a new Part instance."""
parameter_model_class = Part
output_options = PartOutputOptions
filterset_class = PartFilter
is_create = True

View File

@@ -40,7 +40,7 @@ RULESET_NAMES = [choice[0] for choice in RULESET_CHOICES]
# Permission types available for each ruleset.
RULESET_PERMISSIONS = ['view', 'add', 'change', 'delete']
RULESET_CHANGE_INHERIT = [('part', 'partparameter'), ('part', 'bomitem')]
RULESET_CHANGE_INHERIT = [('part', 'bomitem')]
def get_ruleset_models() -> dict:

View File

@@ -1482,9 +1482,9 @@ pynacl==1.6.0 \
--hash=sha256:f46386c24a65383a9081d68e9c2de909b1834ec74ff3013271f1bca9c2d233eb \
--hash=sha256:f4b3824920e206b4f52abd7de621ea7a44fd3cb5c8daceb7c3612345dfc54f2e
# via paramiko
pypdf==6.3.0 \
--hash=sha256:2d5f9741e851e378908692d571374b3cbd94582fdd1c740fcf7c029ec35ac0e6 \
--hash=sha256:d066a2fdf8195e1811ae5a9d5a2f97f5bed0e1e7954297295eadee6357e76c5d
pypdf==6.4.0 \
--hash=sha256:4769d471f8ddc3341193ecc5d6560fa44cf8cd0abfabf21af4e195cc0c224072 \
--hash=sha256:55ab9837ed97fd7fcc5c131d52fcc2223bc5c6b8a1488bbf7c0e27f1f0023a79
# via -r src/backend/requirements.in
pyphen==0.17.2 \
--hash=sha256:3a07fb017cb2341e1d9ff31b8634efb1ae4dc4b130468c7c39dd3d32e7c3affd \

View File

@@ -1,8 +1,10 @@
import { t } from '@lingui/core/macro';
import { Table } from '@mantine/core';
import { Alert, Table, Text } from '@mantine/core';
import {
IconAddressBook,
IconCalendar,
IconCircleCheck,
IconCircleX,
IconCoins,
IconUser,
IconUsers
@@ -23,8 +25,9 @@ import type {
ApiFormFieldType
} from '@lib/types/Forms';
import type { TableFieldRowProps } from '../components/forms/fields/TableField';
import { useCreateApiFormModal } from '../hooks/UseForm';
import { useCreateApiFormModal, useEditApiFormModal } from '../hooks/UseForm';
import { useGlobalSettingsState } from '../states/SettingsStates';
import { useUserState } from '../states/UserState';
import { RenderPartColumn } from '../tables/ColumnRenderers';
export function useSalesOrderFields({
@@ -190,6 +193,64 @@ export function useSalesOrderLineItemFields({
}, [salePrice, partCurrency, orderId, create]);
}
export function useCheckShipmentForm({
shipmentId,
onSuccess
}: {
shipmentId: number;
onSuccess: (response: any) => void;
}) {
const user = useUserState();
return useEditApiFormModal({
url: ApiEndpoints.sales_order_shipment_list,
pk: shipmentId,
title: t`Check Shipment`,
preFormContent: (
<Alert color='green' icon={<IconCircleCheck />} title={t`Check Shipment`}>
<Text>{t`Marking the shipment as checked indicates that you have verified that all items included in this shipment are correct`}</Text>
</Alert>
),
fetchInitialData: false,
fields: {
checked_by: {
hidden: true,
value: user.getUser()?.pk
}
},
successMessage: t`Shipment marked as checked`,
onFormSuccess: onSuccess
});
}
export function useUncheckShipmentForm({
shipmentId,
onSuccess
}: {
shipmentId: number;
onSuccess: (response: any) => void;
}) {
return useEditApiFormModal({
url: ApiEndpoints.sales_order_shipment_list,
pk: shipmentId,
title: t`Uncheck Shipment`,
preFormContent: (
<Alert color='red' icon={<IconCircleX />} title={t`Uncheck Shipment`}>
<Text>{t`Marking the shipment as unchecked indicates that the shipment requires further verification`}</Text>
</Alert>
),
fetchInitialData: false,
fields: {
checked_by: {
hidden: true,
value: null
}
},
successMessage: t`Shipment marked as unchecked`,
onFormSuccess: onSuccess
});
}
function SalesOrderAllocateLineRow({
props,
record,

View File

@@ -1,6 +1,11 @@
import { t } from '@lingui/core/macro';
import { Stack } from '@mantine/core';
import { IconCalendar, IconTable, IconTools } from '@tabler/icons-react';
import {
IconCalendar,
IconListDetails,
IconTable,
IconTools
} from '@tabler/icons-react';
import { useMemo } from 'react';
import { ModelType } from '@lib/enums/ModelType';
@@ -16,6 +21,7 @@ import SegmentedControlPanel from '../../components/panels/SegmentedControlPanel
import { useGlobalSettingsState } from '../../states/SettingsStates';
import { useUserState } from '../../states/UserState';
import { PartCategoryFilter } from '../../tables/Filter';
import BuildOrderParametricTable from '../../tables/build/BuildOrderParametricTable';
import { BuildOrderTable } from '../../tables/build/BuildOrderTable';
function BuildOrderCalendar() {
@@ -74,6 +80,12 @@ export default function BuildIndex() {
label: t`Calendar View`,
icon: <IconCalendar />,
content: <BuildOrderCalendar />
},
{
value: 'parametric',
label: t`Parametric View`,
icon: <IconListDetails />,
content: <BuildOrderParametricTable />
}
]
})

View File

@@ -1,5 +1,5 @@
import { t } from '@lingui/core/macro';
import { Alert, Grid, Skeleton, Stack, Text } from '@mantine/core';
import { Grid, Skeleton, Stack, Text } from '@mantine/core';
import {
IconBookmark,
IconCircleCheck,
@@ -39,8 +39,10 @@ import { RenderAddress } from '../../components/render/Company';
import { RenderUser } from '../../components/render/User';
import { formatDate } from '../../defaults/formatters';
import {
useCheckShipmentForm,
useSalesOrderShipmentCompleteFields,
useSalesOrderShipmentFields
useSalesOrderShipmentFields,
useUncheckShipmentForm
} from '../../forms/SalesOrderForms';
import {
useCreateApiFormModal,
@@ -320,44 +322,14 @@ export default function SalesOrderShipmentDetail() {
onFormSuccess: refreshShipment
});
const checkShipment = useEditApiFormModal({
url: ApiEndpoints.sales_order_shipment_list,
pk: shipment.pk,
title: t`Check Shipment`,
preFormContent: (
<Alert color='green' icon={<IconCircleCheck />} title={t`Check Shipment`}>
<Text>{t`Marking the shipment as checked indicates that you have verified that all items included in this shipment are correct`}</Text>
</Alert>
),
fetchInitialData: false,
fields: {
checked_by: {
hidden: true,
value: userId
}
},
successMessage: t`Shipment marked as checked`,
onFormSuccess: refreshShipment
const checkShipment = useCheckShipmentForm({
shipmentId: shipment.pk,
onSuccess: refreshShipment
});
const uncheckShipment = useEditApiFormModal({
url: ApiEndpoints.sales_order_shipment_list,
pk: shipment.pk,
title: t`Uncheck Shipment`,
preFormContent: (
<Alert color='red' icon={<IconCircleX />} title={t`Uncheck Shipment`}>
<Text>{t`Marking the shipment as unchecked indicates that the shipment requires further verification`}</Text>
</Alert>
),
fetchInitialData: false,
fields: {
checked_by: {
hidden: true,
value: null
}
},
successMessage: t`Shipment marked as unchecked`,
onFormSuccess: refreshShipment
const uncheckShipment = useUncheckShipmentForm({
shipmentId: shipment.pk,
onSuccess: refreshShipment
});
const shipmentBadges = useMemo(() => {

View File

@@ -0,0 +1,40 @@
import { ApiEndpoints, ModelType } from '@lib/index';
import type { TableFilter } from '@lib/types/Filters';
import type { TableColumn } from '@lib/types/Tables';
import { type ReactNode, useMemo } from 'react';
import { DescriptionColumn, ReferenceColumn } from '../ColumnRenderers';
import { OrderStatusFilter, OutstandingFilter } from '../Filter';
import ParametricDataTable from '../general/ParametricDataTable';
export default function BuildOrderParametricTable({
queryParams
}: {
queryParams?: Record<string, any>;
}): ReactNode {
const customColumns: TableColumn[] = useMemo(() => {
return [
ReferenceColumn({
switchable: false
}),
DescriptionColumn({
accessor: 'title'
})
];
}, []);
const customFilters: TableFilter[] = useMemo(() => {
return [OutstandingFilter(), OrderStatusFilter({ model: ModelType.build })];
}, []);
return (
<ParametricDataTable
modelType={ModelType.build}
endpoint={ApiEndpoints.build_order_list}
customColumns={customColumns}
customFilters={customFilters}
queryParams={{
...queryParams
}}
/>
);
}

View File

@@ -4,7 +4,11 @@ import type { TableFilter } from '@lib/types/Filters';
import type { TableColumn } from '@lib/types/Tables';
import { t } from '@lingui/core/macro';
import { type ReactNode, useMemo } from 'react';
import { CompanyColumn, ReferenceColumn } from '../ColumnRenderers';
import {
CompanyColumn,
DescriptionColumn,
ReferenceColumn
} from '../ColumnRenderers';
import {
AssignedToMeFilter,
OrderStatusFilter,
@@ -22,7 +26,9 @@ export default function PurchaseOrderParametricTable({
}): ReactNode {
const customColumns: TableColumn[] = useMemo(() => {
return [
ReferenceColumn({}),
ReferenceColumn({
switchable: false
}),
{
accessor: 'supplier__name',
title: t`Supplier`,
@@ -30,7 +36,8 @@ export default function PurchaseOrderParametricTable({
render: (record: any) => (
<CompanyColumn company={record.supplier_detail} />
)
}
},
DescriptionColumn({})
];
}, []);

View File

@@ -4,7 +4,11 @@ import type { TableFilter } from '@lib/types/Filters';
import type { TableColumn } from '@lib/types/Tables';
import { t } from '@lingui/core/macro';
import { type ReactNode, useMemo } from 'react';
import { CompanyColumn, ReferenceColumn } from '../ColumnRenderers';
import {
CompanyColumn,
DescriptionColumn,
ReferenceColumn
} from '../ColumnRenderers';
import {
AssignedToMeFilter,
OrderStatusFilter,
@@ -22,7 +26,7 @@ export default function ReturnOrderParametricTable({
}): ReactNode {
const customColumns: TableColumn[] = useMemo(() => {
return [
ReferenceColumn({}),
ReferenceColumn({ switchable: false }),
{
accessor: 'customer__name',
title: t`Customer`,
@@ -30,7 +34,8 @@ export default function ReturnOrderParametricTable({
render: (record: any) => (
<CompanyColumn company={record.customer_detail} />
)
}
},
DescriptionColumn({})
];
}, []);

View File

@@ -4,7 +4,11 @@ import type { TableFilter } from '@lib/types/Filters';
import type { TableColumn } from '@lib/types/Tables';
import { t } from '@lingui/core/macro';
import { type ReactNode, useMemo } from 'react';
import { CompanyColumn, ReferenceColumn } from '../ColumnRenderers';
import {
CompanyColumn,
DescriptionColumn,
ReferenceColumn
} from '../ColumnRenderers';
import {
AssignedToMeFilter,
OrderStatusFilter,
@@ -22,7 +26,7 @@ export default function SalesOrderParametricTable({
}): ReactNode {
const customColumns: TableColumn[] = useMemo(() => {
return [
ReferenceColumn({}),
ReferenceColumn({ switchable: false }),
{
accessor: 'customer__name',
title: t`Customer`,
@@ -30,7 +34,8 @@ export default function SalesOrderParametricTable({
render: (record: any) => (
<CompanyColumn company={record.customer_detail} />
)
}
},
DescriptionColumn({})
];
}, []);

View File

@@ -1,5 +1,9 @@
import { t } from '@lingui/core/macro';
import { IconTruckDelivery } from '@tabler/icons-react';
import {
IconCircleCheck,
IconCircleX,
IconTruckDelivery
} from '@tabler/icons-react';
import { useCallback, useMemo, useState } from 'react';
import { useNavigate } from 'react-router-dom';
@@ -19,8 +23,10 @@ import type { TableFilter } from '@lib/types/Filters';
import type { TableColumn } from '@lib/types/Tables';
import dayjs from 'dayjs';
import {
useCheckShipmentForm,
useSalesOrderShipmentCompleteFields,
useSalesOrderShipmentFields
useSalesOrderShipmentFields,
useUncheckShipmentForm
} from '../../forms/SalesOrderForms';
import {
useCreateApiFormModal,
@@ -91,6 +97,20 @@ export default function SalesOrderShipmentTable({
table: table
});
const checkShipment = useCheckShipmentForm({
shipmentId: selectedShipment.pk,
onSuccess: () => {
table.refreshTable();
}
});
const uncheckShipment = useUncheckShipmentForm({
shipmentId: selectedShipment.pk,
onSuccess: () => {
table.refreshTable();
}
});
const completeShipment = useCreateApiFormModal({
url: ApiEndpoints.sales_order_shipment_complete,
pk: selectedShipment.pk,
@@ -188,6 +208,30 @@ export default function SalesOrderShipmentTable({
const shipped: boolean = !!record.shipment_date;
return [
{
hidden:
!!record.checked_by || !user.hasChangeRole(UserRoles.sales_order),
title: t`Check Shipment`,
color: 'blue',
icon: <IconCircleCheck />,
onClick: () => {
setSelectedShipment(record);
checkShipment.open();
}
},
{
hidden:
shipped ||
!record.checked_by ||
!user.hasChangeRole(UserRoles.sales_order),
title: t`Uncheck Shipment`,
color: 'red',
icon: <IconCircleX />,
onClick: () => {
setSelectedShipment(record);
uncheckShipment.open();
}
},
{
hidden: shipped || !user.hasChangeRole(UserRoles.sales_order),
title: t`Complete Shipment`,
@@ -271,7 +315,9 @@ export default function SalesOrderShipmentTable({
<>
{newShipment.modal}
{editShipment.modal}
{checkShipment.modal}
{deleteShipment.modal}
{uncheckShipment.modal}
{completeShipment.modal}
<InvenTreeTable
url={apiUrl(ApiEndpoints.sales_order_shipment_list)}