2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-08-03 02:21:34 +00:00

Table formatting (#10104)

* Format <BuildLineTable />

* More formatting

* Add helper function for running playwright tests

* Fix playwright test

* Further formatting updates

* Adjust order tables
This commit is contained in:
Oliver
2025-07-31 16:39:19 +10:00
committed by GitHub
parent b8ea75b2b4
commit 4794c6d860
13 changed files with 76 additions and 39 deletions

View File

@@ -184,14 +184,14 @@ export default function SupplierPartDetail() {
const tr: DetailsField[] = [ const tr: DetailsField[] = [
{ {
type: 'string', type: 'number',
name: 'in_stock', name: 'in_stock',
label: t`In Stock`, label: t`In Stock`,
copy: true, copy: true,
icon: 'stock' icon: 'stock'
}, },
{ {
type: 'string', type: 'number',
name: 'on_order', name: 'on_order',
label: t`On Order`, label: t`On Order`,
copy: true, copy: true,

View File

@@ -288,13 +288,13 @@ export function BomTable({
available_stock <= 0 ? ( available_stock <= 0 ? (
<Text c='red' style={{ fontStyle: 'italic' }}>{t`No stock`}</Text> <Text c='red' style={{ fontStyle: 'italic' }}>{t`No stock`}</Text>
) : ( ) : (
available_stock `${formatDecimal(available_stock)}`
); );
if (record.external_stock > 0) { if (record.external_stock > 0) {
extra.push( extra.push(
<Text key='external'> <Text key='external'>
{t`External stock`}: {record.external_stock} {t`External stock`}: {formatDecimal(record.external_stock)}
</Text> </Text>
); );
} }
@@ -303,7 +303,7 @@ export function BomTable({
extra.push( extra.push(
<Text key='substitute'> <Text key='substitute'>
{t`Includes substitute stock`}:{' '} {t`Includes substitute stock`}:{' '}
{record.available_substitute_stock} {formatDecimal(record.available_substitute_stock)}
</Text> </Text>
); );
} }
@@ -311,7 +311,8 @@ export function BomTable({
if (record.allow_variants && record.available_variant_stock > 0) { if (record.allow_variants && record.available_variant_stock > 0) {
extra.push( extra.push(
<Text key='variant'> <Text key='variant'>
{t`Includes variant stock`}: {record.available_variant_stock} {t`Includes variant stock`}:{' '}
{formatDecimal(record.available_variant_stock)}
</Text> </Text>
); );
} }
@@ -363,7 +364,7 @@ export function BomTable({
fs={record.consumable && 'italic'} fs={record.consumable && 'italic'}
c={can_build <= 0 && !record.consumable ? 'red' : undefined} c={can_build <= 0 && !record.consumable ? 'red' : undefined}
> >
{can_build} {formatDecimal(can_build)}
</Text> </Text>
); );

View File

@@ -10,6 +10,7 @@ import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
import { ModelType } from '@lib/enums/ModelType'; import { ModelType } from '@lib/enums/ModelType';
import { UserRoles } from '@lib/enums/Roles'; import { UserRoles } from '@lib/enums/Roles';
import { apiUrl } from '@lib/functions/Api'; import { apiUrl } from '@lib/functions/Api';
import { formatDecimal } from '@lib/functions/Formatting';
import type { TableFilter } from '@lib/types/Filters'; import type { TableFilter } from '@lib/types/Filters';
import type { TableColumn } from '@lib/types/Tables'; import type { TableColumn } from '@lib/types/Tables';
import type { StockOperationProps } from '../../forms/StockForms'; import type { StockOperationProps } from '../../forms/StockForms';
@@ -133,13 +134,15 @@ export default function BuildAllocatedStockTable({
{ {
accessor: 'available', accessor: 'available',
title: t`Available Quantity`, title: t`Available Quantity`,
render: (record: any) => record?.stock_item_detail?.quantity render: (record: any) =>
formatDecimal(record?.stock_item_detail?.quantity)
}, },
{ {
accessor: 'quantity', accessor: 'quantity',
title: t`Allocated Quantity`, title: t`Allocated Quantity`,
sortable: true, sortable: true,
switchable: false switchable: false,
render: (record: any) => formatDecimal(record?.quantity)
}, },
LocationColumn({ LocationColumn({
accessor: 'location_detail', accessor: 'location_detail',

View File

@@ -24,6 +24,7 @@ import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
import { ModelType } from '@lib/enums/ModelType'; import { ModelType } from '@lib/enums/ModelType';
import { UserRoles } from '@lib/enums/Roles'; import { UserRoles } from '@lib/enums/Roles';
import { apiUrl } from '@lib/functions/Api'; import { apiUrl } from '@lib/functions/Api';
import { formatDecimal } from '@lib/functions/Formatting';
import type { TableFilter } from '@lib/types/Filters'; import type { TableFilter } from '@lib/types/Filters';
import type { TableColumn } from '@lib/types/Tables'; import type { TableColumn } from '@lib/types/Tables';
import OrderPartsWizard from '../../components/wizards/OrderPartsWizard'; import OrderPartsWizard from '../../components/wizards/OrderPartsWizard';
@@ -248,7 +249,7 @@ export default function BuildLineTable({
if (record.in_production > 0) { if (record.in_production > 0) {
extra.push( extra.push(
<Text key='production' size='sm'> <Text key='production' size='sm'>
{t`In production`}: {record.in_production} {t`In production`}: {formatDecimal(record.in_production)}
</Text> </Text>
); );
} }
@@ -257,7 +258,7 @@ export default function BuildLineTable({
if (record.on_order > 0) { if (record.on_order > 0) {
extra.push( extra.push(
<Text key='on-order' size='sm'> <Text key='on-order' size='sm'>
{t`On order`}: {record.on_order} {t`On order`}: {formatDecimal(record.on_order)}
</Text> </Text>
); );
} }
@@ -266,7 +267,7 @@ export default function BuildLineTable({
if (record.external_stock > 0) { if (record.external_stock > 0) {
extra.push( extra.push(
<Text key='external' size='sm'> <Text key='external' size='sm'>
{t`External stock`}: {record.external_stock} {t`External stock`}: {formatDecimal(record.external_stock)}
</Text> </Text>
); );
} }
@@ -287,7 +288,7 @@ export default function BuildLineTable({
iconColor={sufficient ? 'blue' : 'orange'} iconColor={sufficient ? 'blue' : 'orange'}
value={ value={
available > 0 ? ( available > 0 ? (
available `${formatDecimal(available)}`
) : ( ) : (
<Text <Text
c='red' c='red'
@@ -398,7 +399,8 @@ export default function BuildLineTable({
if (record?.bom_item_detail?.setup_quantity) { if (record?.bom_item_detail?.setup_quantity) {
extra.push( extra.push(
<Text key='setup-quantity' size='sm'> <Text key='setup-quantity' size='sm'>
{t`Setup Quantity`}: {record.bom_item_detail.setup_quantity} {t`Setup Quantity`}:{' '}
{formatDecimal(record.bom_item_detail.setup_quantity)}
</Text> </Text>
); );
} }
@@ -427,7 +429,7 @@ export default function BuildLineTable({
extra={extra} extra={extra}
value={ value={
<Group justify='space-between' wrap='nowrap'> <Group justify='space-between' wrap='nowrap'>
<Text>{record.requiredQuantity}</Text> <Text>{formatDecimal(record.requiredQuantity)}</Text>
{record?.part_detail?.units && ( {record?.part_detail?.units && (
<Text size='xs'>[{record.part_detail.units}]</Text> <Text size='xs'>[{record.part_detail.units}]</Text>
)} )}

View File

@@ -12,7 +12,7 @@ import type { ApiEndpoints } from '@lib/enums/ApiEndpoints';
import type { UserRoles } from '@lib/enums/Roles'; import type { UserRoles } from '@lib/enums/Roles';
import { apiUrl } from '@lib/functions/Api'; import { apiUrl } from '@lib/functions/Api';
import type { TableColumn } from '@lib/types/Tables'; import type { TableColumn } from '@lib/types/Tables';
import { formatCurrency } from '../../defaults/formatters'; import { formatCurrency, formatDecimal } from '../../defaults/formatters';
import { extraLineItemFields } from '../../forms/CommonForms'; import { extraLineItemFields } from '../../forms/CommonForms';
import { import {
useCreateApiFormModal, useCreateApiFormModal,
@@ -49,7 +49,8 @@ export default function ExtraLineItemTable({
DescriptionColumn({}), DescriptionColumn({}),
{ {
accessor: 'quantity', accessor: 'quantity',
switchable: false switchable: false,
render: (record: any) => formatDecimal(record.quantity)
}, },
{ {
accessor: 'price', accessor: 'price',

View File

@@ -14,7 +14,7 @@ import type { TableColumn } from '@lib/types/Tables';
import type { InvenTreeTableProps } from '@lib/types/Tables'; import type { InvenTreeTableProps } from '@lib/types/Tables';
import { ActionDropdown } from '../../components/items/ActionDropdown'; import { ActionDropdown } from '../../components/items/ActionDropdown';
import OrderPartsWizard from '../../components/wizards/OrderPartsWizard'; import OrderPartsWizard from '../../components/wizards/OrderPartsWizard';
import { formatPriceRange } from '../../defaults/formatters'; import { formatDecimal, formatPriceRange } from '../../defaults/formatters';
import { usePartFields } from '../../forms/PartForms'; import { usePartFields } from '../../forms/PartForms';
import { InvenTreeIcon } from '../../functions/icons'; import { InvenTreeIcon } from '../../functions/icons';
import { import {
@@ -80,14 +80,14 @@ function partTableColumns(): TableColumn[] {
const available = Math.max(0, stock - allocated); const available = Math.max(0, stock - allocated);
const min_stock = record?.minimum_stock ?? 0; const min_stock = record?.minimum_stock ?? 0;
let text = String(stock); let text = String(formatDecimal(stock));
let color: string | undefined = undefined; let color: string | undefined = undefined;
if (min_stock > stock) { if (min_stock > stock) {
extra.push( extra.push(
<Text key='min-stock' c='orange'> <Text key='min-stock' c='orange'>
{`${t`Minimum stock`}: ${min_stock}`} {`${t`Minimum stock`}: ${formatDecimal(min_stock)}`}
</Text> </Text>
); );
@@ -96,20 +96,20 @@ function partTableColumns(): TableColumn[] {
if (record.ordering > 0) { if (record.ordering > 0) {
extra.push( extra.push(
<Text key='on-order'>{`${t`On Order`}: ${record.ordering}`}</Text> <Text key='on-order'>{`${t`On Order`}: ${formatDecimal(record.ordering)}`}</Text>
); );
} }
if (record.building) { if (record.building) {
extra.push( extra.push(
<Text key='building'>{`${t`Building`}: ${record.building}`}</Text> <Text key='building'>{`${t`Building`}: ${formatDecimal(record.building)}`}</Text>
); );
} }
if (record.allocated_to_build_orders > 0) { if (record.allocated_to_build_orders > 0) {
extra.push( extra.push(
<Text key='bo-allocations'> <Text key='bo-allocations'>
{`${t`Build Order Allocations`}: ${record.allocated_to_build_orders}`} {`${t`Build Order Allocations`}: ${formatDecimal(record.allocated_to_build_orders)}`}
</Text> </Text>
); );
} }
@@ -117,7 +117,7 @@ function partTableColumns(): TableColumn[] {
if (record.allocated_to_sales_orders > 0) { if (record.allocated_to_sales_orders > 0) {
extra.push( extra.push(
<Text key='so-allocations'> <Text key='so-allocations'>
{`${t`Sales Order Allocations`}: ${record.allocated_to_sales_orders}`} {`${t`Sales Order Allocations`}: ${formatDecimal(record.allocated_to_sales_orders)}`}
</Text> </Text>
); );
} }
@@ -125,7 +125,7 @@ function partTableColumns(): TableColumn[] {
if (available != stock) { if (available != stock) {
extra.push( extra.push(
<Text key='available'> <Text key='available'>
{t`Available`}: {available} {t`Available`}: {formatDecimal(available)}
</Text> </Text>
); );
} }
@@ -133,7 +133,7 @@ function partTableColumns(): TableColumn[] {
if (record.external_stock > 0) { if (record.external_stock > 0) {
extra.push( extra.push(
<Text key='external'> <Text key='external'>
{t`External stock`}: {record.external_stock} {t`External stock`}: {formatDecimal(record.external_stock)}
</Text> </Text>
); );
} }

View File

@@ -17,6 +17,7 @@ import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
import { ModelType } from '@lib/enums/ModelType'; import { ModelType } from '@lib/enums/ModelType';
import { UserRoles } from '@lib/enums/Roles'; import { UserRoles } from '@lib/enums/Roles';
import { apiUrl } from '@lib/functions/Api'; import { apiUrl } from '@lib/functions/Api';
import { formatDecimal } from '@lib/functions/Formatting';
import type { TableFilter } from '@lib/types/Filters'; import type { TableFilter } from '@lib/types/Filters';
import type { TableColumn } from '@lib/types/Tables'; import type { TableColumn } from '@lib/types/Tables';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
@@ -198,7 +199,7 @@ export function PurchaseOrderLineItemTable({
return ( return (
<TableHoverCard <TableHoverCard
value={record.quantity} value={formatDecimal(record.quantity)}
extra={extra} extra={extra}
title={t`Quantity`} title={t`Quantity`}
/> />

View File

@@ -12,6 +12,7 @@ import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
import { ModelType } from '@lib/enums/ModelType'; import { ModelType } from '@lib/enums/ModelType';
import { UserRoles } from '@lib/enums/Roles'; import { UserRoles } from '@lib/enums/Roles';
import { apiUrl } from '@lib/functions/Api'; import { apiUrl } from '@lib/functions/Api';
import { formatDecimal } from '@lib/index';
import type { TableFilter } from '@lib/types/Filters'; import type { TableFilter } from '@lib/types/Filters';
import type { TableColumn } from '@lib/types/Tables'; import type { TableColumn } from '@lib/types/Tables';
import { useSupplierPartFields } from '../../forms/CompanyForms'; import { useSupplierPartFields } from '../../forms/CompanyForms';
@@ -91,7 +92,8 @@ export function SupplierPartTable({
}), }),
{ {
accessor: 'in_stock', accessor: 'in_stock',
sortable: true sortable: true,
render: (record: any) => formatDecimal(record.in_stock)
}, },
{ {
accessor: 'packaging', accessor: 'packaging',

View File

@@ -29,7 +29,7 @@ import type { TableFilter } from '@lib/types/Filters';
import type { TableColumn } from '@lib/types/Tables'; import type { TableColumn } from '@lib/types/Tables';
import { RenderPart } from '../../components/render/Part'; import { RenderPart } from '../../components/render/Part';
import OrderPartsWizard from '../../components/wizards/OrderPartsWizard'; import OrderPartsWizard from '../../components/wizards/OrderPartsWizard';
import { formatCurrency } from '../../defaults/formatters'; import { formatCurrency, formatDecimal } from '../../defaults/formatters';
import { useBuildOrderFields } from '../../forms/BuildForms'; import { useBuildOrderFields } from '../../forms/BuildForms';
import { import {
useAllocateToSalesOrderForm, useAllocateToSalesOrderForm,
@@ -104,7 +104,8 @@ export default function SalesOrderLineItemTable({
}, },
{ {
accessor: 'quantity', accessor: 'quantity',
sortable: true sortable: true,
render: (record: any) => formatDecimal(record.quantity)
}, },
{ {
accessor: 'sale_price', accessor: 'sale_price',

View File

@@ -11,7 +11,11 @@ import { apiUrl } from '@lib/functions/Api';
import type { TableFilter } from '@lib/types/Filters'; import type { TableFilter } from '@lib/types/Filters';
import type { TableColumn } from '@lib/types/Tables'; import type { TableColumn } from '@lib/types/Tables';
import OrderPartsWizard from '../../components/wizards/OrderPartsWizard'; import OrderPartsWizard from '../../components/wizards/OrderPartsWizard';
import { formatCurrency, formatPriceRange } from '../../defaults/formatters'; import {
formatCurrency,
formatDecimal,
formatPriceRange
} from '../../defaults/formatters';
import { import {
type StockOperationProps, type StockOperationProps,
useStockFields useStockFields
@@ -84,7 +88,7 @@ function stockItemTableColumns({
const quantity = record?.quantity ?? 0; const quantity = record?.quantity ?? 0;
const allocated = record?.allocated ?? 0; const allocated = record?.allocated ?? 0;
const available = quantity - allocated; const available = quantity - allocated;
let text = quantity; let text = formatDecimal(quantity);
const part = record?.part_detail ?? {}; const part = record?.part_detail ?? {};
const extra: ReactNode[] = []; const extra: ReactNode[] = [];
let color = undefined; let color = undefined;
@@ -175,7 +179,7 @@ function stockItemTableColumns({
if (available > 0) { if (available > 0) {
extra.push( extra.push(
<Text key='available' size='sm' c='orange'> <Text key='available' size='sm' c='orange'>
{`${t`Available`}: ${available}`} {`${t`Available`}: ${formatDecimal(available)}`}
</Text> </Text>
); );
} else { } else {

View File

@@ -6,6 +6,7 @@ import { useNavigate } from 'react-router-dom';
import { ApiEndpoints } from '@lib/enums/ApiEndpoints'; import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
import { ModelType } from '@lib/enums/ModelType'; import { ModelType } from '@lib/enums/ModelType';
import { apiUrl } from '@lib/functions/Api'; import { apiUrl } from '@lib/functions/Api';
import { formatDecimal } from '@lib/functions/Formatting';
import type { TableFilter } from '@lib/types/Filters'; import type { TableFilter } from '@lib/types/Filters';
import type { TableColumn } from '@lib/types/Tables'; import type { TableColumn } from '@lib/types/Tables';
import { RenderBuildOrder } from '../../components/render/Build'; import { RenderBuildOrder } from '../../components/render/Build';
@@ -70,7 +71,7 @@ export function StockTrackingTable({ itemId }: Readonly<{ itemId: number }>) {
{ {
label: t`Quantity`, label: t`Quantity`,
key: 'quantity', key: 'quantity',
details: deltas.quantity details: formatDecimal(deltas.quantity)
}, },
{ {
label: t`Added`, label: t`Added`,

View File

@@ -456,5 +456,5 @@ test('Build Order - BOM Quantity', async ({ browser }) => {
.getByRole('cell', { name: 'Thumbnail R_10K_0805_1%' }) .getByRole('cell', { name: 'Thumbnail R_10K_0805_1%' })
.locator('div'); .locator('div');
const row2 = await getRowFromCell(line); const row2 = await getRowFromCell(line);
await row2.getByText('1175').waitFor(); await row2.getByText('1,175').first().waitFor();
}); });

View File

@@ -1291,6 +1291,7 @@ def test(
help={ help={
'dev': 'Set up development environment at the end', 'dev': 'Set up development environment at the end',
'validate_files': 'Validate media files are correctly copied', 'validate_files': 'Validate media files are correctly copied',
'use_ssh': 'Use SSH protocol for cloning the demo dataset (requires SSH key)',
} }
) )
def setup_test( def setup_test(
@@ -1298,6 +1299,7 @@ def setup_test(
ignore_update=False, ignore_update=False,
dev=False, dev=False,
validate_files=False, validate_files=False,
use_ssh=False,
path='inventree-demo-dataset', path='inventree-demo-dataset',
): ):
"""Setup a testing environment.""" """Setup a testing environment."""
@@ -1313,12 +1315,15 @@ def setup_test(
info('Removing old data ...') info('Removing old data ...')
run(c, f'rm {template_dir} -r') run(c, f'rm {template_dir} -r')
URL = 'https://github.com/inventree/demo-dataset'
if use_ssh:
# Use SSH protocol for cloning the demo dataset
URL = 'git@github.com:inventree/demo-dataset.git'
# Get test data # Get test data
info('Cloning demo dataset ...') info('Cloning demo dataset ...')
run( run(c, f'git clone {URL} {template_dir} -v --depth=1')
c,
f'git clone https://github.com/inventree/demo-dataset {template_dir} -v --depth=1',
)
# Make sure migrations are done - might have just deleted sqlite database # Make sure migrations are done - might have just deleted sqlite database
if not ignore_update: if not ignore_update:
@@ -1590,6 +1595,21 @@ def frontend_server(c):
yarn(c, 'yarn run dev --host') yarn(c, 'yarn run dev --host')
@task
def frontend_test(c, host: str = '0.0.0.0'):
"""Start the playwright test runner for the frontend code."""
info('Starting frontend test runner')
frontend_path = local_dir().joinpath('src', 'frontend').resolve()
cmd = 'npx playwright test --ui'
if host:
cmd += f' --ui-host={host}'
run(c, cmd, path=frontend_path)
@task( @task(
help={ help={
'ref': 'git ref, default: current git ref', 'ref': 'git ref, default: current git ref',
@@ -1874,6 +1894,7 @@ development = Collection(
delete_data, delete_data,
docs_server, docs_server,
frontend_server, frontend_server,
frontend_test,
gunicorn, gunicorn,
import_fixtures, import_fixtures,
schema, schema,