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,