2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-12-16 09:18:10 +00:00

Remove code for ManufacturerPartParameter

This commit is contained in:
Oliver Walters
2025-11-24 11:28:00 +00:00
parent 9bcdc7ddcd
commit 9ceaad0848
9 changed files with 108 additions and 265 deletions

View File

@@ -2,6 +2,53 @@
from django.db import migrations
def convert_to_numeric_value(value: str, units: str):
"""Convert a value (with units) to a numeric value.
Defaults to zero if the value cannot be converted.
"""
import InvenTree.conversion
# Default value is null
result = None
if units:
try:
result = InvenTree.conversion.convert_physical_value(value, units)
result = float(result.magnitude)
except Exception:
pass
else:
try:
result = float(value)
except Exception:
pass
return result
def remove_existing_parameters(apps, schema_editor):
"""Remove any existing Parameter or ParameterTemplate objects from the database."""
Parameter = apps.get_model("common", "Parameter")
ParameterTemplate = apps.get_model("common", "ParameterTemplate")
n_params = Parameter.objects.count()
n_templates = ParameterTemplate.objects.count()
Parameter.objects.all().delete()
ParameterTemplate.objects.all().delete()
if n_params > 0:
print(f"Removed {n_params} existing Parameter instances.")
if n_templates > 0:
print(f"Removed {n_templates} existing ParameterTemplate instances.")
assert Parameter.objects.count() == 0
assert ParameterTemplate.objects.count() == 0
def copy_part_parameters(apps, schema_editor):
"""Forward migration: copy from PartParameterTemplate to ParameterTemplate."""
@@ -29,14 +76,14 @@ def copy_part_parameters(apps, schema_editor):
ParameterTemplate.objects.bulk_create(templates)
print(f"\nMigrated {len(templates)} PartParameterTemplate instances.")
assert ParameterTemplate.objects.filter(model_type='part').count() == len(templates)
assert ParameterTemplate.objects.filter().count() == len(templates)
# Next, copy PartParameter instances to Parameter instances
parameters = []
for parameter in PartParameter.objects.all():
# Find the corresponding ParameterTemplate
template = ParameterTemplate.objects.get(name=parameter.template.name, model_type='part')
template = ParameterTemplate.objects.get(name=parameter.template.name)
parameters.append(Parameter(
template=template,
@@ -56,6 +103,44 @@ def copy_part_parameters(apps, schema_editor):
assert Parameter.objects.filter(model_type='part').count() == len(parameters)
def copy_manufacturer_part_parameters(apps, schema_editor):
"""Copy ManufacturerPartParameter to Parameter."""
ManufacturerPartParameter = apps.get_model("company", "ManufacturerPartParameter")
Parameter = apps.get_model("common", "Parameter")
ParameterTemplate = apps.get_model("common", "ParameterTemplate")
parameters = []
for parameter in ManufacturerPartParameter.objects.all():
# Find the corresponding ParameterTemplate
template = ParameterTemplate.objects.filter(name=parameter.template.name).first()
if not template:
# A matching template does not exist - let's create one
template = ParameterTemplate.objects.create(
name=parameter.name,
description='',
units=parameter.units,
checkbox=False
)
parameters.append(Parameter(
template=template,
model_type='manufacturerpart',
model_id=parameter.manufacturer_part.id,
data=parameter.value,
data_numeric=convert_to_numeric_value(parameter.value),
note=parameter.note
))
if len(parameters) > 0:
Parameter.objects.bulk_create(parameters)
print(f"\nMigrated {len(parameters)} ManufacturerPartParameter instances.")
assert Parameter.objects.filter(model_type='manufacturerpart').count() == len(parameters)
class Migration(migrations.Migration):
dependencies = [
@@ -64,10 +149,17 @@ class Migration(migrations.Migration):
]
operations = [
migrations.RunPython(
remove_existing_parameters,
reverse_code=migrations.RunPython.noop
),
migrations.RunPython(
copy_part_parameters,
reverse_code=migrations.RunPython.noop
),
# TODO: Data migration for existing ManufacturerPartParameter objects
migrations.RunPython(
copy_manufacturer_part_parameters,
reverse_code=migrations.RunPython.noop
)
# TODO: Data migration for existing CategoryParameter objects
]

View File

@@ -740,6 +740,7 @@ class ParameterTemplateSerializer(
)
@register_importer()
class ParameterSerializer(
FilterableSerializerMixin, DataImportExportSerializerMixin, InvenTreeModelSerializer
):

View File

@@ -9,7 +9,6 @@ from .models import (
Company,
Contact,
ManufacturerPart,
ManufacturerPartParameter,
SupplierPart,
SupplierPriceBreak,
)
@@ -56,17 +55,6 @@ class ManufacturerPartAdmin(admin.ModelAdmin):
autocomplete_fields = ('part', 'manufacturer')
@admin.register(ManufacturerPartParameter)
class ManufacturerPartParameterAdmin(admin.ModelAdmin):
"""Admin class for ManufacturerPartParameter model."""
list_display = ('manufacturer_part', 'name', 'value')
search_fields = ['manufacturer_part__manufacturer__name', 'name', 'value']
autocomplete_fields = ('manufacturer_part',)
@admin.register(SupplierPriceBreak)
class SupplierPriceBreakAdmin(admin.ModelAdmin):
"""Admin class for the SupplierPriceBreak model."""

View File

@@ -24,7 +24,6 @@ from .models import (
Company,
Contact,
ManufacturerPart,
ManufacturerPartParameter,
SupplierPart,
SupplierPriceBreak,
)
@@ -32,7 +31,6 @@ from .serializers import (
AddressSerializer,
CompanySerializer,
ContactSerializer,
ManufacturerPartParameterSerializer,
ManufacturerPartSerializer,
SupplierPartSerializer,
SupplierPriceBreakSerializer,
@@ -217,56 +215,6 @@ class ManufacturerPartDetail(RetrieveUpdateDestroyAPI):
serializer_class = ManufacturerPartSerializer
class ManufacturerPartParameterFilter(FilterSet):
"""Custom filterset for the ManufacturerPartParameterList API endpoint."""
class Meta:
"""Metaclass options."""
model = ManufacturerPartParameter
fields = ['name', 'value', 'units', 'manufacturer_part']
manufacturer = rest_filters.ModelChoiceFilter(
queryset=Company.objects.all(), field_name='manufacturer_part__manufacturer'
)
part = rest_filters.ModelChoiceFilter(
queryset=part.models.Part.objects.all(), field_name='manufacturer_part__part'
)
class ManufacturerPartParameterOptions(OutputConfiguration):
"""Available output options for the ManufacturerPartParameter endpoints."""
OPTIONS = [
InvenTreeOutputOption(
description='Include detailed information about the linked ManufacturerPart in the response',
flag='manufacturer_part_detail',
default=False,
)
]
class ManufacturerPartParameterList(
SerializerContextMixin, ListCreateDestroyAPIView, OutputOptionsMixin
):
"""API endpoint for list view of ManufacturerPartParamater model."""
queryset = ManufacturerPartParameter.objects.all()
serializer_class = ManufacturerPartParameterSerializer
filterset_class = ManufacturerPartParameterFilter
output_options = ManufacturerPartParameterOptions
filter_backends = SEARCH_ORDER_FILTER
search_fields = ['name', 'value', 'units']
class ManufacturerPartParameterDetail(RetrieveUpdateDestroyAPI):
"""API endpoint for detail view of ManufacturerPartParameter model."""
queryset = ManufacturerPartParameter.objects.all()
serializer_class = ManufacturerPartParameterSerializer
class SupplierPartFilter(FilterSet):
"""API filters for the SupplierPartList endpoint."""
@@ -518,22 +466,6 @@ class SupplierPriceBreakDetail(SupplierPriceBreakMixin, RetrieveUpdateDestroyAPI
manufacturer_part_api_urls = [
path(
'parameter/',
include([
path(
'<int:pk>/',
ManufacturerPartParameterDetail.as_view(),
name='api-manufacturer-part-parameter-detail',
),
# Catch anything else
path(
'',
ManufacturerPartParameterList.as_view(),
name='api-manufacturer-part-parameter-list',
),
]),
),
path(
'<int:pk>/',
include([

View File

@@ -473,6 +473,7 @@ class Address(InvenTree.models.InvenTreeModel):
class ManufacturerPart(
InvenTree.models.InvenTreeAttachmentMixin,
InvenTree.models.InvenTreeParameterMixin,
InvenTree.models.InvenTreeBarcodeMixin,
InvenTree.models.InvenTreeNotesMixin,
InvenTree.models.InvenTreeMetadataModel,
@@ -652,6 +653,7 @@ class SupplierPartManager(models.Manager):
class SupplierPart(
InvenTree.models.InvenTreeAttachmentMixin,
InvenTree.models.InvenTreeParameterMixin,
InvenTree.models.MetadataMixin,
InvenTree.models.InvenTreeBarcodeMixin,
InvenTree.models.InvenTreeNotesMixin,

View File

@@ -36,7 +36,6 @@ from .models import (
Company,
Contact,
ManufacturerPart,
ManufacturerPartParameter,
SupplierPart,
SupplierPriceBreak,
)
@@ -302,33 +301,6 @@ class ManufacturerPartSerializer(
)
@register_importer()
class ManufacturerPartParameterSerializer(
FilterableSerializerMixin, DataImportExportSerializerMixin, InvenTreeModelSerializer
):
"""Serializer for the ManufacturerPartParameter model."""
class Meta:
"""Metaclass options."""
model = ManufacturerPartParameter
fields = [
'pk',
'manufacturer_part',
'manufacturer_part_detail',
'name',
'value',
'units',
]
manufacturer_part_detail = enable_filter(
ManufacturerPartSerializer(
source='manufacturer_part', many=False, read_only=True, allow_null=True
)
)
class SupplierPriceBreakBriefSerializer(
FilterableSerializerMixin, InvenTreeModelSerializer
):

View File

@@ -3,7 +3,6 @@ import { Grid, Skeleton, Stack } from '@mantine/core';
import {
IconBuildingWarehouse,
IconInfoCircle,
IconList,
IconPackages
} from '@tabler/icons-react';
import { useMemo } from 'react';
@@ -33,6 +32,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 { useManufacturerPartFields } from '../../forms/CompanyForms';
import {
useCreateApiFormModal,
@@ -41,7 +41,6 @@ import {
} from '../../hooks/UseForm';
import { useInstance } from '../../hooks/UseInstance';
import { useUserState } from '../../states/UserState';
import ManufacturerPartParameterTable from '../../tables/purchasing/ManufacturerPartParameterTable';
import { SupplierPartTable } from '../../tables/purchasing/SupplierPartTable';
import { StockItemTable } from '../../tables/stock/StockItemTable';
@@ -161,18 +160,10 @@ export default function ManufacturerPartDetail() {
icon: <IconInfoCircle />,
content: detailsPanel
},
{
name: 'parameters',
label: t`Parameters`,
icon: <IconList />,
content: manufacturerPart?.pk ? (
<ManufacturerPartParameterTable
params={{ manufacturer_part: manufacturerPart.pk }}
/>
) : (
<Skeleton />
)
},
ParametersPanel({
model_type: ModelType.manufacturerpart,
model_id: manufacturerPart?.pk
}),
{
name: 'stock',
label: t`Received Stock`,

View File

@@ -36,6 +36,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 { useSupplierPartFields } from '../../forms/CompanyForms';
import {
useCreateApiFormModal,
@@ -247,6 +248,10 @@ export default function SupplierPartDetail() {
icon: <IconInfoCircle />,
content: detailsPanel
},
ParametersPanel({
model_type: ModelType.supplierpart,
model_id: supplierPart?.pk
}),
{
name: 'stock',
label: t`Received Stock`,

View File

@@ -1,140 +0,0 @@
import { t } from '@lingui/core/macro';
import { useCallback, useMemo, useState } from 'react';
import { AddItemButton } from '@lib/components/AddItemButton';
import {
type RowAction,
RowDeleteAction,
RowEditAction
} from '@lib/components/RowActions';
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
import { UserRoles } from '@lib/enums/Roles';
import { apiUrl } from '@lib/functions/Api';
import type { TableColumn } from '@lib/types/Tables';
import { useManufacturerPartParameterFields } from '../../forms/CompanyForms';
import {
useCreateApiFormModal,
useDeleteApiFormModal,
useEditApiFormModal
} from '../../hooks/UseForm';
import { useTable } from '../../hooks/UseTable';
import { useUserState } from '../../states/UserState';
import { InvenTreeTable } from '../InvenTreeTable';
export default function ManufacturerPartParameterTable({
params
}: Readonly<{
params: any;
}>) {
const table = useTable('manufacturer-part-parameter');
const user = useUserState();
const tableColumns: TableColumn[] = useMemo(() => {
return [
{
accessor: 'name',
title: t`Name`,
sortable: true,
switchable: false
},
{
accessor: 'value',
title: t`Value`,
sortable: true,
switchable: false
},
{
accessor: 'units',
title: t`Units`,
sortable: false,
switchable: true
}
];
}, []);
const fields = useManufacturerPartParameterFields();
const [selectedParameter, setSelectedParameter] = useState<
number | undefined
>(undefined);
const createParameter = useCreateApiFormModal({
url: ApiEndpoints.manufacturer_part_parameter_list,
title: t`Add Parameter`,
fields: fields,
table: table,
initialData: {
manufacturer_part: params.manufacturer_part
}
});
const editParameter = useEditApiFormModal({
url: ApiEndpoints.manufacturer_part_parameter_list,
pk: selectedParameter,
title: t`Edit Parameter`,
fields: fields,
table: table
});
const deleteParameter = useDeleteApiFormModal({
url: ApiEndpoints.manufacturer_part_parameter_list,
pk: selectedParameter,
title: t`Delete Parameter`,
table: table
});
const rowActions = useCallback(
(record: any): RowAction[] => {
return [
RowEditAction({
hidden: !user.hasChangeRole(UserRoles.purchase_order),
onClick: () => {
setSelectedParameter(record.pk);
editParameter.open();
}
}),
RowDeleteAction({
hidden: !user.hasDeleteRole(UserRoles.purchase_order),
onClick: () => {
setSelectedParameter(record.pk);
deleteParameter.open();
}
})
];
},
[user]
);
const tableActions = useMemo(() => {
return [
<AddItemButton
key='add-parameter'
tooltip={t`Add Parameter`}
onClick={() => {
createParameter.open();
}}
hidden={!user.hasAddRole(UserRoles.purchase_order)}
/>
];
}, [user]);
return (
<>
{createParameter.modal}
{editParameter.modal}
{deleteParameter.modal}
<InvenTreeTable
url={apiUrl(ApiEndpoints.manufacturer_part_parameter_list)}
tableState={table}
columns={tableColumns}
props={{
params: {
...params
},
rowActions: rowActions,
tableActions: tableActions
}}
/>
</>
);
}