From cd41ca2a87da2252b61ea046483a5328bae64064 Mon Sep 17 00:00:00 2001 From: Oliver Date: Sat, 22 Feb 2025 11:52:16 +1100 Subject: [PATCH] Batch code backport (#9138) * Batch code fix (#9123) * Fix batch code assignment when receiving items * Add playwright tests * Harden playwright tests * Refactoring * Handle undefined values * Fix conflicts --- .../src/components/render/StatusRenderer.tsx | 20 +++ src/frontend/src/forms/PurchaseOrderForms.tsx | 123 +++++++++--------- 2 files changed, 81 insertions(+), 62 deletions(-) diff --git a/src/frontend/src/components/render/StatusRenderer.tsx b/src/frontend/src/components/render/StatusRenderer.tsx index 7fc1def3ee..45b99377e8 100644 --- a/src/frontend/src/components/render/StatusRenderer.tsx +++ b/src/frontend/src/components/render/StatusRenderer.tsx @@ -83,6 +83,26 @@ export function getStatusCodes(type: ModelType | string) { return statusCodes; } +/** + * Return a list of status codes select options for a given model type + * returns an array of objects with keys "value" and "display_name" + * + */ +export function getStatusCodeOptions(type: ModelType | string): any[] { + const statusCodes = getStatusCodes(type); + + if (!statusCodes) { + return []; + } + + return Object.values(statusCodes?.values ?? []).map((entry) => { + return { + value: entry.key, + display_name: entry.label + }; + }); +} + /* * Return the name of a status code, based on the key */ diff --git a/src/frontend/src/forms/PurchaseOrderForms.tsx b/src/frontend/src/forms/PurchaseOrderForms.tsx index 8523a3a28f..3ba8ac4bb4 100644 --- a/src/frontend/src/forms/PurchaseOrderForms.tsx +++ b/src/frontend/src/forms/PurchaseOrderForms.tsx @@ -22,10 +22,7 @@ import { IconUser, IconUsers } from '@tabler/icons-react'; -import { useQuery } from '@tanstack/react-query'; import { useEffect, useMemo, useState } from 'react'; - -import { api } from '../App'; import { ActionButton } from '../components/buttons/ActionButton'; import RemoveRowButton from '../components/buttons/RemoveRowButton'; import { StandaloneField } from '../components/forms/StandaloneField'; @@ -40,6 +37,7 @@ import { import { Thumbnail } from '../components/images/Thumbnail'; import { ProgressBar } from '../components/items/ProgressBar'; import { StylishText } from '../components/items/StylishText'; +import { getStatusCodeOptions } from '../components/render/StatusRenderer'; import { ApiEndpoints } from '../enums/ApiEndpoints'; import { ModelType } from '../enums/ModelType'; import { InvenTreeIcon } from '../functions/icons'; @@ -291,10 +289,14 @@ function LineItemFormRow({ order: record?.order }); // Generate new serial numbers - serialNumberGenerator.update({ - part: record?.supplier_part_detail?.part, - quantity: props.item.quantity - }); + if (trackable) { + serialNumberGenerator.update({ + part: record?.supplier_part_detail?.part, + quantity: props.item.quantity + }); + } else { + props.changeFn(props.idx, 'serial_numbers', undefined); + } } }); @@ -564,7 +566,10 @@ function LineItemFormRow({ )} props.changeFn(props.idx, 'batch', value)} + onValueChange={(value) => { + props.changeFn(props.idx, 'batch_code', value); + }} + fieldName='batch_code' fieldDefinition={{ field_type: 'string', label: t`Batch Code`, @@ -578,6 +583,7 @@ function LineItemFormRow({ onValueChange={(value) => props.changeFn(props.idx, 'serial_numbers', value) } + fieldName='serial_numbers' fieldDefinition={{ field_type: 'string', label: t`Serial Numbers`, @@ -589,6 +595,7 @@ function LineItemFormRow({ props.changeFn(props.idx, 'packaging', value)} + fieldName='packaging' fieldDefinition={{ field_type: 'string', label: t`Packaging` @@ -599,6 +606,7 @@ function LineItemFormRow({ props.changeFn(props.idx, 'status', value)} fieldDefinition={{ field_type: 'choice', @@ -610,6 +618,7 @@ function LineItemFormRow({ /> props.changeFn(props.idx, 'note', value)} fieldDefinition={{ field_type: 'string', @@ -634,23 +643,10 @@ type LineItemsForm = { }; export function useReceiveLineItems(props: LineItemsForm) { - const { data } = useQuery({ - queryKey: ['stock', 'status'], - queryFn: async () => { - return api.get(apiUrl(ApiEndpoints.stock_status)).then((response) => { - if (response.status === 200) { - const entries = Object.values(response.data.values); - const mapped = entries.map((item: any) => { - return { - value: item.key, - display_name: item.label - }; - }); - return mapped; - } - }); - } - }); + const stockStatusCodes = useMemo( + () => getStatusCodeOptions(ModelType.stockitem), + [] + ); const records = Object.fromEntries( props.items.map((item) => [item.pk, item]) @@ -660,44 +656,46 @@ export function useReceiveLineItems(props: LineItemsForm) { (elem) => elem.quantity !== elem.received ); - const fields: ApiFormFieldSet = { - id: { - value: props.orderPk, - hidden: true - }, - items: { - field_type: 'table', - value: filteredItems.map((elem, idx) => { - return { - line_item: elem.pk, - location: elem.destination ?? elem.destination_detail?.pk ?? null, - quantity: elem.quantity - elem.received, - batch_code: '', - serial_numbers: '', - status: 10, - barcode: null - }; - }), - modelRenderer: (row: TableFieldRowProps) => { - const record = records[row.item.line_item]; - - return ( - - ); + const fields: ApiFormFieldSet = useMemo(() => { + return { + id: { + value: props.orderPk, + hidden: true }, - headers: [t`Part`, t`SKU`, t`Received`, t`Quantity`, t`Actions`] - }, - location: { - filters: { - structural: false + items: { + field_type: 'table', + value: filteredItems.map((elem, idx) => { + return { + line_item: elem.pk, + location: elem.destination ?? elem.destination_detail?.pk ?? null, + quantity: elem.quantity - elem.received, + batch_code: '', + serial_numbers: '', + status: 10, + barcode: null + }; + }), + modelRenderer: (row: TableFieldRowProps) => { + const record = records[row.item.line_item]; + + return ( + + ); + }, + headers: [t`Part`, t`SKU`, t`Received`, t`Quantity`, t`Actions`] + }, + location: { + filters: { + structural: false + } } - } - }; + }; + }, [filteredItems, props, stockStatusCodes]); return useCreateApiFormModal({ ...props.formProps, @@ -707,6 +705,7 @@ export function useReceiveLineItems(props: LineItemsForm) { initialData: { location: props.destinationPk }, - size: '80%' + size: '80%', + successMessage: t`Items received` }); }