mirror of
https://github.com/inventree/InvenTree.git
synced 2025-06-18 04:55:44 +00:00
Batch code generation (#7000)
* Refactor framework for generating batch codes - Provide additional kwargs to plugin - Move into new file - Error handling * Implement API endpoint for generating a new batch code * Fixes * Refactor into stock.generators * Fix API endpoint * Pass time context through to plugins * Generate batch code when receiving items * Create useGenerator hook - Build up a dataset and query server whenever it changes - Look for result in response data - For now, just used for generating batch codes - may be used for more in the future * Refactor PurchaseOrderForms to use new generator hook * Refactor StockForms implementation * Remove dead code * add OAS diff * fix ref * fix ref again * wrong branch, sorry * Update src/frontend/src/hooks/UseGenerator.tsx Co-authored-by: Lukas <76838159+wolflu05@users.noreply.github.com> * Bump API version * Do not override batch code if already generated * Add serial number generator - Move to /generate/ API endpoint - Move batch code generator too * Update PUI endpoints * Add debouncing to useGenerator hook * Refactor useGenerator func * Add serial number generator to stock form * Add batch code genereator to build order form * Update buildfields * Use build batch code when creating new output --------- Co-authored-by: Matthias Mair <code@mjmair.com> Co-authored-by: Lukas <76838159+wolflu05@users.noreply.github.com>
This commit is contained in:
@ -108,6 +108,10 @@ export enum ApiEndpoints {
|
||||
stock_status = 'stock/status/',
|
||||
stock_install = 'stock/:id/install',
|
||||
|
||||
// Generator API endpoints
|
||||
generate_batch_code = 'generate/batch-code/',
|
||||
generate_serial_number = 'generate/serial-number/',
|
||||
|
||||
// Order API endpoints
|
||||
purchase_order_list = 'order/po/',
|
||||
purchase_order_line_list = 'order/po-line/',
|
||||
|
@ -19,6 +19,7 @@ import { ApiEndpoints } from '../enums/ApiEndpoints';
|
||||
import { ModelType } from '../enums/ModelType';
|
||||
import { InvenTreeIcon } from '../functions/icons';
|
||||
import { useCreateApiFormModal } from '../hooks/UseForm';
|
||||
import { useBatchCodeGenerator } from '../hooks/UseGenerator';
|
||||
import { apiUrl } from '../states/ApiState';
|
||||
import { PartColumn, StatusColumn } from '../tables/ColumnRenderers';
|
||||
|
||||
@ -34,10 +35,19 @@ export function useBuildOrderFields({
|
||||
null
|
||||
);
|
||||
|
||||
const [batchCode, setBatchCode] = useState<string>('');
|
||||
|
||||
const batchGenerator = useBatchCodeGenerator((value: any) => {
|
||||
if (!batchCode) {
|
||||
setBatchCode(value);
|
||||
}
|
||||
});
|
||||
|
||||
return useMemo(() => {
|
||||
return {
|
||||
reference: {},
|
||||
part: {
|
||||
disabled: !create,
|
||||
filters: {
|
||||
assembly: true,
|
||||
virtual: false
|
||||
@ -49,6 +59,10 @@ export function useBuildOrderFields({
|
||||
record.default_location || record.category_default_location
|
||||
);
|
||||
}
|
||||
|
||||
batchGenerator.update({
|
||||
part: value
|
||||
});
|
||||
}
|
||||
},
|
||||
title: {},
|
||||
@ -66,7 +80,10 @@ export function useBuildOrderFields({
|
||||
sales_order: {
|
||||
icon: <IconTruckDelivery />
|
||||
},
|
||||
batch: {},
|
||||
batch: {
|
||||
value: batchCode,
|
||||
onValueChange: (value: any) => setBatchCode(value)
|
||||
},
|
||||
target_date: {
|
||||
icon: <IconCalendar />
|
||||
},
|
||||
@ -90,7 +107,7 @@ export function useBuildOrderFields({
|
||||
}
|
||||
}
|
||||
};
|
||||
}, [create, destination]);
|
||||
}, [create, destination, batchCode]);
|
||||
}
|
||||
|
||||
export function useBuildOrderOutputFields({
|
||||
|
@ -39,6 +39,7 @@ import { ApiEndpoints } from '../enums/ApiEndpoints';
|
||||
import { ModelType } from '../enums/ModelType';
|
||||
import { InvenTreeIcon } from '../functions/icons';
|
||||
import { useCreateApiFormModal } from '../hooks/UseForm';
|
||||
import { useBatchCodeGenerator } from '../hooks/UseGenerator';
|
||||
import { apiUrl } from '../states/ApiState';
|
||||
|
||||
/*
|
||||
@ -212,6 +213,12 @@ function LineItemFormRow({
|
||||
input.changeFn(input.idx, 'location', location);
|
||||
}, [location]);
|
||||
|
||||
const batchCodeGenerator = useBatchCodeGenerator((value: any) => {
|
||||
if (!batchCode) {
|
||||
setBatchCode(value);
|
||||
}
|
||||
});
|
||||
|
||||
// State for serializing
|
||||
const [batchCode, setBatchCode] = useState<string>('');
|
||||
const [serials, setSerials] = useState<string>('');
|
||||
@ -219,6 +226,13 @@ function LineItemFormRow({
|
||||
onClose: () => {
|
||||
input.changeFn(input.idx, 'batch_code', '');
|
||||
input.changeFn(input.idx, 'serial_numbers', '');
|
||||
},
|
||||
onOpen: () => {
|
||||
// Generate a new batch code
|
||||
batchCodeGenerator.update({
|
||||
part: record?.supplier_part_detail?.part,
|
||||
order: record?.order
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -21,6 +21,10 @@ import {
|
||||
useCreateApiFormModal,
|
||||
useDeleteApiFormModal
|
||||
} from '../hooks/UseForm';
|
||||
import {
|
||||
useBatchCodeGenerator,
|
||||
useSerialNumberGenerator
|
||||
} from '../hooks/UseGenerator';
|
||||
import { apiUrl } from '../states/ApiState';
|
||||
|
||||
/**
|
||||
@ -34,15 +38,41 @@ export function useStockFields({
|
||||
const [part, setPart] = useState<number | null>(null);
|
||||
const [supplierPart, setSupplierPart] = useState<number | null>(null);
|
||||
|
||||
const [batchCode, setBatchCode] = useState<string>('');
|
||||
const [serialNumbers, setSerialNumbers] = useState<string>('');
|
||||
|
||||
const [trackable, setTrackable] = useState<boolean>(false);
|
||||
|
||||
const batchGenerator = useBatchCodeGenerator((value: any) => {
|
||||
if (!batchCode) {
|
||||
setBatchCode(value);
|
||||
}
|
||||
});
|
||||
|
||||
const serialGenerator = useSerialNumberGenerator((value: any) => {
|
||||
if (!serialNumbers && create && trackable) {
|
||||
setSerialNumbers(value);
|
||||
}
|
||||
});
|
||||
|
||||
return useMemo(() => {
|
||||
const fields: ApiFormFieldSet = {
|
||||
part: {
|
||||
value: part,
|
||||
disabled: !create,
|
||||
onValueChange: (change) => {
|
||||
setPart(change);
|
||||
onValueChange: (value, record) => {
|
||||
setPart(value);
|
||||
// TODO: implement remaining functionality from old stock.py
|
||||
|
||||
setTrackable(record.trackable ?? false);
|
||||
|
||||
batchGenerator.update({ part: value });
|
||||
serialGenerator.update({ part: value });
|
||||
|
||||
if (!record.trackable) {
|
||||
setSerialNumbers('');
|
||||
}
|
||||
|
||||
// Clear the 'supplier_part' field if the part is changed
|
||||
setSupplierPart(null);
|
||||
}
|
||||
@ -50,7 +80,9 @@ export function useStockFields({
|
||||
supplier_part: {
|
||||
// TODO: icon
|
||||
value: supplierPart,
|
||||
onValueChange: setSupplierPart,
|
||||
onValueChange: (value) => {
|
||||
setSupplierPart(value);
|
||||
},
|
||||
filters: {
|
||||
part_detail: true,
|
||||
supplier_detail: true,
|
||||
@ -70,22 +102,29 @@ export function useStockFields({
|
||||
},
|
||||
location: {
|
||||
hidden: !create,
|
||||
onValueChange: (value) => {
|
||||
batchGenerator.update({ location: value });
|
||||
},
|
||||
filters: {
|
||||
structural: false
|
||||
}
|
||||
// TODO: icon
|
||||
},
|
||||
quantity: {
|
||||
hidden: !create,
|
||||
description: t`Enter initial quantity for this stock item`
|
||||
description: t`Enter initial quantity for this stock item`,
|
||||
onValueChange: (value) => {
|
||||
batchGenerator.update({ quantity: value });
|
||||
}
|
||||
},
|
||||
serial_numbers: {
|
||||
// TODO: icon
|
||||
field_type: 'string',
|
||||
label: t`Serial Numbers`,
|
||||
description: t`Enter serial numbers for new stock (or leave blank)`,
|
||||
required: false,
|
||||
hidden: !create
|
||||
disabled: !trackable,
|
||||
hidden: !create,
|
||||
value: serialNumbers,
|
||||
onValueChange: (value) => setSerialNumbers(value)
|
||||
},
|
||||
serial: {
|
||||
hidden: create
|
||||
@ -93,6 +132,8 @@ export function useStockFields({
|
||||
},
|
||||
batch: {
|
||||
// TODO: icon
|
||||
value: batchCode,
|
||||
onValueChange: (value) => setBatchCode(value)
|
||||
},
|
||||
status: {},
|
||||
expiry_date: {
|
||||
@ -120,7 +161,7 @@ export function useStockFields({
|
||||
// TODO: refer to stock.py in original codebase
|
||||
|
||||
return fields;
|
||||
}, [part, supplierPart]);
|
||||
}, [part, supplierPart, batchCode, serialNumbers, trackable, create]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -194,6 +194,7 @@ const icons = {
|
||||
downright: IconCornerDownRight,
|
||||
barcode: IconQrcode,
|
||||
barLine: IconMinusVertical,
|
||||
batch: IconClipboardText,
|
||||
batch_code: IconClipboardText,
|
||||
destination: IconFlag,
|
||||
repeat_destination: IconFlagShare,
|
||||
|
90
src/frontend/src/hooks/UseGenerator.tsx
Normal file
90
src/frontend/src/hooks/UseGenerator.tsx
Normal file
@ -0,0 +1,90 @@
|
||||
import { useDebouncedValue } from '@mantine/hooks';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useCallback, useState } from 'react';
|
||||
|
||||
import { api } from '../App';
|
||||
import { ApiEndpoints } from '../enums/ApiEndpoints';
|
||||
import { apiUrl } from '../states/ApiState';
|
||||
|
||||
export type GeneratorState = {
|
||||
query: Record<string, any>;
|
||||
result: any;
|
||||
update: (params: Record<string, any>, overwrite?: boolean) => void;
|
||||
};
|
||||
|
||||
/* Hook for managing generation of data via the InvenTree API.
|
||||
* We pass an endpoint, and start with an initially empty query.
|
||||
* We can pass additional parameters to the query, and update the query as needed.
|
||||
* Each update calls a new query to the API, and the result is stored in the state.
|
||||
*/
|
||||
export function useGenerator(
|
||||
endpoint: ApiEndpoints,
|
||||
key: string,
|
||||
onGenerate?: (value: any) => void
|
||||
): GeneratorState {
|
||||
// Track the result
|
||||
const [result, setResult] = useState<any>(null);
|
||||
|
||||
// Track the generator query
|
||||
const [query, setQuery] = useState<Record<string, any>>({});
|
||||
|
||||
// Prevent rapid updates
|
||||
const [debouncedQuery] = useDebouncedValue<Record<string, any>>(query, 250);
|
||||
|
||||
// Callback to update the generator query
|
||||
const update = useCallback(
|
||||
(params: Record<string, any>, overwrite?: boolean) => {
|
||||
if (overwrite) {
|
||||
setQuery(params);
|
||||
} else {
|
||||
setQuery((query) => ({
|
||||
...query,
|
||||
...params
|
||||
}));
|
||||
}
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
// API query handler
|
||||
const queryGenerator = useQuery({
|
||||
enabled: true,
|
||||
queryKey: ['generator', key, endpoint, debouncedQuery],
|
||||
queryFn: async () => {
|
||||
return api.post(apiUrl(endpoint), debouncedQuery).then((response) => {
|
||||
const value = response?.data[key];
|
||||
setResult(value);
|
||||
|
||||
if (onGenerate) {
|
||||
onGenerate(value);
|
||||
}
|
||||
|
||||
return response;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
query,
|
||||
update,
|
||||
result
|
||||
};
|
||||
}
|
||||
|
||||
// Generate a batch code with provided data
|
||||
export function useBatchCodeGenerator(onGenerate: (value: any) => void) {
|
||||
return useGenerator(
|
||||
ApiEndpoints.generate_batch_code,
|
||||
'batch_code',
|
||||
onGenerate
|
||||
);
|
||||
}
|
||||
|
||||
// Generate a serial number with provided data
|
||||
export function useSerialNumberGenerator(onGenerate: (value: any) => void) {
|
||||
return useGenerator(
|
||||
ApiEndpoints.generate_serial_number,
|
||||
'serial_number',
|
||||
onGenerate
|
||||
);
|
||||
}
|
@ -191,6 +191,13 @@ export default function BuildDetail() {
|
||||
model: ModelType.stocklocation,
|
||||
label: t`Destination Location`,
|
||||
hidden: !build.destination
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'batch',
|
||||
label: t`Batch Code`,
|
||||
hidden: !build.batch,
|
||||
copy: true
|
||||
}
|
||||
];
|
||||
|
||||
|
@ -113,6 +113,9 @@ export default function BuildOutputTable({ build }: { build: any }) {
|
||||
url: apiUrl(ApiEndpoints.build_output_create, buildId),
|
||||
title: t`Add Build Output`,
|
||||
fields: buildOutputFields,
|
||||
initialData: {
|
||||
batch_code: build.batch
|
||||
},
|
||||
table: table
|
||||
});
|
||||
|
||||
|
Reference in New Issue
Block a user