diff --git a/src/components/Dashboard/Finance/FinanceOverview.jsx b/src/components/Dashboard/Finance/FinanceOverview.jsx index 410f677d..8fa6e20c 100644 --- a/src/components/Dashboard/Finance/FinanceOverview.jsx +++ b/src/components/Dashboard/Finance/FinanceOverview.jsx @@ -1,9 +1,22 @@ import DashboardOverviewPage from '../common/DashboardOverviewPage' import StatsDisplay from '../common/StatsDisplay' +import ModelHistoryDisplay from '../common/ModelHistoryDisplay' +import ObjectTable from '../common/ObjectTable' const collapseDefaults = { invoiceStats: true, - paymentStats: true + paymentStats: true, + taxRecordStats: true, + invoiceCountHistory: true, + invoiceAmountHistory: true, + paymentCountHistory: true, + paymentAmountHistory: true, + taxRecordCountHistory: true, + taxRecordAmountHistory: true, + overdueInvoices: true, + partiallyPaidInvoices: true, + dueInvoices: true, + authorisedPayments: true } const sections = [ @@ -18,6 +31,135 @@ const sections = [ title: 'Payment Statistics', className: 'no-t-padding-collapse', content: + }, + { + key: 'taxRecordStats', + title: 'Tax Record Statistics', + className: 'no-t-padding-collapse', + content: + }, + { + key: 'invoiceHistoryRow', + type: 'row', + columns: [ + [ + { + key: 'invoiceCountHistory', + title: 'Invoice Count History', + content: ( + + ) + } + ], + [ + { + key: 'invoiceAmountHistory', + title: 'Invoice Amount History', + content: ( + + ) + } + ] + ] + }, + { + key: 'paymentHistoryRow', + type: 'row', + columns: [ + [ + { + key: 'paymentCountHistory', + title: 'Payment Count History', + content: ( + + ) + } + ], + [ + { + key: 'paymentAmountHistory', + title: 'Payment Amount History', + content: ( + + ) + } + ] + ] + }, + { + key: 'taxRecordHistoryRow', + type: 'row', + columns: [ + [ + { + key: 'taxRecordCountHistory', + title: 'Tax Record Count History', + content: ( + + ) + } + ], + [ + { + key: 'taxRecordAmountHistory', + title: 'Tax Record Amount History', + content: ( + + ) + } + ] + ] + }, + { + key: 'overdueAndPartiallyPaidInvoicesRow', + type: 'row', + columns: [ + [ + { + key: 'overdueInvoices', + title: 'Overdue Invoices', + content: ( + + ) + } + ], + [ + { + key: 'partiallyPaidInvoices', + title: 'Partially Paid Invoices', + content: ( + + ) + } + ] + ] + }, + { + key: 'dueInvoicesAndAuthorisedPaymentsRow', + type: 'row', + columns: [ + [ + { + key: 'dueInvoices', + title: 'Due Invoices', + content: ( + + ) + } + ], + [ + { + key: 'authorisedPayments', + title: 'Authorised Payments', + content: ( + + ) + } + ] + ] } ] diff --git a/src/components/Dashboard/Inventory/InventoryOverview.jsx b/src/components/Dashboard/Inventory/InventoryOverview.jsx index 04958c8f..ddde526b 100644 --- a/src/components/Dashboard/Inventory/InventoryOverview.jsx +++ b/src/components/Dashboard/Inventory/InventoryOverview.jsx @@ -9,15 +9,28 @@ const collapseDefaults = { productStockStats: true, purchaseOrderStats: true, stockEventStats: true, - partStockHistory: true, + stockTransferStats: true, + stockLocationStats: true, + shipmentStats: true, + partStockCountHistory: true, + partStockValueHistory: true, newPartStocks: true, - filamentStockHistory: true, + filamentStockCountHistory: true, + filamentStockValueHistory: true, unconsumedFilamentStocks: true, - productStockHistory: true, + productStockCountHistory: true, + productStockValueHistory: true, draftProductStocks: true, - purchaseOrderHistory: true, + purchaseOrderCountHistory: true, + purchaseOrderAmountHistory: true, draftPurchaseOrders: true, - stockEventHistory: true + stockEventHistory: true, + stockTransferHistory: true, + draftStockTransfers: true, + sentPurchaseOrders: true, + usedPartStocks: true, + plannedShipments: true, + shippedInboundShipments: true } const sections = [ @@ -52,24 +65,82 @@ const sections = [ content: }, { - key: 'partAndFilamentHistoryRow', + key: 'stockTransferStats', + title: 'Stock Transfer Statistics', + className: 'no-t-padding-collapse', + content: + }, + { + key: 'stockLocationStats', + title: 'Stock Location Statistics', + className: 'no-t-padding-collapse', + content: + }, + { + key: 'shipmentStats', + title: 'Shipment Statistics', + className: 'no-t-padding-collapse', + content: + }, + { + key: 'partStockHistoryRow', type: 'row', columns: [ [ { - key: 'partStockHistory', - title: 'Part Stock History', + key: 'partStockCountHistory', + title: 'Part Stock Count History', content: ( - + ) } ], [ { - key: 'filamentStockHistory', - title: 'Filament Stock History', + key: 'partStockValueHistory', + title: 'Part Stock Quantity History', content: ( - + + ) + } + ] + ] + }, + { + key: 'filamentStockHistoryRow', + type: 'row', + columns: [ + [ + { + key: 'filamentStockCountHistory', + title: 'Filament Stock Count History', + content: ( + + ) + } + ], + [ + { + key: 'filamentStockValueHistory', + title: 'Filament Stock Weight History', + content: ( + ) } ] @@ -103,23 +174,63 @@ const sections = [ ] }, { - key: 'productAndPurchaseOrderHistoryRow', + key: 'productStockHistoryRow', type: 'row', columns: [ [ { - key: 'productStockHistory', - title: 'Product Stock History', + key: 'productStockCountHistory', + title: 'Product Stock Count History', content: ( - + ) } ], [ { - key: 'purchaseOrderHistory', - title: 'Purchase Order History', - content: + key: 'productStockValueHistory', + title: 'Product Stock Quantity History', + content: ( + + ) + } + ] + ] + }, + { + key: 'purchaseOrderHistoryRow', + type: 'row', + columns: [ + [ + { + key: 'purchaseOrderCountHistory', + title: 'Purchase Order Count History', + content: ( + + ) + } + ], + [ + { + key: 'purchaseOrderAmountHistory', + title: 'Purchase Order Amount History', + content: ( + + ) } ] ] @@ -155,7 +266,7 @@ const sections = [ ] }, { - key: 'stockEventHistoryRow', + key: 'stockEventAndTransferHistoryRow', type: 'row', columns: [ [ @@ -166,6 +277,85 @@ const sections = [ ) } + ], + [ + { + key: 'stockTransferHistory', + title: 'Stock Transfer History', + content: + } + ] + ] + }, + { + key: 'draftTransfersAndSentPurchaseOrdersRow', + type: 'row', + columns: [ + [ + { + key: 'draftStockTransfers', + title: 'Draft Stock Transfers', + content: ( + + ) + } + ], + [ + { + key: 'sentPurchaseOrders', + title: 'Sent Purchase Orders', + content: ( + + ) + } + ] + ] + }, + { + key: 'usedPartsAndPlannedShipmentsRow', + type: 'row', + columns: [ + [ + { + key: 'usedPartStocks', + title: 'Used Part Stocks', + content: ( + + ) + } + ], + [ + { + key: 'plannedShipments', + title: 'Planned Shipments', + content: ( + + ) + } + ] + ] + }, + { + key: 'shippedInboundShipmentsRow', + type: 'row', + columns: [ + [ + { + key: 'shippedInboundShipments', + title: 'Inbound Shipments In Transit', + content: ( + + ) + } ] ] } diff --git a/src/components/Dashboard/Sales/SalesOverview.jsx b/src/components/Dashboard/Sales/SalesOverview.jsx index 70d46d83..a29e34b3 100644 --- a/src/components/Dashboard/Sales/SalesOverview.jsx +++ b/src/components/Dashboard/Sales/SalesOverview.jsx @@ -5,17 +5,28 @@ import ObjectTable from '../common/ObjectTable' const collapseDefaults = { salesOrderStats: true, + orderItemStats: true, listingStats: true, marketplaceStats: true, clientStats: true, - salesOrderHistory: true, - confirmedSalesOrders: true, + shipmentStats: true, + salesOrderCountHistory: true, + salesOrderAmountHistory: true, listingHistory: true, + orderItemCountHistory: true, + orderItemAmountHistory: true, + shipmentCountHistory: true, + shipmentAmountHistory: true, + confirmedSalesOrders: true, draftListings: true, marketplaceHistory: true, - readyMarketplaces: true, clientHistory: true, - activeClients: true + readyMarketplaces: true, + activeClients: true, + sentSalesOrders: true, + activeListings: true, + ordersToShip: true, + plannedOutboundShipments: true } const sections = [ @@ -25,6 +36,12 @@ const sections = [ className: 'no-t-padding-collapse', content: }, + { + key: 'orderItemStats', + title: 'Order Item Statistics', + className: 'no-t-padding-collapse', + content: + }, { key: 'listingStats', title: 'Listing Statistics', @@ -44,16 +61,42 @@ const sections = [ content: }, { - key: 'salesOrderAndListingHistoryRow', + key: 'shipmentStats', + title: 'Shipment Statistics', + className: 'no-t-padding-collapse', + content: + }, + { + key: 'salesOrderHistoryRow', type: 'row', columns: [ [ { - key: 'salesOrderHistory', - title: 'Sales Order History', - content: + key: 'salesOrderCountHistory', + title: 'Sales Order Count History', + content: ( + + ) } ], + [ + { + key: 'salesOrderAmountHistory', + title: 'Sales Order Amount History', + content: ( + + ) + } + ] + ] + }, + { + key: 'listingHistoryRow', + type: 'row', + columns: [ [ { key: 'listingHistory', @@ -63,6 +106,54 @@ const sections = [ ] ] }, + { + key: 'orderItemHistoryRow', + type: 'row', + columns: [ + [ + { + key: 'orderItemCountHistory', + title: 'Order Item Count History', + content: ( + + ) + } + ], + [ + { + key: 'orderItemAmountHistory', + title: 'Order Item Amount History', + content: ( + + ) + } + ] + ] + }, + { + key: 'shipmentHistoryRow', + type: 'row', + columns: [ + [ + { + key: 'shipmentCountHistory', + title: 'Shipment Count History', + content: ( + + ) + } + ], + [ + { + key: 'shipmentAmountHistory', + title: 'Shipment Amount History', + content: ( + + ) + } + ] + ] + }, { key: 'confirmedSalesOrdersAndDraftListingsRow', type: 'row', @@ -90,6 +181,30 @@ const sections = [ ] ] }, + { + key: 'sentSalesOrdersAndActiveListingsRow', + type: 'row', + columns: [ + [ + { + key: 'sentSalesOrders', + title: 'Sent Sales Orders', + content: ( + + ) + } + ], + [ + { + key: 'activeListings', + title: 'Active Listings', + content: ( + + ) + } + ] + ] + }, { key: 'marketplaceAndClientHistoryRow', type: 'row', @@ -131,6 +246,36 @@ const sections = [ } ] ] + }, + { + key: 'ordersToShipAndPlannedShipmentsRow', + type: 'row', + columns: [ + [ + { + key: 'ordersToShip', + title: 'Orders To Ship', + content: ( + + ) + } + ], + [ + { + key: 'plannedOutboundShipments', + title: 'Planned Outbound Shipments', + content: ( + + ) + } + ] + ] } ] diff --git a/src/components/Dashboard/common/ModelHistoryDisplay.jsx b/src/components/Dashboard/common/ModelHistoryDisplay.jsx index 8a49af56..1f2f1556 100644 --- a/src/components/Dashboard/common/ModelHistoryDisplay.jsx +++ b/src/components/Dashboard/common/ModelHistoryDisplay.jsx @@ -35,14 +35,52 @@ const legendMeasureStyle = { const toCssHeight = (value) => typeof value === 'number' ? `${value}px` : value +const isAmountStat = (statDef) => { + if (statDef.prefix === '£') return true + const name = statDef.name || '' + if (name.includes('Amount') || name.includes('Value')) return true + const combineList = statDef.combine || statDef.sum + if ( + combineList?.some( + (statName) => statName.includes('Amount') || statName.endsWith('.sum') + ) + ) { + return true + } + return false +} + +const isValueStat = (statDef) => { + if (isAmountStat(statDef)) return false + if (statDef.name?.endsWith('.sum')) return true + if (statDef.suffix) return true + return false +} + +export const resolveStatHistoryGroup = (statDef) => { + if (statDef.history === false) return null + if (statDef.historyGroup) return statDef.historyGroup + if (isAmountStat(statDef)) return 'amount' + if (isValueStat(statDef)) return 'value' + return 'count' +} + +export const getModelHistoryStats = (model, historyGroup = 'count') => { + return (model?.stats || []).filter( + (statDef) => resolveStatHistoryGroup(statDef) === historyGroup + ) +} + const ModelHistoryDisplay = ({ objectType, + historyGroup = 'count', startDate, endDate, styles, height = 400, - chartType = 'bar' + chartType: chartTypeProp }) => { + const chartType = chartTypeProp ?? (historyGroup === 'amount' ? 'line' : 'bar') const { getModelHistory, connected } = useContext(ApiServerContext) const { token } = useContext(AuthContext) const [historyData, setHistoryData] = useState([]) @@ -132,8 +170,8 @@ const ModelHistoryDisplay = ({ ) const modelStats = useMemo( - () => (model?.stats || []).filter((statDef) => statDef.history !== false), - [model] + () => getModelHistoryStats(model, historyGroup), + [model, historyGroup] ) const themeColors = getColors() @@ -516,6 +554,7 @@ const ModelHistoryDisplay = ({ ModelHistoryDisplay.propTypes = { objectType: PropTypes.string.isRequired, + historyGroup: PropTypes.oneOf(['count', 'amount', 'value']), startDate: PropTypes.oneOfType([ PropTypes.string, PropTypes.instanceOf(Date) diff --git a/src/database/models/FilamentStock.js b/src/database/models/FilamentStock.js index 6580d0e0..ac10149e 100644 --- a/src/database/models/FilamentStock.js +++ b/src/database/models/FilamentStock.js @@ -192,22 +192,19 @@ export const FilamentStock = { name: 'unconsumed.count', label: 'Unconsumed', type: 'number', - color: 'success', - history: false + color: 'success' }, { name: 'used.count', label: 'Used', type: 'number', - color: 'warning', - history: false + color: 'warning' }, { name: 'consumed.count', label: 'Consumed', type: 'number', - color: 'default', - history: false + color: 'default' }, { name: 'totalCurrentWeight.sum', @@ -215,7 +212,8 @@ export const FilamentStock = { type: 'number', roundNumber: 2, suffix: 'g', - cardWidth: 200 + cardWidth: 200, + historyGroup: 'value' } ] } diff --git a/src/database/models/Invoice.js b/src/database/models/Invoice.js index f281b845..a6ac4abd 100644 --- a/src/database/models/Invoice.js +++ b/src/database/models/Invoice.js @@ -741,6 +741,36 @@ export const Invoice = { prefix: '£', roundNumber: 2, color: 'error' + }, + { + name: 'partiallyPaid.partiallyPaidGrandTotalAmount.sum', + label: 'Partially Paid Amount', + type: 'number', + prefix: '£', + roundNumber: 2, + color: 'processing', + cardWidth: 200 + }, + { + name: 'paid.paidCount.count', + label: 'Paid', + type: 'number', + color: 'success' + }, + { + name: 'paid.paidGrandTotalAmount.sum', + label: 'Paid Amount', + type: 'number', + prefix: '£', + roundNumber: 2, + color: 'success', + cardWidth: 200 + }, + { + name: 'cancelled.cancelledCount.count', + label: 'Cancelled', + type: 'number', + color: 'default' } ] } diff --git a/src/database/models/OrderItem.js b/src/database/models/OrderItem.js index a835f44d..5aa64345 100644 --- a/src/database/models/OrderItem.js +++ b/src/database/models/OrderItem.js @@ -503,5 +503,49 @@ export const OrderItem = { readOnly: true, columnWidth: 225 } + ], + stats: [ + { + name: 'draft.count', + label: 'Draft', + type: 'number', + color: 'default' + }, + { + name: 'ordered.count', + label: 'Ordered', + type: 'number', + color: 'cyan' + }, + { + name: 'shipped.count', + label: 'Shipped', + type: 'number', + color: 'processing' + }, + { + name: 'received.count', + label: 'Received', + type: 'number', + color: 'success' + }, + { + name: 'shippedValue.totalAmountWithTax.sum', + label: 'Shipped Value', + type: 'number', + prefix: '£', + roundNumber: 2, + color: 'processing', + cardWidth: 175 + }, + { + name: 'receivedValue.totalAmountWithTax.sum', + label: 'Received Value', + type: 'number', + prefix: '£', + roundNumber: 2, + color: 'success', + cardWidth: 175 + } ] } diff --git a/src/database/models/PartStock.js b/src/database/models/PartStock.js index 183301b3..f1c90418 100644 --- a/src/database/models/PartStock.js +++ b/src/database/models/PartStock.js @@ -257,36 +257,33 @@ export const PartStock = { name: 'draft.count', label: 'Draft', type: 'number', - color: 'default', - history: false + color: 'default' }, { name: 'new.count', label: 'New', type: 'number', - color: 'success', - history: false + color: 'success' }, { name: 'used.count', label: 'Used', type: 'number', - color: 'warning', - history: false + color: 'warning' }, { name: 'consumed.count', label: 'Consumed', type: 'number', - color: 'default', - history: false + color: 'default' }, { name: 'totalCurrentQuantity.sum', label: 'Total Current Quantity', type: 'number', roundNumber: 2, - cardWidth: 200 + cardWidth: 200, + historyGroup: 'value' } ] } diff --git a/src/database/models/Payment.js b/src/database/models/Payment.js index 452f6909..17fa6fc7 100644 --- a/src/database/models/Payment.js +++ b/src/database/models/Payment.js @@ -379,6 +379,21 @@ export const Payment = { prefix: '£', roundNumber: 2, color: 'red' + }, + { + name: 'cancelled.cancelledCount.count', + label: 'Cancelled', + type: 'number', + color: 'default' + }, + { + name: 'cancelled.cancelledAmount.sum', + label: 'Cancelled Amount', + type: 'number', + prefix: '£', + roundNumber: 2, + color: 'default', + cardWidth: 200 } ] } diff --git a/src/database/models/ProductStock.js b/src/database/models/ProductStock.js index 3ab91a5f..d8ddbb56 100644 --- a/src/database/models/ProductStock.js +++ b/src/database/models/ProductStock.js @@ -420,36 +420,33 @@ export const ProductStock = { name: 'draft.count', label: 'Draft', type: 'number', - color: 'default', - history: false + color: 'default' }, { name: 'new.count', label: 'New', type: 'number', - color: 'success', - history: false + color: 'success' }, { name: 'used.count', label: 'Used', type: 'number', - color: 'warning', - history: false + color: 'warning' }, { name: 'consumed.count', label: 'Consumed', type: 'number', - color: 'default', - history: false + color: 'default' }, { name: 'totalCurrentQuantity.sum', label: 'Total Current Quantity', type: 'number', roundNumber: 2, - cardWidth: 200 + cardWidth: 200, + historyGroup: 'value' } ] } diff --git a/src/database/models/PurchaseOrder.js b/src/database/models/PurchaseOrder.js index 832b4f9a..887c1e58 100644 --- a/src/database/models/PurchaseOrder.js +++ b/src/database/models/PurchaseOrder.js @@ -449,6 +449,24 @@ export const PurchaseOrder = { label: 'Completed', type: 'number', color: 'success' + }, + { + name: 'awaitingReceiptValue.grandTotalAmount.sum', + label: 'Awaiting Receipt Value', + type: 'number', + prefix: '£', + roundNumber: 2, + color: 'warning', + cardWidth: 220 + }, + { + name: 'receivedValue.grandTotalAmount.sum', + label: 'Received Value', + type: 'number', + prefix: '£', + roundNumber: 2, + color: 'success', + cardWidth: 200 } ] } diff --git a/src/database/models/SalesOrder.js b/src/database/models/SalesOrder.js index 91c81e45..e3c6fe50 100644 --- a/src/database/models/SalesOrder.js +++ b/src/database/models/SalesOrder.js @@ -448,6 +448,24 @@ export const SalesOrder = { label: 'Cancelled', type: 'number', color: 'error' + }, + { + name: 'pipelineValue.grandTotalAmount.sum', + label: 'Pipeline Value', + type: 'number', + prefix: '£', + roundNumber: 2, + color: 'purple', + cardWidth: 200 + }, + { + name: 'completedValue.grandTotalAmount.sum', + label: 'Completed Value', + type: 'number', + prefix: '£', + roundNumber: 2, + color: 'success', + cardWidth: 200 } ] } diff --git a/src/database/models/Shipment.js b/src/database/models/Shipment.js index 3899dcff..ec3e5d3a 100644 --- a/src/database/models/Shipment.js +++ b/src/database/models/Shipment.js @@ -428,5 +428,46 @@ export const Shipment = { step: 0.01, columnWidth: 275 } + ], + stats: [ + { + name: 'draft.count', + label: 'Draft', + type: 'number', + color: 'default' + }, + { + name: 'planned.count', + label: 'Planned', + type: 'number', + color: 'warning' + }, + { + name: 'shipped.count', + label: 'Shipped', + type: 'number', + color: 'processing' + }, + { + name: 'delivered.count', + label: 'Delivered', + type: 'number', + color: 'success' + }, + { + name: 'cancelled.count', + label: 'Cancelled', + type: 'number', + color: 'error' + }, + { + name: 'inTransitValue.amountWithTax.sum', + label: 'In Transit Value', + type: 'number', + prefix: '£', + roundNumber: 2, + color: 'processing', + cardWidth: 200 + } ] } diff --git a/src/database/models/StockEvent.js b/src/database/models/StockEvent.js index c93c5c47..923ffd4c 100644 --- a/src/database/models/StockEvent.js +++ b/src/database/models/StockEvent.js @@ -115,6 +115,14 @@ export const StockEvent = { type: 'number', color: 'success', cardWidth: 220 + }, + { + name: 'totalEvents', + label: 'Total Events', + type: 'number', + color: 'default', + cardWidth: 175, + sum: ['partStock.count', 'filamentStock.count', 'productStock.count'] } ] } diff --git a/src/database/models/TaxRecord.js b/src/database/models/TaxRecord.js index 77320452..b10164e4 100644 --- a/src/database/models/TaxRecord.js +++ b/src/database/models/TaxRecord.js @@ -224,5 +224,49 @@ export const TaxRecord = { type: 'dateTime', readOnly: true } + ], + stats: [ + { + name: 'total.count.count', + label: 'Total Records', + type: 'number', + color: 'default' + }, + { + name: 'total.taxAmount.sum', + label: 'Total Tax', + type: 'number', + prefix: '£', + roundNumber: 2, + color: 'warning', + cardWidth: 175 + }, + { + name: 'total.amount.sum', + label: 'Total Transaction Value', + type: 'number', + prefix: '£', + roundNumber: 2, + color: 'default', + cardWidth: 220 + }, + { + name: 'salesOrder.taxAmount.sum', + label: 'Sales Tax', + type: 'number', + prefix: '£', + roundNumber: 2, + color: 'success', + cardWidth: 175 + }, + { + name: 'purchaseOrder.taxAmount.sum', + label: 'Purchase Tax', + type: 'number', + prefix: '£', + roundNumber: 2, + color: 'processing', + cardWidth: 175 + } ] }