From d0c090a657a63889a7030d1261045d849b97d95e Mon Sep 17 00:00:00 2001 From: ryan Date: Wed, 10 Jun 2026 13:18:49 +0700 Subject: [PATCH] Update purchase analytics --- internal/contract/analytics_contract.go | 20 +- internal/entities/analytics.go | 20 +- internal/models/analytics.go | 20 +- internal/processor/analytics_processor.go | 20 +- .../processor/analytics_processor_test.go | 29 ++- internal/repository/analytics_repository.go | 201 ++++++++++++++---- internal/transformer/analytics_transformer.go | 20 +- .../transformer/analytics_transformer_test.go | 30 +++ 8 files changed, 283 insertions(+), 77 deletions(-) diff --git a/internal/contract/analytics_contract.go b/internal/contract/analytics_contract.go index f7d019a..a5a0b75 100644 --- a/internal/contract/analytics_contract.go +++ b/internal/contract/analytics_contract.go @@ -106,7 +106,11 @@ type PurchasingAnalyticsResponse struct { type PurchasingSummary struct { TotalPurchases float64 `json:"total_purchases"` + RawMaterialPurchases float64 `json:"raw_material_purchases"` + NonInventoryPurchases float64 `json:"non_inventory_purchases"` TotalPurchaseOrders int64 `json:"total_purchase_orders"` + RawMaterialPurchaseOrders int64 `json:"raw_material_purchase_orders"` + NonInventoryExpenseCount int64 `json:"non_inventory_expense_count"` TotalQuantity float64 `json:"total_quantity"` AveragePurchaseOrderValue float64 `json:"average_purchase_order_value"` TotalIngredients int64 `json:"total_ingredients"` @@ -114,12 +118,16 @@ type PurchasingSummary struct { } type PurchasingAnalyticsData struct { - Date time.Time `json:"date"` - Purchases float64 `json:"purchases"` - PurchaseOrders int64 `json:"purchase_orders"` - Quantity float64 `json:"quantity"` - Ingredients int64 `json:"ingredients"` - Vendors int64 `json:"vendors"` + Date time.Time `json:"date"` + Purchases float64 `json:"purchases"` + RawMaterialPurchases float64 `json:"raw_material_purchases"` + NonInventoryPurchases float64 `json:"non_inventory_purchases"` + PurchaseOrders int64 `json:"purchase_orders"` + RawMaterialPurchaseOrders int64 `json:"raw_material_purchase_orders"` + NonInventoryExpenseCount int64 `json:"non_inventory_expense_count"` + Quantity float64 `json:"quantity"` + Ingredients int64 `json:"ingredients"` + Vendors int64 `json:"vendors"` } type PurchasingIngredientData struct { diff --git a/internal/entities/analytics.go b/internal/entities/analytics.go index 0fa8bda..b9f74c8 100644 --- a/internal/entities/analytics.go +++ b/internal/entities/analytics.go @@ -38,7 +38,11 @@ type PurchasingAnalytics struct { type PurchasingSummary struct { TotalPurchases float64 `json:"total_purchases"` + RawMaterialPurchases float64 `json:"raw_material_purchases"` + NonInventoryPurchases float64 `json:"non_inventory_purchases"` TotalPurchaseOrders int64 `json:"total_purchase_orders"` + RawMaterialPurchaseOrders int64 `json:"raw_material_purchase_orders"` + NonInventoryExpenseCount int64 `json:"non_inventory_expense_count"` TotalQuantity float64 `json:"total_quantity"` AveragePurchaseOrderValue float64 `json:"average_purchase_order_value"` TotalIngredients int64 `json:"total_ingredients"` @@ -46,12 +50,16 @@ type PurchasingSummary struct { } type PurchasingAnalyticsData struct { - Date time.Time `json:"date"` - Purchases float64 `json:"purchases"` - PurchaseOrders int64 `json:"purchase_orders"` - Quantity float64 `json:"quantity"` - Ingredients int64 `json:"ingredients"` - Vendors int64 `json:"vendors"` + Date time.Time `json:"date"` + Purchases float64 `json:"purchases"` + RawMaterialPurchases float64 `json:"raw_material_purchases"` + NonInventoryPurchases float64 `json:"non_inventory_purchases"` + PurchaseOrders int64 `json:"purchase_orders"` + RawMaterialPurchaseOrders int64 `json:"raw_material_purchase_orders"` + NonInventoryExpenseCount int64 `json:"non_inventory_expense_count"` + Quantity float64 `json:"quantity"` + Ingredients int64 `json:"ingredients"` + Vendors int64 `json:"vendors"` } type PurchasingIngredientData struct { diff --git a/internal/models/analytics.go b/internal/models/analytics.go index ec8858d..0b75cb2 100644 --- a/internal/models/analytics.go +++ b/internal/models/analytics.go @@ -113,7 +113,11 @@ type PurchasingAnalyticsResponse struct { // PurchasingSummary represents the summary of purchasing analytics type PurchasingSummary struct { TotalPurchases float64 `json:"total_purchases"` + RawMaterialPurchases float64 `json:"raw_material_purchases"` + NonInventoryPurchases float64 `json:"non_inventory_purchases"` TotalPurchaseOrders int64 `json:"total_purchase_orders"` + RawMaterialPurchaseOrders int64 `json:"raw_material_purchase_orders"` + NonInventoryExpenseCount int64 `json:"non_inventory_expense_count"` TotalQuantity float64 `json:"total_quantity"` AveragePurchaseOrderValue float64 `json:"average_purchase_order_value"` TotalIngredients int64 `json:"total_ingredients"` @@ -122,12 +126,16 @@ type PurchasingSummary struct { // PurchasingAnalyticsData represents purchasing analytics by time period type PurchasingAnalyticsData struct { - Date time.Time `json:"date"` - Purchases float64 `json:"purchases"` - PurchaseOrders int64 `json:"purchase_orders"` - Quantity float64 `json:"quantity"` - Ingredients int64 `json:"ingredients"` - Vendors int64 `json:"vendors"` + Date time.Time `json:"date"` + Purchases float64 `json:"purchases"` + RawMaterialPurchases float64 `json:"raw_material_purchases"` + NonInventoryPurchases float64 `json:"non_inventory_purchases"` + PurchaseOrders int64 `json:"purchase_orders"` + RawMaterialPurchaseOrders int64 `json:"raw_material_purchase_orders"` + NonInventoryExpenseCount int64 `json:"non_inventory_expense_count"` + Quantity float64 `json:"quantity"` + Ingredients int64 `json:"ingredients"` + Vendors int64 `json:"vendors"` } // PurchasingIngredientData represents purchasing analytics for an ingredient diff --git a/internal/processor/analytics_processor.go b/internal/processor/analytics_processor.go index 4743144..4eccc2c 100644 --- a/internal/processor/analytics_processor.go +++ b/internal/processor/analytics_processor.go @@ -185,12 +185,16 @@ func (p *AnalyticsProcessorImpl) GetPurchasingAnalytics(ctx context.Context, req data := make([]models.PurchasingAnalyticsData, len(result.Data)) for i, item := range result.Data { data[i] = models.PurchasingAnalyticsData{ - Date: item.Date, - Purchases: item.Purchases, - PurchaseOrders: item.PurchaseOrders, - Quantity: item.Quantity, - Ingredients: item.Ingredients, - Vendors: item.Vendors, + Date: item.Date, + Purchases: item.Purchases, + RawMaterialPurchases: item.RawMaterialPurchases, + NonInventoryPurchases: item.NonInventoryPurchases, + PurchaseOrders: item.PurchaseOrders, + RawMaterialPurchaseOrders: item.RawMaterialPurchaseOrders, + NonInventoryExpenseCount: item.NonInventoryExpenseCount, + Quantity: item.Quantity, + Ingredients: item.Ingredients, + Vendors: item.Vendors, } } @@ -227,7 +231,11 @@ func (p *AnalyticsProcessorImpl) GetPurchasingAnalytics(ctx context.Context, req GroupBy: req.GroupBy, Summary: models.PurchasingSummary{ TotalPurchases: result.Summary.TotalPurchases, + RawMaterialPurchases: result.Summary.RawMaterialPurchases, + NonInventoryPurchases: result.Summary.NonInventoryPurchases, TotalPurchaseOrders: result.Summary.TotalPurchaseOrders, + RawMaterialPurchaseOrders: result.Summary.RawMaterialPurchaseOrders, + NonInventoryExpenseCount: result.Summary.NonInventoryExpenseCount, TotalQuantity: result.Summary.TotalQuantity, AveragePurchaseOrderValue: result.Summary.AveragePurchaseOrderValue, TotalIngredients: result.Summary.TotalIngredients, diff --git a/internal/processor/analytics_processor_test.go b/internal/processor/analytics_processor_test.go index 0c46b4d..e088b2e 100644 --- a/internal/processor/analytics_processor_test.go +++ b/internal/processor/analytics_processor_test.go @@ -75,7 +75,23 @@ func TestAnalyticsProcessorGetPurchasingAnalyticsPassesOutletName(t *testing.T) purchasingResult: &entities.PurchasingAnalytics{ OutletName: &outletName, Summary: entities.PurchasingSummary{ - TotalPurchases: 125, + TotalPurchases: 300, + RawMaterialPurchases: 125, + NonInventoryPurchases: 175, + TotalPurchaseOrders: 3, + RawMaterialPurchaseOrders: 1, + NonInventoryExpenseCount: 2, + }, + Data: []entities.PurchasingAnalyticsData{ + { + Date: now, + Purchases: 300, + RawMaterialPurchases: 125, + NonInventoryPurchases: 175, + PurchaseOrders: 3, + RawMaterialPurchaseOrders: 1, + NonInventoryExpenseCount: 2, + }, }, }, }, expenseRepositoryStub{}) @@ -92,7 +108,16 @@ func TestAnalyticsProcessorGetPurchasingAnalyticsPassesOutletName(t *testing.T) require.Equal(t, &outletID, result.OutletID) require.NotNil(t, result.OutletName) require.Equal(t, outletName, *result.OutletName) - require.Equal(t, float64(125), result.Summary.TotalPurchases) + require.Equal(t, float64(300), result.Summary.TotalPurchases) + require.Equal(t, float64(125), result.Summary.RawMaterialPurchases) + require.Equal(t, float64(175), result.Summary.NonInventoryPurchases) + require.Equal(t, int64(3), result.Summary.TotalPurchaseOrders) + require.Equal(t, int64(1), result.Summary.RawMaterialPurchaseOrders) + require.Equal(t, int64(2), result.Summary.NonInventoryExpenseCount) + require.Len(t, result.Data, 1) + require.Equal(t, float64(300), result.Data[0].Purchases) + require.Equal(t, float64(125), result.Data[0].RawMaterialPurchases) + require.Equal(t, float64(175), result.Data[0].NonInventoryPurchases) } func TestAnalyticsProcessorGetProfitLossAnalyticsMapsOverviewAndReportFields(t *testing.T) { diff --git a/internal/repository/analytics_repository.go b/internal/repository/analytics_repository.go index dd73f91..4677b31 100644 --- a/internal/repository/analytics_repository.go +++ b/internal/repository/analytics_repository.go @@ -145,68 +145,179 @@ func (r *AnalyticsRepositoryImpl) GetPurchasingAnalytics(ctx context.Context, or } } - summaryQuery := r.db.WithContext(ctx). - Table("inventory_movements im"). - Select(` - COALESCE(SUM(im.total_cost), 0) as total_purchases, - COUNT(DISTINCT im.reference_id) as total_purchase_orders, - COALESCE(SUM(im.quantity), 0) as total_quantity, + rawMaterialOutletFilter := "" + nonInventoryOutletFilter := "" + rawMaterialSummaryArgs := []interface{}{ + organizationID, + entities.InventoryMovementTypePurchase, + "INGREDIENT", + entities.InventoryMovementReferenceTypePurchaseOrder, + dateFrom, + dateTo, + } + nonInventorySummaryArgs := []interface{}{ + organizationID, + entities.PurchaseCategoryTypeNonInventory, + "approved", + dateFrom, + dateTo, + } + if outletID != nil { + rawMaterialOutletFilter = "AND im.outlet_id = ?" + nonInventoryOutletFilter = "AND e.outlet_id = ?" + rawMaterialSummaryArgs = append(rawMaterialSummaryArgs, *outletID) + nonInventorySummaryArgs = append(nonInventorySummaryArgs, *outletID) + } + summaryArgs := append(rawMaterialSummaryArgs, nonInventorySummaryArgs...) + + summaryQuery := ` + WITH raw_material AS ( + SELECT + COALESCE(SUM(im.total_cost), 0) as raw_material_purchases, + COUNT(DISTINCT im.reference_id) as raw_material_purchase_orders, + COALESCE(SUM(im.quantity), 0) as total_quantity, + COUNT(DISTINCT im.item_id) as total_ingredients, + COUNT(DISTINCT po.vendor_id) as total_vendors + FROM inventory_movements im + LEFT JOIN purchase_orders po ON im.reference_id = po.id + WHERE im.organization_id = ? + AND im.movement_type = ? + AND im.item_type = ? + AND im.reference_type = ? + AND im.created_at >= ? AND im.created_at <= ? + ` + rawMaterialOutletFilter + ` + ), + non_inventory AS ( + SELECT + COALESCE(SUM(ei.amount), 0) as non_inventory_purchases, + COUNT(DISTINCT e.id) as non_inventory_expense_count + FROM expense_items ei + JOIN expenses e ON ei.expense_id = e.id + JOIN purchase_categories pc ON ei.purchase_category_id = pc.id + WHERE e.organization_id = ? + AND pc.type = ? + AND e.status = ? + AND e.transaction_date >= ? AND e.transaction_date <= ? + ` + nonInventoryOutletFilter + ` + ) + SELECT + rm.raw_material_purchases + ni.non_inventory_purchases as total_purchases, + rm.raw_material_purchases, + ni.non_inventory_purchases, + rm.raw_material_purchase_orders + ni.non_inventory_expense_count as total_purchase_orders, + rm.raw_material_purchase_orders, + ni.non_inventory_expense_count, + rm.total_quantity, CASE - WHEN COUNT(DISTINCT im.reference_id) > 0 - THEN COALESCE(SUM(im.total_cost), 0) / COUNT(DISTINCT im.reference_id) + WHEN rm.raw_material_purchase_orders + ni.non_inventory_expense_count > 0 + THEN (rm.raw_material_purchases + ni.non_inventory_purchases) / (rm.raw_material_purchase_orders + ni.non_inventory_expense_count) ELSE 0 END as average_purchase_order_value, - COUNT(DISTINCT im.item_id) as total_ingredients, - COUNT(DISTINCT po.vendor_id) as total_vendors - `). - Joins("LEFT JOIN purchase_orders po ON im.reference_id = po.id"). - Where("im.organization_id = ?", organizationID). - Where("im.movement_type = ?", entities.InventoryMovementTypePurchase). - Where("im.item_type = ?", "INGREDIENT"). - Where("im.reference_type = ?", entities.InventoryMovementReferenceTypePurchaseOrder). - Where("im.created_at >= ? AND im.created_at <= ?", dateFrom, dateTo) + rm.total_ingredients, + rm.total_vendors + FROM raw_material rm + CROSS JOIN non_inventory ni + ` - summaryQuery = r.resolveOutletID(summaryQuery, outletID, "im.outlet_id") - - if err := summaryQuery.Scan(&summary).Error; err != nil { + if err := r.db.WithContext(ctx).Raw(summaryQuery, summaryArgs...).Scan(&summary).Error; err != nil { return nil, err } var dateFormat string switch groupBy { case "hour": - dateFormat = "DATE_TRUNC('hour', im.created_at)" + dateFormat = "DATE_TRUNC('hour', im.created_at)::timestamp" case "week": - dateFormat = "DATE_TRUNC('week', im.created_at)" + dateFormat = "DATE_TRUNC('week', im.created_at)::timestamp" case "month": - dateFormat = "DATE_TRUNC('month', im.created_at)" + dateFormat = "DATE_TRUNC('month', im.created_at)::timestamp" default: - dateFormat = "DATE_TRUNC('day', im.created_at)" + dateFormat = "DATE_TRUNC('day', im.created_at)::timestamp" } + nonInventoryDateFormat := "DATE_TRUNC('day', e.transaction_date)::timestamp" + switch groupBy { + case "hour": + nonInventoryDateFormat = "DATE_TRUNC('hour', e.transaction_date)::timestamp" + case "week": + nonInventoryDateFormat = "DATE_TRUNC('week', e.transaction_date)::timestamp" + case "month": + nonInventoryDateFormat = "DATE_TRUNC('month', e.transaction_date)::timestamp" + } + + rawMaterialDataArgs := []interface{}{ + organizationID, + entities.InventoryMovementTypePurchase, + "INGREDIENT", + entities.InventoryMovementReferenceTypePurchaseOrder, + dateFrom, + dateTo, + } + nonInventoryDataArgs := []interface{}{ + organizationID, + entities.PurchaseCategoryTypeNonInventory, + "approved", + dateFrom, + dateTo, + } + if outletID != nil { + rawMaterialDataArgs = append(rawMaterialDataArgs, *outletID) + nonInventoryDataArgs = append(nonInventoryDataArgs, *outletID) + } + dataArgs := append(rawMaterialDataArgs, nonInventoryDataArgs...) + var data []entities.PurchasingAnalyticsData - dataQuery := r.db.WithContext(ctx). - Table("inventory_movements im"). - Select(` - `+dateFormat+` as date, - COALESCE(SUM(im.total_cost), 0) as purchases, - COUNT(DISTINCT im.reference_id) as purchase_orders, - COALESCE(SUM(im.quantity), 0) as quantity, - COUNT(DISTINCT im.item_id) as ingredients, - COUNT(DISTINCT po.vendor_id) as vendors - `). - Joins("LEFT JOIN purchase_orders po ON im.reference_id = po.id"). - Where("im.organization_id = ?", organizationID). - Where("im.movement_type = ?", entities.InventoryMovementTypePurchase). - Where("im.item_type = ?", "INGREDIENT"). - Where("im.reference_type = ?", entities.InventoryMovementReferenceTypePurchaseOrder). - Where("im.created_at >= ? AND im.created_at <= ?", dateFrom, dateTo). - Group(dateFormat). - Order(dateFormat) + dataQuery := ` + WITH raw_material AS ( + SELECT + ` + dateFormat + ` as date, + COALESCE(SUM(im.total_cost), 0) as raw_material_purchases, + COUNT(DISTINCT im.reference_id) as raw_material_purchase_orders, + COALESCE(SUM(im.quantity), 0) as quantity, + COUNT(DISTINCT im.item_id) as ingredients, + COUNT(DISTINCT po.vendor_id) as vendors + FROM inventory_movements im + LEFT JOIN purchase_orders po ON im.reference_id = po.id + WHERE im.organization_id = ? + AND im.movement_type = ? + AND im.item_type = ? + AND im.reference_type = ? + AND im.created_at >= ? AND im.created_at <= ? + ` + rawMaterialOutletFilter + ` + GROUP BY 1 + ), + non_inventory AS ( + SELECT + ` + nonInventoryDateFormat + ` as date, + COALESCE(SUM(ei.amount), 0) as non_inventory_purchases, + COUNT(DISTINCT e.id) as non_inventory_expense_count + FROM expense_items ei + JOIN expenses e ON ei.expense_id = e.id + JOIN purchase_categories pc ON ei.purchase_category_id = pc.id + WHERE e.organization_id = ? + AND pc.type = ? + AND e.status = ? + AND e.transaction_date >= ? AND e.transaction_date <= ? + ` + nonInventoryOutletFilter + ` + GROUP BY 1 + ) + SELECT + COALESCE(rm.date, ni.date) as date, + COALESCE(rm.raw_material_purchases, 0) + COALESCE(ni.non_inventory_purchases, 0) as purchases, + COALESCE(rm.raw_material_purchases, 0) as raw_material_purchases, + COALESCE(ni.non_inventory_purchases, 0) as non_inventory_purchases, + COALESCE(rm.raw_material_purchase_orders, 0) + COALESCE(ni.non_inventory_expense_count, 0) as purchase_orders, + COALESCE(rm.raw_material_purchase_orders, 0) as raw_material_purchase_orders, + COALESCE(ni.non_inventory_expense_count, 0) as non_inventory_expense_count, + COALESCE(rm.quantity, 0) as quantity, + COALESCE(rm.ingredients, 0) as ingredients, + COALESCE(rm.vendors, 0) as vendors + FROM raw_material rm + FULL OUTER JOIN non_inventory ni ON rm.date = ni.date + ORDER BY date + ` - dataQuery = r.resolveOutletID(dataQuery, outletID, "im.outlet_id") - - if err := dataQuery.Scan(&data).Error; err != nil { + if err := r.db.WithContext(ctx).Raw(dataQuery, dataArgs...).Scan(&data).Error; err != nil { return nil, err } diff --git a/internal/transformer/analytics_transformer.go b/internal/transformer/analytics_transformer.go index f157598..93f3967 100644 --- a/internal/transformer/analytics_transformer.go +++ b/internal/transformer/analytics_transformer.go @@ -169,12 +169,16 @@ func PurchasingAnalyticsModelToContract(resp *models.PurchasingAnalyticsResponse data := make([]contract.PurchasingAnalyticsData, len(resp.Data)) for i, item := range resp.Data { data[i] = contract.PurchasingAnalyticsData{ - Date: item.Date, - Purchases: item.Purchases, - PurchaseOrders: item.PurchaseOrders, - Quantity: item.Quantity, - Ingredients: item.Ingredients, - Vendors: item.Vendors, + Date: item.Date, + Purchases: item.Purchases, + RawMaterialPurchases: item.RawMaterialPurchases, + NonInventoryPurchases: item.NonInventoryPurchases, + PurchaseOrders: item.PurchaseOrders, + RawMaterialPurchaseOrders: item.RawMaterialPurchaseOrders, + NonInventoryExpenseCount: item.NonInventoryExpenseCount, + Quantity: item.Quantity, + Ingredients: item.Ingredients, + Vendors: item.Vendors, } } @@ -211,7 +215,11 @@ func PurchasingAnalyticsModelToContract(resp *models.PurchasingAnalyticsResponse GroupBy: resp.GroupBy, Summary: contract.PurchasingSummary{ TotalPurchases: resp.Summary.TotalPurchases, + RawMaterialPurchases: resp.Summary.RawMaterialPurchases, + NonInventoryPurchases: resp.Summary.NonInventoryPurchases, TotalPurchaseOrders: resp.Summary.TotalPurchaseOrders, + RawMaterialPurchaseOrders: resp.Summary.RawMaterialPurchaseOrders, + NonInventoryExpenseCount: resp.Summary.NonInventoryExpenseCount, TotalQuantity: resp.Summary.TotalQuantity, AveragePurchaseOrderValue: resp.Summary.AveragePurchaseOrderValue, TotalIngredients: resp.Summary.TotalIngredients, diff --git a/internal/transformer/analytics_transformer_test.go b/internal/transformer/analytics_transformer_test.go index 2a7bc6c..45775ff 100644 --- a/internal/transformer/analytics_transformer_test.go +++ b/internal/transformer/analytics_transformer_test.go @@ -52,17 +52,47 @@ func TestPurchasingAnalyticsContractToModelIgnoresInvalidOutlet(t *testing.T) { func TestPurchasingAnalyticsModelToContractCopiesOutletName(t *testing.T) { outletID := uuid.New() outletName := "Main Outlet" + now := time.Date(2026, 5, 1, 0, 0, 0, 0, time.UTC) result := PurchasingAnalyticsModelToContract(&models.PurchasingAnalyticsResponse{ OrganizationID: uuid.New(), OutletID: &outletID, OutletName: &outletName, + Summary: models.PurchasingSummary{ + TotalPurchases: 300, + RawMaterialPurchases: 125, + NonInventoryPurchases: 175, + TotalPurchaseOrders: 3, + RawMaterialPurchaseOrders: 1, + NonInventoryExpenseCount: 2, + }, + Data: []models.PurchasingAnalyticsData{ + { + Date: now, + Purchases: 300, + RawMaterialPurchases: 125, + NonInventoryPurchases: 175, + PurchaseOrders: 3, + RawMaterialPurchaseOrders: 1, + NonInventoryExpenseCount: 2, + }, + }, }) require.NotNil(t, result) require.Equal(t, &outletID, result.OutletID) require.NotNil(t, result.OutletName) require.Equal(t, outletName, *result.OutletName) + require.Equal(t, float64(300), result.Summary.TotalPurchases) + require.Equal(t, float64(125), result.Summary.RawMaterialPurchases) + require.Equal(t, float64(175), result.Summary.NonInventoryPurchases) + require.Equal(t, int64(3), result.Summary.TotalPurchaseOrders) + require.Equal(t, int64(1), result.Summary.RawMaterialPurchaseOrders) + require.Equal(t, int64(2), result.Summary.NonInventoryExpenseCount) + require.Len(t, result.Data, 1) + require.Equal(t, float64(300), result.Data[0].Purchases) + require.Equal(t, float64(125), result.Data[0].RawMaterialPurchases) + require.Equal(t, float64(175), result.Data[0].NonInventoryPurchases) } func TestPurchasingAnalyticsModelToContractOmitsNilOutletName(t *testing.T) {