mirror of
https://github.com/inventree/InvenTree.git
synced 2026-07-18 04:33:48 +00:00
[API] Refactor API for duplicating objects (#12294)
* Implement generic serializer for custom model duplication * Apply pattern to BuildOrder * Add more generic options * Bump API version * Adjust default option * Refactor existing implementations * Dynamic class typing * Add duplicate field to more model types - Company - ManufacturerPart - SupplierPart - SalesOrderShipment * Implement parameter duplication for more models: - Company - ManufacturerPart - SupplierPart * Simplify code * Refactor
This commit is contained in:
@@ -36,16 +36,18 @@ import {
|
||||
} from '../hooks/UseGenerator';
|
||||
import { useGlobalSettingsState } from '../states/SettingsStates';
|
||||
import { RenderPartColumn } from '../tables/ColumnRenderers';
|
||||
import { ProjectCodeField, TagsField } from './CommonFields';
|
||||
import { DuplicateField, ProjectCodeField, TagsField } from './CommonFields';
|
||||
|
||||
/**
|
||||
* Field set for BuildOrder forms
|
||||
*/
|
||||
export function useBuildOrderFields({
|
||||
create,
|
||||
duplicateBuildId,
|
||||
modalId
|
||||
}: {
|
||||
create: boolean;
|
||||
duplicateBuildId?: number | null;
|
||||
modalId: string;
|
||||
}): ApiFormFieldSet {
|
||||
const [destination, setDestination] = useState<number | null | undefined>(
|
||||
@@ -133,7 +135,13 @@ export function useBuildOrderFields({
|
||||
is_active: true
|
||||
}
|
||||
},
|
||||
external: {}
|
||||
external: {},
|
||||
duplicate: DuplicateField({
|
||||
originalId: duplicateBuildId,
|
||||
extraFields: {
|
||||
copy_parameters: {}
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
if (!globalSettings.isSet('PROJECT_CODES_ENABLED', true)) {
|
||||
@@ -144,8 +152,19 @@ export function useBuildOrderFields({
|
||||
delete fields.external;
|
||||
}
|
||||
|
||||
if (!duplicateBuildId) {
|
||||
delete fields.duplicate;
|
||||
}
|
||||
|
||||
return fields;
|
||||
}, [create, destination, batchCode, batchGenerator.result, globalSettings]);
|
||||
}, [
|
||||
create,
|
||||
destination,
|
||||
batchCode,
|
||||
batchGenerator.result,
|
||||
globalSettings,
|
||||
duplicateBuildId
|
||||
]);
|
||||
}
|
||||
|
||||
export function useBuildOrderOutputFields({
|
||||
|
||||
@@ -2,6 +2,26 @@ import type { ApiFormFieldType } from '@lib/types/Forms';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { IconList } from '@tabler/icons-react';
|
||||
|
||||
// Generic field for implementing a "duplication options" form field
|
||||
export function DuplicateField({
|
||||
originalId,
|
||||
extraFields
|
||||
}: Readonly<{
|
||||
originalId?: number | null;
|
||||
extraFields?: Record<string, any>;
|
||||
}>): ApiFormFieldType {
|
||||
return {
|
||||
children: {
|
||||
original: {
|
||||
value: originalId,
|
||||
hidden: true
|
||||
},
|
||||
...extraFields
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Generic field for rendering a list of tags within a form
|
||||
export function TagsField({
|
||||
label,
|
||||
description,
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
IconPhone
|
||||
} from '@tabler/icons-react';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { TagsField } from './CommonFields';
|
||||
import { DuplicateField, TagsField } from './CommonFields';
|
||||
|
||||
/**
|
||||
* Field set for SupplierPart instance
|
||||
@@ -21,11 +21,13 @@ import { TagsField } from './CommonFields';
|
||||
export function useSupplierPartFields({
|
||||
manufacturerId,
|
||||
manufacturerPartId,
|
||||
partId
|
||||
partId,
|
||||
duplicateSupplierPartId
|
||||
}: {
|
||||
manufacturerId?: number;
|
||||
manufacturerPartId?: number;
|
||||
partId?: number;
|
||||
duplicateSupplierPartId?: number | null;
|
||||
}) {
|
||||
const [part, setPart] = useState<any>({});
|
||||
|
||||
@@ -95,14 +97,34 @@ export function useSupplierPartFields({
|
||||
icon: <IconPackage />
|
||||
},
|
||||
primary: {},
|
||||
active: {}
|
||||
active: {},
|
||||
duplicate: DuplicateField({
|
||||
originalId: duplicateSupplierPartId,
|
||||
extraFields: {
|
||||
copy_parameters: {}
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
if (!duplicateSupplierPartId) {
|
||||
delete fields.duplicate;
|
||||
}
|
||||
|
||||
return fields;
|
||||
}, [manufacturerId, manufacturerPartId, partId, part]);
|
||||
}, [
|
||||
manufacturerId,
|
||||
manufacturerPartId,
|
||||
partId,
|
||||
part,
|
||||
duplicateSupplierPartId
|
||||
]);
|
||||
}
|
||||
|
||||
export function useManufacturerPartFields() {
|
||||
export function useManufacturerPartFields({
|
||||
duplicateManufacturerPartId
|
||||
}: {
|
||||
duplicateManufacturerPartId?: number | null;
|
||||
} = {}) {
|
||||
return useMemo(() => {
|
||||
const fields: ApiFormFieldSet = {
|
||||
part: {},
|
||||
@@ -120,18 +142,32 @@ export function useManufacturerPartFields() {
|
||||
MPN: {},
|
||||
description: {},
|
||||
tags: TagsField({}),
|
||||
link: {}
|
||||
link: {},
|
||||
duplicate: DuplicateField({
|
||||
originalId: duplicateManufacturerPartId,
|
||||
extraFields: {
|
||||
copy_parameters: {}
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
if (!duplicateManufacturerPartId) {
|
||||
delete fields.duplicate;
|
||||
}
|
||||
|
||||
return fields;
|
||||
}, []);
|
||||
}, [duplicateManufacturerPartId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Field set for editing a company instance
|
||||
*/
|
||||
export function companyFields(): ApiFormFieldSet {
|
||||
return {
|
||||
export function companyFields({
|
||||
duplicateCompanyId
|
||||
}: {
|
||||
duplicateCompanyId?: number | null;
|
||||
} = {}): ApiFormFieldSet {
|
||||
const fields: ApiFormFieldSet = {
|
||||
name: {},
|
||||
description: {},
|
||||
website: {
|
||||
@@ -151,6 +187,18 @@ export function companyFields(): ApiFormFieldSet {
|
||||
is_supplier: {},
|
||||
is_manufacturer: {},
|
||||
is_customer: {},
|
||||
active: {}
|
||||
active: {},
|
||||
duplicate: DuplicateField({
|
||||
originalId: duplicateCompanyId,
|
||||
extraFields: {
|
||||
copy_parameters: {}
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
if (!duplicateCompanyId) {
|
||||
delete fields.duplicate;
|
||||
}
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
@@ -156,7 +156,7 @@ export function usePartFields({
|
||||
fields.duplicate = {
|
||||
icon: <IconCopy />,
|
||||
children: {
|
||||
part: {
|
||||
original: {
|
||||
value: duplicatePartInstance?.pk,
|
||||
hidden: true
|
||||
},
|
||||
|
||||
@@ -316,7 +316,7 @@ export function usePurchaseOrderFields({
|
||||
if (!!duplicateOrderId) {
|
||||
fields.duplicate = {
|
||||
children: {
|
||||
order_id: {
|
||||
original: {
|
||||
hidden: true,
|
||||
value: duplicateOrderId
|
||||
},
|
||||
|
||||
@@ -84,15 +84,10 @@ export function useReturnOrderFields({
|
||||
if (!!duplicateOrderId) {
|
||||
fields.duplicate = {
|
||||
children: {
|
||||
order_id: {
|
||||
original: {
|
||||
hidden: true,
|
||||
value: duplicateOrderId
|
||||
},
|
||||
copy_lines: {
|
||||
// Cannot duplicate lines from a return order!
|
||||
value: false,
|
||||
hidden: true
|
||||
},
|
||||
copy_extra_lines: {},
|
||||
copy_parameters: {}
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ export function useSalesOrderFields({
|
||||
if (!!duplicateOrderId) {
|
||||
fields.duplicate = {
|
||||
children: {
|
||||
order_id: {
|
||||
original: {
|
||||
hidden: true,
|
||||
value: duplicateOrderId
|
||||
},
|
||||
|
||||
@@ -54,13 +54,12 @@ export function useTransferOrderFields({
|
||||
if (!!duplicateOrderId) {
|
||||
fields.duplicate = {
|
||||
children: {
|
||||
order_id: {
|
||||
original: {
|
||||
hidden: true,
|
||||
value: duplicateOrderId
|
||||
},
|
||||
copy_lines: {},
|
||||
// Transfer Orders don't have extra lines for now...
|
||||
copy_extra_lines: { hidden: true, value: false }
|
||||
copy_parameters: {}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -632,6 +632,7 @@ export default function BuildDetail() {
|
||||
|
||||
const duplicateBuildOrderFields = useBuildOrderFields({
|
||||
create: false,
|
||||
duplicateBuildId: build.pk,
|
||||
modalId: 'duplicate-build-order'
|
||||
});
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ import { DetailsImage } from '../../components/details/DetailsImage';
|
||||
import { ItemDetailsGrid } from '../../components/details/ItemDetails';
|
||||
import {
|
||||
DeleteItemAction,
|
||||
DuplicateItemAction,
|
||||
EditItemAction,
|
||||
OptionsActionDropdown
|
||||
} from '../../components/items/ActionDropdown';
|
||||
@@ -43,6 +44,7 @@ import { PanelGroup } from '../../components/panels/PanelGroup';
|
||||
import ParametersPanel from '../../components/panels/ParametersPanel';
|
||||
import { companyFields } from '../../forms/CompanyForms';
|
||||
import {
|
||||
useCreateApiFormModal,
|
||||
useDeleteApiFormModal,
|
||||
useEditApiFormModal
|
||||
} from '../../hooks/UseForm';
|
||||
@@ -293,11 +295,23 @@ export default function CompanyDetail(props: Readonly<CompanyDetailProps>) {
|
||||
url: ApiEndpoints.company_list,
|
||||
pk: company?.pk,
|
||||
title: t`Edit Company`,
|
||||
fields: companyFields(),
|
||||
fields: useMemo(() => companyFields({}), []),
|
||||
queryParams: new URLSearchParams({ tags: 'true' }),
|
||||
onFormSuccess: refreshInstance
|
||||
});
|
||||
|
||||
const duplicateCompany = useCreateApiFormModal({
|
||||
url: ApiEndpoints.company_list,
|
||||
title: t`Duplicate Company`,
|
||||
initialData: useMemo(() => ({ ...company }), [company]),
|
||||
fields: useMemo(
|
||||
() => companyFields({ duplicateCompanyId: company?.pk }),
|
||||
[company]
|
||||
),
|
||||
follow: true,
|
||||
modelType: ModelType.company
|
||||
});
|
||||
|
||||
const deleteCompany = useDeleteApiFormModal({
|
||||
url: ApiEndpoints.company_list,
|
||||
pk: company?.pk,
|
||||
@@ -322,6 +336,10 @@ export default function CompanyDetail(props: Readonly<CompanyDetailProps>) {
|
||||
hidden: !user.hasChangeRole(UserRoles.purchase_order),
|
||||
onClick: () => editCompany.open()
|
||||
}),
|
||||
DuplicateItemAction({
|
||||
hidden: !user.hasAddRole(UserRoles.purchase_order),
|
||||
onClick: () => duplicateCompany.open()
|
||||
}),
|
||||
DeleteItemAction({
|
||||
hidden: !user.hasDeleteRole(UserRoles.purchase_order),
|
||||
onClick: () => deleteCompany.open()
|
||||
@@ -345,6 +363,7 @@ export default function CompanyDetail(props: Readonly<CompanyDetailProps>) {
|
||||
<>
|
||||
{editCompany.modal}
|
||||
{deleteCompany.modal}
|
||||
{duplicateCompany.modal}
|
||||
<InstanceDetail
|
||||
query={instanceQuery}
|
||||
requiredPermission={ModelType.company}
|
||||
|
||||
@@ -220,10 +220,14 @@ export default function ManufacturerPartDetail() {
|
||||
onFormSuccess: refreshInstance
|
||||
});
|
||||
|
||||
const duplicateManufacturerPartFields = useManufacturerPartFields({
|
||||
duplicateManufacturerPartId: manufacturerPart?.pk
|
||||
});
|
||||
|
||||
const duplicateManufacturerPart = useCreateApiFormModal({
|
||||
url: ApiEndpoints.manufacturer_part_list,
|
||||
title: t`Add Manufacturer Part`,
|
||||
fields: editManufacturerPartFields,
|
||||
fields: duplicateManufacturerPartFields,
|
||||
initialData: {
|
||||
...manufacturerPart
|
||||
},
|
||||
|
||||
@@ -357,10 +357,14 @@ export default function SupplierPartDetail() {
|
||||
}
|
||||
});
|
||||
|
||||
const duplicateSupplierPartFields = useSupplierPartFields({
|
||||
duplicateSupplierPartId: supplierPart?.pk
|
||||
});
|
||||
|
||||
const duplicateSupplierPart = useCreateApiFormModal({
|
||||
url: ApiEndpoints.supplier_part_list,
|
||||
title: t`Add Supplier Part`,
|
||||
fields: supplierPartFields,
|
||||
fields: duplicateSupplierPartFields,
|
||||
initialData: {
|
||||
...supplierPart
|
||||
},
|
||||
|
||||
@@ -24,6 +24,7 @@ import { ActionDropdown } from '../../components/items/ActionDropdown';
|
||||
import ImportPartWizard from '../../components/wizards/ImportPartWizard';
|
||||
import OrderPartsWizard from '../../components/wizards/OrderPartsWizard';
|
||||
import { formatDecimal, formatPriceRange } from '../../defaults/formatters';
|
||||
import { DuplicateField } from '../../forms/CommonFields';
|
||||
import { dataImporterSessionFields } from '../../forms/ImporterForms';
|
||||
import { usePartFields } from '../../forms/PartForms';
|
||||
import { InvenTreeIcon } from '../../functions/icons';
|
||||
@@ -312,12 +313,9 @@ export function PartListTable({
|
||||
const duplicatePartFields: ApiFormFieldSet = useMemo(() => {
|
||||
return {
|
||||
...createPartFields,
|
||||
duplicate: {
|
||||
children: {
|
||||
part: {
|
||||
value: selectedPart.pk,
|
||||
hidden: true
|
||||
},
|
||||
duplicate: DuplicateField({
|
||||
originalId: selectedPart.pk,
|
||||
extraFields: {
|
||||
copy_image: {
|
||||
value: true
|
||||
},
|
||||
@@ -337,7 +335,7 @@ export function PartListTable({
|
||||
hidden: !selectedPart.testable
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
}, [createPartFields, globalSettings, selectedPart]);
|
||||
|
||||
|
||||
@@ -133,10 +133,14 @@ export function ManufacturerPartTable({
|
||||
table: table
|
||||
});
|
||||
|
||||
const duplicateManufacturerPartFields = useManufacturerPartFields({
|
||||
duplicateManufacturerPartId: selectedPart?.pk
|
||||
});
|
||||
|
||||
const duplicateManufacturerPart = useCreateApiFormModal({
|
||||
url: ApiEndpoints.manufacturer_part_list,
|
||||
title: t`Add Manufacturer Part`,
|
||||
fields: useMemo(() => manufacturerPartFields, [manufacturerPartFields]),
|
||||
fields: duplicateManufacturerPartFields,
|
||||
table: table,
|
||||
initialData: {
|
||||
...selectedPart
|
||||
|
||||
@@ -292,10 +292,14 @@ export function SupplierPartTable({
|
||||
}
|
||||
});
|
||||
|
||||
const duplicateSupplierPartFields = useSupplierPartFields({
|
||||
duplicateSupplierPartId: selectedSupplierPart?.pk
|
||||
});
|
||||
|
||||
const duplicateSupplierPart = useCreateApiFormModal({
|
||||
url: ApiEndpoints.supplier_part_list,
|
||||
title: t`Add Supplier Part`,
|
||||
fields: useMemo(() => editSupplierPartFields, [editSupplierPartFields]),
|
||||
fields: duplicateSupplierPartFields,
|
||||
initialData: {
|
||||
...selectedSupplierPart,
|
||||
primary: false,
|
||||
|
||||
Reference in New Issue
Block a user