diff --git a/src/frontend/src/pages/company/SupplierPartDetail.tsx b/src/frontend/src/pages/company/SupplierPartDetail.tsx index f9e829a969..8064126b48 100644 --- a/src/frontend/src/pages/company/SupplierPartDetail.tsx +++ b/src/frontend/src/pages/company/SupplierPartDetail.tsx @@ -184,14 +184,14 @@ export default function SupplierPartDetail() { const tr: DetailsField[] = [ { - type: 'string', + type: 'number', name: 'in_stock', label: t`In Stock`, copy: true, icon: 'stock' }, { - type: 'string', + type: 'number', name: 'on_order', label: t`On Order`, copy: true, diff --git a/src/frontend/src/tables/bom/BomTable.tsx b/src/frontend/src/tables/bom/BomTable.tsx index cbe870af06..bca5a6c8ca 100644 --- a/src/frontend/src/tables/bom/BomTable.tsx +++ b/src/frontend/src/tables/bom/BomTable.tsx @@ -288,13 +288,13 @@ export function BomTable({ available_stock <= 0 ? ( {t`No stock`} ) : ( - available_stock + `${formatDecimal(available_stock)}` ); if (record.external_stock > 0) { extra.push( - {t`External stock`}: {record.external_stock} + {t`External stock`}: {formatDecimal(record.external_stock)} ); } @@ -303,7 +303,7 @@ export function BomTable({ extra.push( {t`Includes substitute stock`}:{' '} - {record.available_substitute_stock} + {formatDecimal(record.available_substitute_stock)} ); } @@ -311,7 +311,8 @@ export function BomTable({ if (record.allow_variants && record.available_variant_stock > 0) { extra.push( - {t`Includes variant stock`}: {record.available_variant_stock} + {t`Includes variant stock`}:{' '} + {formatDecimal(record.available_variant_stock)} ); } @@ -363,7 +364,7 @@ export function BomTable({ fs={record.consumable && 'italic'} c={can_build <= 0 && !record.consumable ? 'red' : undefined} > - {can_build} + {formatDecimal(can_build)} ); diff --git a/src/frontend/src/tables/build/BuildAllocatedStockTable.tsx b/src/frontend/src/tables/build/BuildAllocatedStockTable.tsx index 18d1760b99..fc75257b44 100644 --- a/src/frontend/src/tables/build/BuildAllocatedStockTable.tsx +++ b/src/frontend/src/tables/build/BuildAllocatedStockTable.tsx @@ -10,6 +10,7 @@ import { ApiEndpoints } from '@lib/enums/ApiEndpoints'; import { ModelType } from '@lib/enums/ModelType'; import { UserRoles } from '@lib/enums/Roles'; import { apiUrl } from '@lib/functions/Api'; +import { formatDecimal } from '@lib/functions/Formatting'; import type { TableFilter } from '@lib/types/Filters'; import type { TableColumn } from '@lib/types/Tables'; import type { StockOperationProps } from '../../forms/StockForms'; @@ -133,13 +134,15 @@ export default function BuildAllocatedStockTable({ { accessor: 'available', title: t`Available Quantity`, - render: (record: any) => record?.stock_item_detail?.quantity + render: (record: any) => + formatDecimal(record?.stock_item_detail?.quantity) }, { accessor: 'quantity', title: t`Allocated Quantity`, sortable: true, - switchable: false + switchable: false, + render: (record: any) => formatDecimal(record?.quantity) }, LocationColumn({ accessor: 'location_detail', diff --git a/src/frontend/src/tables/build/BuildLineTable.tsx b/src/frontend/src/tables/build/BuildLineTable.tsx index a08972d6fc..f104f3e2a4 100644 --- a/src/frontend/src/tables/build/BuildLineTable.tsx +++ b/src/frontend/src/tables/build/BuildLineTable.tsx @@ -24,6 +24,7 @@ import { ApiEndpoints } from '@lib/enums/ApiEndpoints'; import { ModelType } from '@lib/enums/ModelType'; import { UserRoles } from '@lib/enums/Roles'; import { apiUrl } from '@lib/functions/Api'; +import { formatDecimal } from '@lib/functions/Formatting'; import type { TableFilter } from '@lib/types/Filters'; import type { TableColumn } from '@lib/types/Tables'; import OrderPartsWizard from '../../components/wizards/OrderPartsWizard'; @@ -248,7 +249,7 @@ export default function BuildLineTable({ if (record.in_production > 0) { extra.push( - {t`In production`}: {record.in_production} + {t`In production`}: {formatDecimal(record.in_production)} ); } @@ -257,7 +258,7 @@ export default function BuildLineTable({ if (record.on_order > 0) { extra.push( - {t`On order`}: {record.on_order} + {t`On order`}: {formatDecimal(record.on_order)} ); } @@ -266,7 +267,7 @@ export default function BuildLineTable({ if (record.external_stock > 0) { extra.push( - {t`External stock`}: {record.external_stock} + {t`External stock`}: {formatDecimal(record.external_stock)} ); } @@ -287,7 +288,7 @@ export default function BuildLineTable({ iconColor={sufficient ? 'blue' : 'orange'} value={ available > 0 ? ( - available + `${formatDecimal(available)}` ) : ( - {t`Setup Quantity`}: {record.bom_item_detail.setup_quantity} + {t`Setup Quantity`}:{' '} + {formatDecimal(record.bom_item_detail.setup_quantity)} ); } @@ -427,7 +429,7 @@ export default function BuildLineTable({ extra={extra} value={ - {record.requiredQuantity} + {formatDecimal(record.requiredQuantity)} {record?.part_detail?.units && ( [{record.part_detail.units}] )} diff --git a/src/frontend/src/tables/general/ExtraLineItemTable.tsx b/src/frontend/src/tables/general/ExtraLineItemTable.tsx index f6212d80c0..bb0c7b5b8c 100644 --- a/src/frontend/src/tables/general/ExtraLineItemTable.tsx +++ b/src/frontend/src/tables/general/ExtraLineItemTable.tsx @@ -12,7 +12,7 @@ import type { ApiEndpoints } from '@lib/enums/ApiEndpoints'; import type { UserRoles } from '@lib/enums/Roles'; import { apiUrl } from '@lib/functions/Api'; import type { TableColumn } from '@lib/types/Tables'; -import { formatCurrency } from '../../defaults/formatters'; +import { formatCurrency, formatDecimal } from '../../defaults/formatters'; import { extraLineItemFields } from '../../forms/CommonForms'; import { useCreateApiFormModal, @@ -49,7 +49,8 @@ export default function ExtraLineItemTable({ DescriptionColumn({}), { accessor: 'quantity', - switchable: false + switchable: false, + render: (record: any) => formatDecimal(record.quantity) }, { accessor: 'price', diff --git a/src/frontend/src/tables/part/PartTable.tsx b/src/frontend/src/tables/part/PartTable.tsx index 8a368f0d7e..1794c9fb4e 100644 --- a/src/frontend/src/tables/part/PartTable.tsx +++ b/src/frontend/src/tables/part/PartTable.tsx @@ -14,7 +14,7 @@ import type { TableColumn } from '@lib/types/Tables'; import type { InvenTreeTableProps } from '@lib/types/Tables'; import { ActionDropdown } from '../../components/items/ActionDropdown'; import OrderPartsWizard from '../../components/wizards/OrderPartsWizard'; -import { formatPriceRange } from '../../defaults/formatters'; +import { formatDecimal, formatPriceRange } from '../../defaults/formatters'; import { usePartFields } from '../../forms/PartForms'; import { InvenTreeIcon } from '../../functions/icons'; import { @@ -80,14 +80,14 @@ function partTableColumns(): TableColumn[] { const available = Math.max(0, stock - allocated); const min_stock = record?.minimum_stock ?? 0; - let text = String(stock); + let text = String(formatDecimal(stock)); let color: string | undefined = undefined; if (min_stock > stock) { extra.push( - {`${t`Minimum stock`}: ${min_stock}`} + {`${t`Minimum stock`}: ${formatDecimal(min_stock)}`} ); @@ -96,20 +96,20 @@ function partTableColumns(): TableColumn[] { if (record.ordering > 0) { extra.push( - {`${t`On Order`}: ${record.ordering}`} + {`${t`On Order`}: ${formatDecimal(record.ordering)}`} ); } if (record.building) { extra.push( - {`${t`Building`}: ${record.building}`} + {`${t`Building`}: ${formatDecimal(record.building)}`} ); } if (record.allocated_to_build_orders > 0) { extra.push( - {`${t`Build Order Allocations`}: ${record.allocated_to_build_orders}`} + {`${t`Build Order Allocations`}: ${formatDecimal(record.allocated_to_build_orders)}`} ); } @@ -117,7 +117,7 @@ function partTableColumns(): TableColumn[] { if (record.allocated_to_sales_orders > 0) { extra.push( - {`${t`Sales Order Allocations`}: ${record.allocated_to_sales_orders}`} + {`${t`Sales Order Allocations`}: ${formatDecimal(record.allocated_to_sales_orders)}`} ); } @@ -125,7 +125,7 @@ function partTableColumns(): TableColumn[] { if (available != stock) { extra.push( - {t`Available`}: {available} + {t`Available`}: {formatDecimal(available)} ); } @@ -133,7 +133,7 @@ function partTableColumns(): TableColumn[] { if (record.external_stock > 0) { extra.push( - {t`External stock`}: {record.external_stock} + {t`External stock`}: {formatDecimal(record.external_stock)} ); } diff --git a/src/frontend/src/tables/purchasing/PurchaseOrderLineItemTable.tsx b/src/frontend/src/tables/purchasing/PurchaseOrderLineItemTable.tsx index 530059fa4c..54c5a9d6fc 100644 --- a/src/frontend/src/tables/purchasing/PurchaseOrderLineItemTable.tsx +++ b/src/frontend/src/tables/purchasing/PurchaseOrderLineItemTable.tsx @@ -17,6 +17,7 @@ import { ApiEndpoints } from '@lib/enums/ApiEndpoints'; import { ModelType } from '@lib/enums/ModelType'; import { UserRoles } from '@lib/enums/Roles'; import { apiUrl } from '@lib/functions/Api'; +import { formatDecimal } from '@lib/functions/Formatting'; import type { TableFilter } from '@lib/types/Filters'; import type { TableColumn } from '@lib/types/Tables'; import { useNavigate } from 'react-router-dom'; @@ -198,7 +199,7 @@ export function PurchaseOrderLineItemTable({ return ( diff --git a/src/frontend/src/tables/purchasing/SupplierPartTable.tsx b/src/frontend/src/tables/purchasing/SupplierPartTable.tsx index 650d3428a0..3cebdb000d 100644 --- a/src/frontend/src/tables/purchasing/SupplierPartTable.tsx +++ b/src/frontend/src/tables/purchasing/SupplierPartTable.tsx @@ -12,6 +12,7 @@ import { ApiEndpoints } from '@lib/enums/ApiEndpoints'; import { ModelType } from '@lib/enums/ModelType'; import { UserRoles } from '@lib/enums/Roles'; import { apiUrl } from '@lib/functions/Api'; +import { formatDecimal } from '@lib/index'; import type { TableFilter } from '@lib/types/Filters'; import type { TableColumn } from '@lib/types/Tables'; import { useSupplierPartFields } from '../../forms/CompanyForms'; @@ -91,7 +92,8 @@ export function SupplierPartTable({ }), { accessor: 'in_stock', - sortable: true + sortable: true, + render: (record: any) => formatDecimal(record.in_stock) }, { accessor: 'packaging', diff --git a/src/frontend/src/tables/sales/SalesOrderLineItemTable.tsx b/src/frontend/src/tables/sales/SalesOrderLineItemTable.tsx index 5e801557bc..e2eb0bca0d 100644 --- a/src/frontend/src/tables/sales/SalesOrderLineItemTable.tsx +++ b/src/frontend/src/tables/sales/SalesOrderLineItemTable.tsx @@ -29,7 +29,7 @@ import type { TableFilter } from '@lib/types/Filters'; import type { TableColumn } from '@lib/types/Tables'; import { RenderPart } from '../../components/render/Part'; import OrderPartsWizard from '../../components/wizards/OrderPartsWizard'; -import { formatCurrency } from '../../defaults/formatters'; +import { formatCurrency, formatDecimal } from '../../defaults/formatters'; import { useBuildOrderFields } from '../../forms/BuildForms'; import { useAllocateToSalesOrderForm, @@ -104,7 +104,8 @@ export default function SalesOrderLineItemTable({ }, { accessor: 'quantity', - sortable: true + sortable: true, + render: (record: any) => formatDecimal(record.quantity) }, { accessor: 'sale_price', diff --git a/src/frontend/src/tables/stock/StockItemTable.tsx b/src/frontend/src/tables/stock/StockItemTable.tsx index f784ca9010..7664386a4a 100644 --- a/src/frontend/src/tables/stock/StockItemTable.tsx +++ b/src/frontend/src/tables/stock/StockItemTable.tsx @@ -11,7 +11,11 @@ import { apiUrl } from '@lib/functions/Api'; import type { TableFilter } from '@lib/types/Filters'; import type { TableColumn } from '@lib/types/Tables'; import OrderPartsWizard from '../../components/wizards/OrderPartsWizard'; -import { formatCurrency, formatPriceRange } from '../../defaults/formatters'; +import { + formatCurrency, + formatDecimal, + formatPriceRange +} from '../../defaults/formatters'; import { type StockOperationProps, useStockFields @@ -84,7 +88,7 @@ function stockItemTableColumns({ const quantity = record?.quantity ?? 0; const allocated = record?.allocated ?? 0; const available = quantity - allocated; - let text = quantity; + let text = formatDecimal(quantity); const part = record?.part_detail ?? {}; const extra: ReactNode[] = []; let color = undefined; @@ -175,7 +179,7 @@ function stockItemTableColumns({ if (available > 0) { extra.push( - {`${t`Available`}: ${available}`} + {`${t`Available`}: ${formatDecimal(available)}`} ); } else { diff --git a/src/frontend/src/tables/stock/StockTrackingTable.tsx b/src/frontend/src/tables/stock/StockTrackingTable.tsx index da4813f64a..5e3acbe7d7 100644 --- a/src/frontend/src/tables/stock/StockTrackingTable.tsx +++ b/src/frontend/src/tables/stock/StockTrackingTable.tsx @@ -6,6 +6,7 @@ import { useNavigate } from 'react-router-dom'; import { ApiEndpoints } from '@lib/enums/ApiEndpoints'; import { ModelType } from '@lib/enums/ModelType'; import { apiUrl } from '@lib/functions/Api'; +import { formatDecimal } from '@lib/functions/Formatting'; import type { TableFilter } from '@lib/types/Filters'; import type { TableColumn } from '@lib/types/Tables'; import { RenderBuildOrder } from '../../components/render/Build'; @@ -70,7 +71,7 @@ export function StockTrackingTable({ itemId }: Readonly<{ itemId: number }>) { { label: t`Quantity`, key: 'quantity', - details: deltas.quantity + details: formatDecimal(deltas.quantity) }, { label: t`Added`, diff --git a/src/frontend/tests/pages/pui_build.spec.ts b/src/frontend/tests/pages/pui_build.spec.ts index 6ad9084eaf..c4c8fdf922 100644 --- a/src/frontend/tests/pages/pui_build.spec.ts +++ b/src/frontend/tests/pages/pui_build.spec.ts @@ -456,5 +456,5 @@ test('Build Order - BOM Quantity', async ({ browser }) => { .getByRole('cell', { name: 'Thumbnail R_10K_0805_1%' }) .locator('div'); const row2 = await getRowFromCell(line); - await row2.getByText('1175').waitFor(); + await row2.getByText('1,175').first().waitFor(); }); diff --git a/tasks.py b/tasks.py index 3770d67722..415b00f096 100644 --- a/tasks.py +++ b/tasks.py @@ -1291,6 +1291,7 @@ def test( help={ 'dev': 'Set up development environment at the end', 'validate_files': 'Validate media files are correctly copied', + 'use_ssh': 'Use SSH protocol for cloning the demo dataset (requires SSH key)', } ) def setup_test( @@ -1298,6 +1299,7 @@ def setup_test( ignore_update=False, dev=False, validate_files=False, + use_ssh=False, path='inventree-demo-dataset', ): """Setup a testing environment.""" @@ -1313,12 +1315,15 @@ def setup_test( info('Removing old data ...') 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 info('Cloning demo dataset ...') - run( - c, - f'git clone https://github.com/inventree/demo-dataset {template_dir} -v --depth=1', - ) + run(c, f'git clone {URL} {template_dir} -v --depth=1') # Make sure migrations are done - might have just deleted sqlite database if not ignore_update: @@ -1590,6 +1595,21 @@ def frontend_server(c): 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( help={ 'ref': 'git ref, default: current git ref', @@ -1874,6 +1894,7 @@ development = Collection( delete_data, docs_server, frontend_server, + frontend_test, gunicorn, import_fixtures, schema,