diff --git a/src/backend/InvenTree/InvenTree/serializers.py b/src/backend/InvenTree/InvenTree/serializers.py
index 2a5f730620..6e2a993228 100644
--- a/src/backend/InvenTree/InvenTree/serializers.py
+++ b/src/backend/InvenTree/InvenTree/serializers.py
@@ -46,6 +46,12 @@ class InvenTreeMoneySerializer(MoneyField):
super().__init__(*args, **kwargs)
+ def to_representation(self, obj):
+ """Convert the Money object to a decimal value for representation."""
+ val = super().to_representation(obj)
+
+ return float(val)
+
def get_value(self, data):
"""Test that the returned amount is a valid Decimal."""
amount = super(DecimalField, self).get_value(data)
@@ -74,7 +80,11 @@ class InvenTreeMoneySerializer(MoneyField):
):
return Money(amount, currency)
- return amount
+ try:
+ fp_amount = float(amount)
+ return fp_amount
+ except Exception:
+ return amount
class InvenTreeCurrencySerializer(serializers.ChoiceField):
diff --git a/src/backend/InvenTree/part/models.py b/src/backend/InvenTree/part/models.py
index 8b6ae18e1b..dd5e418ef4 100644
--- a/src/backend/InvenTree/part/models.py
+++ b/src/backend/InvenTree/part/models.py
@@ -62,7 +62,6 @@ from order import models as OrderModels
from order.status_codes import (
PurchaseOrderStatus,
PurchaseOrderStatusGroups,
- SalesOrderStatus,
SalesOrderStatusGroups,
)
from stock import models as StockModels
@@ -3134,9 +3133,12 @@ class PartPricing(common.models.MetaMixin):
min_sell_history = None
max_sell_history = None
+ # Calculate sale price history too
+ parts = self.part.get_descendants(include_self=True)
+
# Find all line items for shipped sales orders which reference this part
line_items = OrderModels.SalesOrderLineItem.objects.filter(
- order__status=SalesOrderStatus.SHIPPED, part=self.part
+ order__status__in=SalesOrderStatusGroups.COMPLETE, part__in=parts
)
# Exclude line items which do not have associated pricing data
diff --git a/src/backend/InvenTree/stock/test_api.py b/src/backend/InvenTree/stock/test_api.py
index 9be1e753ae..d856b72b5d 100644
--- a/src/backend/InvenTree/stock/test_api.py
+++ b/src/backend/InvenTree/stock/test_api.py
@@ -1487,13 +1487,13 @@ class StockItemTest(StockAPITestCase):
data = self.get(url, expected_code=200).data
# Check fixture values
- self.assertEqual(data['purchase_price'], '123.000000')
+ self.assertAlmostEqual(data['purchase_price'], 123, 3)
self.assertEqual(data['purchase_price_currency'], 'AUD')
# Update just the amount
data = self.patch(url, {'purchase_price': 456}, expected_code=200).data
- self.assertEqual(data['purchase_price'], '456.000000')
+ self.assertAlmostEqual(data['purchase_price'], 456, 3)
self.assertEqual(data['purchase_price_currency'], 'AUD')
# Update the currency
diff --git a/src/frontend/src/pages/part/pricing/BomPricingPanel.tsx b/src/frontend/src/pages/part/pricing/BomPricingPanel.tsx
index 4127c4f033..6d1400cc06 100644
--- a/src/frontend/src/pages/part/pricing/BomPricingPanel.tsx
+++ b/src/frontend/src/pages/part/pricing/BomPricingPanel.tsx
@@ -68,7 +68,7 @@ function BomPieChart({
return {
// Note: Replace '.' in name to avoid issues with tooltip
name: entry?.name?.replace('.', '') ?? '',
- value: entry?.total_price_max,
+ value: Number.parseFloat(entry?.total_price_max),
color: `${CHART_COLORS[index % CHART_COLORS.length]}.5`
};
}) ?? []
diff --git a/src/frontend/src/pages/part/pricing/PricingOverviewPanel.tsx b/src/frontend/src/pages/part/pricing/PricingOverviewPanel.tsx
index 720b639db3..1a1c7d2b48 100644
--- a/src/frontend/src/pages/part/pricing/PricingOverviewPanel.tsx
+++ b/src/frontend/src/pages/part/pricing/PricingOverviewPanel.tsx
@@ -26,6 +26,7 @@ import { type ReactNode, useCallback, useMemo } from 'react';
import { api } from '../../../App';
import { tooltipFormatter } from '../../../components/charts/tooltipFormatter';
+import type { ApiFormFieldSet } from '../../../components/forms/fields/ApiFormField';
import {
EditItemAction,
OptionsActionDropdown
@@ -35,12 +36,14 @@ import { ApiEndpoints } from '../../../enums/ApiEndpoints';
import { InvenTreeIcon } from '../../../functions/icons';
import { useEditApiFormModal } from '../../../hooks/UseForm';
import { apiUrl } from '../../../states/ApiState';
+import { useGlobalSettingsState } from '../../../states/SettingsState';
import { panelOptions } from '../PartPricingPanel';
interface PricingOverviewEntry {
icon: ReactNode;
name: panelOptions;
title: string;
+ valid: boolean;
min_value: number | null | undefined;
max_value: number | null | undefined;
visible?: boolean;
@@ -58,6 +61,8 @@ export default function PricingOverviewPanel({
pricingQuery: UseQueryResult;
doNavigation: (panel: panelOptions) => void;
}>): ReactNode {
+ const globalSettings = useGlobalSettingsState();
+
const refreshPricing = useCallback(() => {
const url = apiUrl(ApiEndpoints.part_pricing, part.pk);
@@ -99,19 +104,29 @@ export default function PricingOverviewPanel({
});
}, [part]);
- const editPricing = useEditApiFormModal({
- title: t`Edit Pricing`,
- url: apiUrl(ApiEndpoints.part_pricing, part.pk),
- fields: {
+ const pricingFields: ApiFormFieldSet = useMemo(() => {
+ return {
override_min: {},
- override_min_currency: {},
+ override_min_currency: {
+ default:
+ globalSettings.getSetting('INVENTREE_DEFAULT_CURRENCY') ?? 'USD'
+ },
override_max: {},
- override_max_currency: {},
+ override_max_currency: {
+ default:
+ globalSettings.getSetting('INVENTREE_DEFAULT_CURRENCY') ?? 'USD'
+ },
update: {
hidden: true,
value: true
}
- },
+ };
+ }, [globalSettings]);
+
+ const editPricing = useEditApiFormModal({
+ title: t`Edit Pricing`,
+ url: apiUrl(ApiEndpoints.part_pricing, part.pk),
+ fields: pricingFields,
onFormSuccess: () => {
pricingQuery.refetch();
}
@@ -168,71 +183,89 @@ export default function PricingOverviewPanel({
const overviewData: PricingOverviewEntry[] = useMemo(() => {
return [
- {
- name: panelOptions.internal,
- title: t`Internal Pricing`,
- icon: ,
- min_value: pricing?.internal_cost_min,
- max_value: pricing?.internal_cost_max
- },
- {
- name: panelOptions.bom,
- title: t`BOM Pricing`,
- icon: ,
- min_value: pricing?.bom_cost_min,
- max_value: pricing?.bom_cost_max
- },
- {
- name: panelOptions.purchase,
- title: t`Purchase Pricing`,
- icon: ,
- min_value: pricing?.purchase_cost_min,
- max_value: pricing?.purchase_cost_max
- },
- {
- name: panelOptions.supplier,
- title: t`Supplier Pricing`,
- icon: ,
- min_value: pricing?.supplier_price_min,
- max_value: pricing?.supplier_price_max
- },
- {
- name: panelOptions.variant,
- title: t`Variant Pricing`,
- icon: ,
- min_value: pricing?.variant_cost_min,
- max_value: pricing?.variant_cost_max
- },
- {
- name: panelOptions.sale_pricing,
- title: t`Sale Pricing`,
- icon: ,
- min_value: pricing?.sale_price_min,
- max_value: pricing?.sale_price_max
- },
- {
- name: panelOptions.sale_history,
- title: t`Sale History`,
- icon: ,
- min_value: pricing?.sale_history_min,
- max_value: pricing?.sale_history_max
- },
{
name: panelOptions.override,
title: t`Override Pricing`,
icon: ,
- min_value: pricing?.override_min,
- max_value: pricing?.override_max
+ min_value: Number.parseFloat(pricing?.override_min),
+ max_value: Number.parseFloat(pricing?.override_max),
+ valid: pricing?.override_min != null && pricing?.override_max != null
},
{
name: panelOptions.overall,
title: t`Overall Pricing`,
icon: ,
- min_value: pricing?.overall_min,
- max_value: pricing?.overall_max
+ min_value: Number.parseFloat(pricing?.overall_min),
+ max_value: Number.parseFloat(pricing?.overall_max),
+ valid: pricing?.overall_min != null && pricing?.overall_max != null
+ },
+ {
+ name: panelOptions.internal,
+ title: t`Internal Pricing`,
+ icon: ,
+ min_value: Number.parseFloat(pricing?.internal_cost_min),
+ max_value: Number.parseFloat(pricing?.internal_cost_max),
+ valid:
+ pricing?.internal_cost_min != null &&
+ pricing?.internal_cost_max != null
+ },
+ {
+ name: panelOptions.bom,
+ title: t`BOM Pricing`,
+ icon: ,
+ min_value: Number.parseFloat(pricing?.bom_cost_min),
+ max_value: Number.parseFloat(pricing?.bom_cost_max),
+ valid: pricing?.bom_cost_min != null && pricing?.bom_cost_max != null
+ },
+ {
+ name: panelOptions.purchase,
+ title: t`Purchase Pricing`,
+ icon: ,
+ min_value: Number.parseFloat(pricing?.purchase_cost_min),
+ max_value: Number.parseFloat(pricing?.purchase_cost_max),
+ valid:
+ pricing?.purchase_cost_min != null &&
+ pricing?.purchase_cost_max != null
+ },
+ {
+ name: panelOptions.supplier,
+ title: t`Supplier Pricing`,
+ icon: ,
+ min_value: Number.parseFloat(pricing?.supplier_price_min),
+ max_value: Number.parseFloat(pricing?.supplier_price_max),
+ valid:
+ pricing?.supplier_price_min != null &&
+ pricing?.supplier_price_max != null
+ },
+ {
+ name: panelOptions.variant,
+ title: t`Variant Pricing`,
+ icon: ,
+ min_value: Number.parseFloat(pricing?.variant_cost_min),
+ max_value: Number.parseFloat(pricing?.variant_cost_max),
+ valid:
+ pricing?.variant_cost_min != null && pricing?.variant_cost_max != null
+ },
+ {
+ name: panelOptions.sale_pricing,
+ title: t`Sale Pricing`,
+ icon: ,
+ min_value: Number.parseFloat(pricing?.sale_price_min),
+ max_value: Number.parseFloat(pricing?.sale_price_max),
+ valid:
+ pricing?.sale_price_min != null && pricing?.sale_price_max != null
+ },
+ {
+ name: panelOptions.sale_history,
+ title: t`Sale History`,
+ icon: ,
+ min_value: Number.parseFloat(pricing?.sale_history_min),
+ max_value: Number.parseFloat(pricing?.sale_history_max),
+ valid:
+ pricing?.sale_history_min != null && pricing?.sale_history_max != null
}
].filter((entry) => {
- return !(entry.min_value == null || entry.max_value == null);
+ return entry.valid;
});
}, [part, pricing]);
diff --git a/src/frontend/src/pages/part/pricing/PurchaseHistoryPanel.tsx b/src/frontend/src/pages/part/pricing/PurchaseHistoryPanel.tsx
index ce3ea98e33..4bdbc79c82 100644
--- a/src/frontend/src/pages/part/pricing/PurchaseHistoryPanel.tsx
+++ b/src/frontend/src/pages/part/pricing/PurchaseHistoryPanel.tsx
@@ -21,7 +21,7 @@ export default function PurchaseHistoryPanel({
const calculateUnitPrice = useCallback((record: any) => {
const pack_quantity =
record?.supplier_part_detail?.pack_quantity_native ?? 1;
- const unit_price = record.purchase_price / pack_quantity;
+ const unit_price = Number.parseFloat(record.purchase_price) / pack_quantity;
return unit_price;
}, []);
@@ -95,7 +95,7 @@ export default function PurchaseHistoryPanel({
return table.records.map((record: any) => {
return {
quantity: record.quantity,
- purchase_price: record.purchase_price,
+ purchase_price: Number.parseFloat(record.purchase_price),
unit_price: calculateUnitPrice(record),
name: record.order_detail.reference
};
diff --git a/src/frontend/src/pages/part/pricing/SaleHistoryPanel.tsx b/src/frontend/src/pages/part/pricing/SaleHistoryPanel.tsx
index 7bbee81ed7..afda0ba3aa 100644
--- a/src/frontend/src/pages/part/pricing/SaleHistoryPanel.tsx
+++ b/src/frontend/src/pages/part/pricing/SaleHistoryPanel.tsx
@@ -57,7 +57,7 @@ export default function SaleHistoryPanel({
return table.records.map((record: any) => {
return {
name: record.order_detail.reference,
- sale_price: record.sale_price
+ sale_price: Number.parseFloat(record.sale_price)
};
});
}, [table.records]);
diff --git a/src/frontend/src/pages/part/pricing/SupplierPricingPanel.tsx b/src/frontend/src/pages/part/pricing/SupplierPricingPanel.tsx
index a4ad956816..fa88c49b85 100644
--- a/src/frontend/src/pages/part/pricing/SupplierPricingPanel.tsx
+++ b/src/frontend/src/pages/part/pricing/SupplierPricingPanel.tsx
@@ -36,7 +36,7 @@ export default function SupplierPricingPanel({
table.records?.map((record: any) => {
return {
quantity: record.quantity,
- supplier_price: record.price,
+ supplier_price: Number.parseFloat(record.price),
unit_price: calculateSupplierPartUnitPrice(record),
name: record.part_detail?.SKU
};
diff --git a/src/frontend/src/pages/part/pricing/VariantPricingPanel.tsx b/src/frontend/src/pages/part/pricing/VariantPricingPanel.tsx
index 224736929d..b6a105fed1 100644
--- a/src/frontend/src/pages/part/pricing/VariantPricingPanel.tsx
+++ b/src/frontend/src/pages/part/pricing/VariantPricingPanel.tsx
@@ -63,8 +63,10 @@ export default function VariantPricingPanel({
return {
part: variant,
name: variant.full_name,
- pmin: variant.pricing_min ?? variant.pricing_max ?? 0,
- pmax: variant.pricing_max ?? variant.pricing_min ?? 0
+ pmin: Number.parseFloat(
+ variant.pricing_min ?? variant.pricing_max ?? 0
+ ),
+ pmax: Number.parseFloat(variant.pricing_max ?? variant.pricing_min ?? 0)
};
});
diff --git a/src/frontend/src/tables/purchasing/SupplierPriceBreakTable.tsx b/src/frontend/src/tables/purchasing/SupplierPriceBreakTable.tsx
index b0085a04a1..e3f195bb0f 100644
--- a/src/frontend/src/tables/purchasing/SupplierPriceBreakTable.tsx
+++ b/src/frontend/src/tables/purchasing/SupplierPriceBreakTable.tsx
@@ -24,7 +24,7 @@ import { type RowAction, RowDeleteAction, RowEditAction } from '../RowActions';
export function calculateSupplierPartUnitPrice(record: any) {
const pack_quantity = record?.part_detail?.pack_quantity_native ?? 1;
- const unit_price = record.price / pack_quantity;
+ const unit_price = Number.parseFloat(record.price) / pack_quantity;
return unit_price;
}