Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PUI] Build allocation sub tables #8380

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/backend/InvenTree/build/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from django.db.models import (
BooleanField,
Case,
Count,
ExpressionWrapper,
F,
FloatField,
Expand Down Expand Up @@ -1362,6 +1363,8 @@ class Meta:
part_detail = part_serializers.PartBriefSerializer(source='bom_item.sub_part', many=False, read_only=True, pricing=False)

# Annotated (calculated) fields

# Total quantity of allocated stock
allocated = serializers.FloatField(
label=_('Allocated Stock'),
read_only=True
Expand Down Expand Up @@ -1476,7 +1479,7 @@ def annotate_queryset(queryset, build=None):
allocated=Coalesce(
Sum('allocations__quantity'), 0,
output_field=models.DecimalField()
),
)
)

ref = 'bom_item__sub_part__'
Expand Down
16 changes: 14 additions & 2 deletions src/backend/InvenTree/templates/js/translated/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -2505,6 +2505,7 @@ function renderBuildLineAllocationTable(element, build_line, options={}) {
url: '{% url "api-build-item-list" %}',
queryParams: {
build_line: build_line.pk,
output: options.output ?? undefined,
},
showHeader: false,
columns: [
Expand Down Expand Up @@ -2609,9 +2610,10 @@ function renderBuildLineAllocationTable(element, build_line, options={}) {
*/
function loadBuildLineTable(table, build_id, options={}) {

const params = options.params || {};
const output = options.output;

let name = 'build-lines';
let params = options.params || {};
let output = options.output;

params.build = build_id;

Expand Down Expand Up @@ -2647,6 +2649,7 @@ function loadBuildLineTable(table, build_id, options={}) {
detailFormatter: function(_index, row, element) {
renderBuildLineAllocationTable(element, row, {
parent_table: table,
output: output,
});
},
formatNoMatches: function() {
Expand Down Expand Up @@ -2730,6 +2733,15 @@ function loadBuildLineTable(table, build_id, options={}) {
return yesNoLabel(row.bom_item_detail.inherited);
}
},
{
field: 'trackable',
title: '{% trans "Trackable" %}',
sortable: true,
switchable: true,
formatter: function(value, row) {
return yesNoLabel(row.part_detail.trackable);
}
},
{
field: 'unit_quantity',
sortable: true,
Expand Down
106 changes: 78 additions & 28 deletions src/frontend/src/components/forms/fields/TableField.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Trans, t } from '@lingui/macro';
import { Alert, Container, Group, Table } from '@mantine/core';
import { Alert, Container, Group, Stack, Table, Text } from '@mantine/core';
import { IconExclamationCircle } from '@tabler/icons-react';
import { useCallback, useEffect, useMemo } from 'react';
import { ReactNode, useCallback, useEffect, useMemo } from 'react';
import { FieldValues, UseControllerReturn } from 'react-hook-form';

import { identifierString } from '../../../functions/conversion';
Expand All @@ -18,6 +18,69 @@ export interface TableFieldRowProps {
removeFn: (idx: number) => void;
}

function TableFieldRow({
item,
idx,
errors,
definition,
control,
changeFn,
removeFn
}: {
item: any;
idx: number;
errors: any;
definition: ApiFormFieldType;
control: UseControllerReturn<FieldValues, any>;
changeFn: (idx: number, key: string, value: any) => void;
removeFn: (idx: number) => void;
}) {
// Table fields require render function
if (!definition.modelRenderer) {
return (
<Table.Tr key="table-row-no-renderer">
<Table.Td colSpan={definition.headers?.length}>
<Alert color="red" title={t`Error`} icon={<IconExclamationCircle />}>
{`modelRenderer entry required for tables`}
</Alert>
</Table.Td>
</Table.Tr>
);
}

return definition.modelRenderer({
item: item,
idx: idx,
rowErrors: errors,
control: control,
changeFn: changeFn,
removeFn: removeFn
});
}

export function TableFieldErrorWrapper({
props,
errorKey,
children
}: {
props: TableFieldRowProps;
errorKey: string;
children: ReactNode;
}) {
const msg = props?.rowErrors && props.rowErrors[errorKey];

return (
<Stack gap="xs">
{children}
{msg && (
<Text size="xs" c="red">
{msg.message}
</Text>
)}
</Stack>
);
}

export function TableField({
definition,
fieldName,
Expand Down Expand Up @@ -47,7 +110,7 @@ export function TableField({
};

// Extract errors associated with the current row
const rowErrors = useCallback(
const rowErrors: any = useCallback(
(idx: number) => {
if (Array.isArray(error)) {
return error[idx];
Expand All @@ -74,31 +137,18 @@ export function TableField({
<Table.Tbody>
{value.length > 0 ? (
value.map((item: any, idx: number) => {
// Table fields require render function
if (!definition.modelRenderer) {
return (
<Table.Tr key="table-row-no-renderer">
<Table.Td colSpan={definition.headers?.length}>
<Alert
color="red"
title={t`Error`}
icon={<IconExclamationCircle />}
>
{`modelRenderer entry required for tables`}
</Alert>
</Table.Td>
</Table.Tr>
);
}

return definition.modelRenderer({
item: item,
idx: idx,
rowErrors: rowErrors(idx),
control: control,
changeFn: onRowFieldChange,
removeFn: removeRow
});
return (
<TableFieldRow
key={`table-row-${idx}`}
item={item}
idx={idx}
errors={rowErrors(idx)}
control={control}
definition={definition}
changeFn={onRowFieldChange}
removeFn={removeRow}
/>
);
})
) : (
<Table.Tr key="table-row-no-entries">
Expand Down
22 changes: 15 additions & 7 deletions src/frontend/src/forms/BuildForms.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ import {
ApiFormFieldSet,
ApiFormFieldType
} from '../components/forms/fields/ApiFormField';
import { TableFieldRowProps } from '../components/forms/fields/TableField';
import {
TableFieldErrorWrapper,
TableFieldRowProps
} from '../components/forms/fields/TableField';
import { ProgressBar } from '../components/items/ProgressBar';
import { StatusRenderer } from '../components/render/StatusRenderer';
import { ApiEndpoints } from '../enums/ApiEndpoints';
Expand Down Expand Up @@ -210,7 +213,11 @@ function BuildOutputFormRow({
<Table.Td>
<PartColumn part={record.part_detail} />
</Table.Td>
<Table.Td>{serial}</Table.Td>
<Table.Td>
<TableFieldErrorWrapper props={props} errorKey="output">
{serial}
</TableFieldErrorWrapper>
</Table.Td>
<Table.Td>{record.batch}</Table.Td>
<Table.Td>
<StatusRenderer status={record.status} type={ModelType.stockitem} />{' '}
Expand Down Expand Up @@ -259,7 +266,7 @@ export function useCompleteBuildOutputsForm({
<BuildOutputFormRow props={row} record={record} key={record.pk} />
);
},
headers: [t`Part`, t`Stock Item`, t`Batch`, t`Status`]
headers: [t`Part`, t`Build Output`, t`Batch`, t`Status`]
},
status_custom_key: {},
location: {
Expand Down Expand Up @@ -454,8 +461,8 @@ function BuildAllocateLineRow({
</Table.Td>
<Table.Td>
<ProgressBar
value={record.allocated}
maximum={record.quantity}
value={record.allocatedQuantity}
maximum={record.requiredQuantity}
progressLabel
/>
</Table.Td>
Expand Down Expand Up @@ -512,6 +519,7 @@ export function useAllocateStockToBuildForm({
lineItems.find((item) => item.pk == row.item.build_line) ?? {};
return (
<BuildAllocateLineRow
key={row.idx}
props={row}
record={record}
sourceLocation={sourceLocation}
Expand Down Expand Up @@ -565,8 +573,8 @@ export function useAllocateStockToBuildForm({
return {
build_line: item.pk,
stock_item: undefined,
quantity: Math.max(0, item.quantity - item.allocated),
output: null
quantity: Math.max(0, item.requiredQuantity - item.allocatedQuantity),
output: outputId
};
})
},
Expand Down
10 changes: 10 additions & 0 deletions src/frontend/src/hooks/UseTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export type TableState = {
clearActiveFilters: () => void;
expandedRecords: any[];
setExpandedRecords: (records: any[]) => void;
isRowExpanded: (pk: number) => boolean;
selectedRecords: any[];
selectedIds: number[];
hasSelectedRecords: boolean;
Expand Down Expand Up @@ -79,6 +80,14 @@ export function useTable(tableName: string): TableState {
// Array of expanded records
const [expandedRecords, setExpandedRecords] = useState<any[]>([]);

// Function to determine if a record is expanded
const isRowExpanded = useCallback(
(pk: number) => {
return expandedRecords.includes(pk);
},
[expandedRecords]
);

// Array of selected records
const [selectedRecords, setSelectedRecords] = useState<any[]>([]);

Expand Down Expand Up @@ -148,6 +157,7 @@ export function useTable(tableName: string): TableState {
clearActiveFilters,
expandedRecords,
setExpandedRecords,
isRowExpanded,
selectedRecords,
selectedIds,
setSelectedRecords,
Expand Down
6 changes: 1 addition & 5 deletions src/frontend/src/pages/build/BuildDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -251,11 +251,7 @@ export default function BuildDetail() {
name: 'line-items',
label: t`Line Items`,
icon: <IconListNumbers />,
content: build?.pk ? (
<BuildLineTable build={build} buildId={build.pk} />
) : (
<Skeleton />
)
content: build?.pk ? <BuildLineTable build={build} /> : <Skeleton />
},
{
name: 'incomplete-outputs',
Expand Down
32 changes: 30 additions & 2 deletions src/frontend/src/tables/InvenTreeTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { useQuery } from '@tanstack/react-query';
import {
DataTable,
DataTableCellClickHandler,
DataTableRowExpansionProps,
DataTableSortStatus
} from 'mantine-datatable';
import React, {
Expand Down Expand Up @@ -103,7 +104,7 @@ export type InvenTreeTableProps<T = any> = {
barcodeActions?: React.ReactNode[];
tableFilters?: TableFilter[];
tableActions?: React.ReactNode[];
rowExpansion?: any;
rowExpansion?: DataTableRowExpansionProps<T>;
idAccessor?: string;
dataFormatter?: (data: any) => any;
rowActions?: (record: T) => RowAction[];
Expand Down Expand Up @@ -633,6 +634,33 @@ export function InvenTreeTable<T extends Record<string, any>>({
tableState.refreshTable();
}

/**
* Memoize row expansion options:
* - If rowExpansion is not provided, return undefined
* - Otherwise, return the rowExpansion object
* - Utilize the useTable hook to track expanded rows
*/
const rowExpansion: DataTableRowExpansionProps<T> | undefined =
useMemo(() => {
if (!props.rowExpansion) {
return undefined;
}

return {
...props.rowExpansion,
expanded: {
recordIds: tableState.expandedRecords,
onRecordIdsChange: (ids: any[]) => {
tableState.setExpandedRecords(ids);
}
}
};
}, [
tableState.expandedRecords,
tableState.setExpandedRecords,
props.rowExpansion
]);

const optionalParams = useMemo(() => {
let optionalParamsa: Record<string, any> = {};
if (tableProps.enablePagination) {
Expand Down Expand Up @@ -779,7 +807,7 @@ export function InvenTreeTable<T extends Record<string, any>>({
onSelectedRecordsChange={
enableSelection ? onSelectedRecordsChange : undefined
}
rowExpansion={tableProps.rowExpansion}
rowExpansion={rowExpansion}
rowStyle={tableProps.rowStyle}
fetching={isFetching}
noRecordsText={missingRecordsText}
Expand Down
7 changes: 5 additions & 2 deletions src/frontend/src/tables/build/BuildAllocatedStockTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -161,8 +161,11 @@ export default function BuildAllocatedStockTable({
const editItem = useEditApiFormModal({
pk: selectedItem,
url: ApiEndpoints.build_item_list,
title: t`Edit Build Item`,
title: t`Edit Stock Allocation`,
fields: {
stock_item: {
disabled: true
},
quantity: {}
},
table: table
Expand All @@ -171,7 +174,7 @@ export default function BuildAllocatedStockTable({
const deleteItem = useDeleteApiFormModal({
pk: selectedItem,
url: ApiEndpoints.build_item_list,
title: t`Delete Build Item`,
title: t`Delete Stock Allocation`,
table: table
});

Expand Down
Loading
Loading