mirror of
https://github.com/inventree/InvenTree.git
synced 2026-09-01 09:41:22 +00:00
Implement duplicate action for stock item
This commit is contained in:
@@ -1250,9 +1250,28 @@ class StockList(
|
|||||||
serializer = self.get_serializer(data=data)
|
serializer = self.get_serializer(data=data)
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
|
|
||||||
|
# Extract 'duplicate' options (if provided) - these are not valid model fields
|
||||||
|
duplicate = serializer.validated_data.pop('duplicate', None)
|
||||||
|
|
||||||
# Extract location information
|
# Extract location information
|
||||||
location = serializer.validated_data.get('location', None)
|
location = serializer.validated_data.get('location', None)
|
||||||
|
|
||||||
|
def apply_duplicate_options(item):
|
||||||
|
"""Apply any provided 'duplicate' options to a newly created StockItem."""
|
||||||
|
if not duplicate:
|
||||||
|
return
|
||||||
|
|
||||||
|
original = duplicate['original']
|
||||||
|
|
||||||
|
if duplicate.get('copy_notes', True):
|
||||||
|
item.copy_notes_from(original)
|
||||||
|
|
||||||
|
if duplicate.get('copy_history', False):
|
||||||
|
item.copyHistoryFrom(original)
|
||||||
|
|
||||||
|
if duplicate.get('copy_tests', False):
|
||||||
|
item.copyTestResultsFrom(original)
|
||||||
|
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
if serials:
|
if serials:
|
||||||
# Create multiple serialized StockItem objects
|
# Create multiple serialized StockItem objects
|
||||||
@@ -1268,6 +1287,8 @@ class StockList(
|
|||||||
item.set_status(status_value)
|
item.set_status(status_value)
|
||||||
item.save()
|
item.save()
|
||||||
|
|
||||||
|
apply_duplicate_options(item)
|
||||||
|
|
||||||
if entry := item.add_tracking_entry(
|
if entry := item.add_tracking_entry(
|
||||||
StockHistoryCode.CREATED,
|
StockHistoryCode.CREATED,
|
||||||
user,
|
user,
|
||||||
@@ -1300,6 +1321,8 @@ class StockList(
|
|||||||
item.save(user=user)
|
item.save(user=user)
|
||||||
item.refresh_from_db()
|
item.refresh_from_db()
|
||||||
|
|
||||||
|
apply_duplicate_options(item)
|
||||||
|
|
||||||
response_data = [
|
response_data = [
|
||||||
StockSerializers.StockItemSerializer(
|
StockSerializers.StockItemSerializer(
|
||||||
item, context=self.get_serializer_context()
|
item, context=self.get_serializer_context()
|
||||||
|
|||||||
@@ -324,6 +324,8 @@ class StockItemSerializer(
|
|||||||
|
|
||||||
export_exclude_fields = ['tags', 'tracking_items']
|
export_exclude_fields = ['tags', 'tracking_items']
|
||||||
|
|
||||||
|
SKIP_CREATE_FIELDS = ['duplicate']
|
||||||
|
|
||||||
export_child_fields = [
|
export_child_fields = [
|
||||||
'part_detail.name',
|
'part_detail.name',
|
||||||
'part_detail.description',
|
'part_detail.description',
|
||||||
@@ -380,6 +382,7 @@ class StockItemSerializer(
|
|||||||
'purchase_price_currency',
|
'purchase_price_currency',
|
||||||
'use_pack_size',
|
'use_pack_size',
|
||||||
'serial_numbers',
|
'serial_numbers',
|
||||||
|
'duplicate',
|
||||||
# Annotated fields
|
# Annotated fields
|
||||||
'allocated',
|
'allocated',
|
||||||
'expired',
|
'expired',
|
||||||
@@ -467,6 +470,28 @@ class StockItemSerializer(
|
|||||||
help_text=_('Enter serial numbers for new items'),
|
help_text=_('Enter serial numbers for new items'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Extra field used only for creation of a new StockItem instance
|
||||||
|
duplicate = InvenTree.serializers.DuplicateOptionsSerializer(
|
||||||
|
StockItem.objects.all(),
|
||||||
|
label=_('Duplicate Stock Item'),
|
||||||
|
help_text=_('Copy initial data from another stock item'),
|
||||||
|
copy_notes=True,
|
||||||
|
copy_fields=[
|
||||||
|
{
|
||||||
|
'name': 'copy_tests',
|
||||||
|
'label': _('Copy Test Results'),
|
||||||
|
'help_text': _('Copy test results from the original stock item'),
|
||||||
|
'default': False,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'copy_history',
|
||||||
|
'label': _('Copy History'),
|
||||||
|
'help_text': _('Copy stock history from the original stock item'),
|
||||||
|
'default': False,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
def validate_part(self, part):
|
def validate_part(self, part):
|
||||||
"""Ensure the provided Part instance is valid."""
|
"""Ensure the provided Part instance is valid."""
|
||||||
if part.virtual:
|
if part.virtual:
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import { modals } from '@mantine/modals';
|
|||||||
import {
|
import {
|
||||||
IconCalendarExclamation,
|
IconCalendarExclamation,
|
||||||
IconCoins,
|
IconCoins,
|
||||||
|
IconCopy,
|
||||||
IconCurrencyDollar,
|
IconCurrencyDollar,
|
||||||
IconLink,
|
IconLink,
|
||||||
IconPackage,
|
IconPackage,
|
||||||
@@ -81,7 +82,8 @@ export function useStockFields({
|
|||||||
create = false,
|
create = false,
|
||||||
supplierPartId,
|
supplierPartId,
|
||||||
pricing,
|
pricing,
|
||||||
modalId
|
modalId,
|
||||||
|
duplicateStockItem
|
||||||
}: {
|
}: {
|
||||||
partId?: number;
|
partId?: number;
|
||||||
stockItem?: any;
|
stockItem?: any;
|
||||||
@@ -89,6 +91,7 @@ export function useStockFields({
|
|||||||
create: boolean;
|
create: boolean;
|
||||||
supplierPartId?: number;
|
supplierPartId?: number;
|
||||||
pricing?: { [priceBreak: number]: [number, string] };
|
pricing?: { [priceBreak: number]: [number, string] };
|
||||||
|
duplicateStockItem?: any;
|
||||||
}): ApiFormFieldSet {
|
}): ApiFormFieldSet {
|
||||||
const globalSettings = useGlobalSettingsState();
|
const globalSettings = useGlobalSettingsState();
|
||||||
|
|
||||||
@@ -301,6 +304,25 @@ export function useStockFields({
|
|||||||
delete fields.serial_numbers;
|
delete fields.serial_numbers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Additional fields for stock item duplication
|
||||||
|
if (create && duplicateStockItem?.pk) {
|
||||||
|
fields.duplicate = {
|
||||||
|
icon: <IconCopy />,
|
||||||
|
children: {
|
||||||
|
original: {
|
||||||
|
value: duplicateStockItem.pk,
|
||||||
|
hidden: true
|
||||||
|
},
|
||||||
|
copy_notes: { value: true },
|
||||||
|
copy_history: { value: false },
|
||||||
|
copy_tests: {
|
||||||
|
value: false,
|
||||||
|
hidden: !duplicateStockItem?.part_detail?.testable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return fields;
|
return fields;
|
||||||
}, [
|
}, [
|
||||||
stockItem,
|
stockItem,
|
||||||
@@ -315,6 +337,7 @@ export function useStockFields({
|
|||||||
purchasePriceCurrency,
|
purchasePriceCurrency,
|
||||||
serialGenerator.result,
|
serialGenerator.result,
|
||||||
batchGenerator.result,
|
batchGenerator.result,
|
||||||
|
duplicateStockItem,
|
||||||
create
|
create
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -742,7 +742,8 @@ export default function StockDetail() {
|
|||||||
|
|
||||||
const duplicateStockItemFields = useStockFields({
|
const duplicateStockItemFields = useStockFields({
|
||||||
create: true,
|
create: true,
|
||||||
modalId: 'duplicate-stock-item'
|
modalId: 'duplicate-stock-item',
|
||||||
|
duplicateStockItem: stockitem
|
||||||
});
|
});
|
||||||
|
|
||||||
const duplicateStockData = useMemo(() => {
|
const duplicateStockData = useMemo(() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user