Add grouping by outlet_id #22
@ -90,7 +90,7 @@ type PurchasingAnalyticsRequest struct {
|
|||||||
OutletID *string `form:"outlet_id,omitempty"`
|
OutletID *string `form:"outlet_id,omitempty"`
|
||||||
DateFrom string `form:"date_from" validate:"required"`
|
DateFrom string `form:"date_from" validate:"required"`
|
||||||
DateTo string `form:"date_to" validate:"required"`
|
DateTo string `form:"date_to" validate:"required"`
|
||||||
GroupBy string `form:"group_by,default=day" validate:"omitempty,oneof=day hour week month"`
|
GroupBy string `form:"group_by,default=day" validate:"omitempty,oneof=day hour week month outlet_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type PurchasingAnalyticsResponse struct {
|
type PurchasingAnalyticsResponse struct {
|
||||||
@ -102,6 +102,7 @@ type PurchasingAnalyticsResponse struct {
|
|||||||
GroupBy string `json:"group_by"`
|
GroupBy string `json:"group_by"`
|
||||||
Summary PurchasingSummary `json:"summary"`
|
Summary PurchasingSummary `json:"summary"`
|
||||||
Data []PurchasingAnalyticsData `json:"data"`
|
Data []PurchasingAnalyticsData `json:"data"`
|
||||||
|
OutletData []PurchasingOutletData `json:"outlet_data,omitempty"`
|
||||||
IngredientData []PurchasingIngredientData `json:"ingredient_data"`
|
IngredientData []PurchasingIngredientData `json:"ingredient_data"`
|
||||||
VendorData []PurchasingVendorData `json:"vendor_data"`
|
VendorData []PurchasingVendorData `json:"vendor_data"`
|
||||||
}
|
}
|
||||||
@ -132,6 +133,20 @@ type PurchasingAnalyticsData struct {
|
|||||||
Vendors int64 `json:"vendors"`
|
Vendors int64 `json:"vendors"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PurchasingOutletData struct {
|
||||||
|
OutletID *uuid.UUID `json:"outlet_id,omitempty"`
|
||||||
|
OutletName string `json:"outlet_name"`
|
||||||
|
Purchases float64 `json:"purchases"`
|
||||||
|
RawMaterialPurchases float64 `json:"raw_material_purchases"`
|
||||||
|
ExpensePurchases float64 `json:"expense_purchases"`
|
||||||
|
PurchaseOrders int64 `json:"purchase_orders"`
|
||||||
|
RawMaterialPurchaseOrders int64 `json:"raw_material_purchase_orders"`
|
||||||
|
ExpenseCount int64 `json:"expense_count"`
|
||||||
|
Quantity float64 `json:"quantity"`
|
||||||
|
Ingredients int64 `json:"ingredients"`
|
||||||
|
Vendors int64 `json:"vendors"`
|
||||||
|
}
|
||||||
|
|
||||||
type PurchasingIngredientData struct {
|
type PurchasingIngredientData struct {
|
||||||
IngredientID uuid.UUID `json:"ingredient_id"`
|
IngredientID uuid.UUID `json:"ingredient_id"`
|
||||||
IngredientName string `json:"ingredient_name"`
|
IngredientName string `json:"ingredient_name"`
|
||||||
|
|||||||
@ -32,6 +32,7 @@ type PurchasingAnalytics struct {
|
|||||||
OutletName *string `json:"outlet_name,omitempty"`
|
OutletName *string `json:"outlet_name,omitempty"`
|
||||||
Summary PurchasingSummary `json:"summary"`
|
Summary PurchasingSummary `json:"summary"`
|
||||||
Data []PurchasingAnalyticsData `json:"data"`
|
Data []PurchasingAnalyticsData `json:"data"`
|
||||||
|
OutletData []PurchasingOutletData `json:"outlet_data,omitempty"`
|
||||||
IngredientData []PurchasingIngredientData `json:"ingredient_data"`
|
IngredientData []PurchasingIngredientData `json:"ingredient_data"`
|
||||||
VendorData []PurchasingVendorData `json:"vendor_data"`
|
VendorData []PurchasingVendorData `json:"vendor_data"`
|
||||||
}
|
}
|
||||||
@ -62,6 +63,20 @@ type PurchasingAnalyticsData struct {
|
|||||||
Vendors int64 `json:"vendors"`
|
Vendors int64 `json:"vendors"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PurchasingOutletData struct {
|
||||||
|
OutletID *uuid.UUID `json:"outlet_id,omitempty"`
|
||||||
|
OutletName string `json:"outlet_name"`
|
||||||
|
Purchases float64 `json:"purchases"`
|
||||||
|
RawMaterialPurchases float64 `json:"raw_material_purchases"`
|
||||||
|
ExpensePurchases float64 `json:"expense_purchases"`
|
||||||
|
PurchaseOrders int64 `json:"purchase_orders"`
|
||||||
|
RawMaterialPurchaseOrders int64 `json:"raw_material_purchase_orders"`
|
||||||
|
ExpenseCount int64 `json:"expense_count"`
|
||||||
|
Quantity float64 `json:"quantity"`
|
||||||
|
Ingredients int64 `json:"ingredients"`
|
||||||
|
Vendors int64 `json:"vendors"`
|
||||||
|
}
|
||||||
|
|
||||||
type PurchasingIngredientData struct {
|
type PurchasingIngredientData struct {
|
||||||
IngredientID uuid.UUID `json:"ingredient_id"`
|
IngredientID uuid.UUID `json:"ingredient_id"`
|
||||||
IngredientName string `json:"ingredient_name"`
|
IngredientName string `json:"ingredient_name"`
|
||||||
|
|||||||
@ -95,7 +95,7 @@ type PurchasingAnalyticsRequest struct {
|
|||||||
OutletID *uuid.UUID `validate:"omitempty"`
|
OutletID *uuid.UUID `validate:"omitempty"`
|
||||||
DateFrom time.Time `validate:"required"`
|
DateFrom time.Time `validate:"required"`
|
||||||
DateTo time.Time `validate:"required"`
|
DateTo time.Time `validate:"required"`
|
||||||
GroupBy string `validate:"omitempty,oneof=day hour week month"`
|
GroupBy string `validate:"omitempty,oneof=day hour week month outlet_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// PurchasingAnalyticsResponse represents the response for purchasing analytics
|
// PurchasingAnalyticsResponse represents the response for purchasing analytics
|
||||||
@ -108,6 +108,7 @@ type PurchasingAnalyticsResponse struct {
|
|||||||
GroupBy string `json:"group_by"`
|
GroupBy string `json:"group_by"`
|
||||||
Summary PurchasingSummary `json:"summary"`
|
Summary PurchasingSummary `json:"summary"`
|
||||||
Data []PurchasingAnalyticsData `json:"data"`
|
Data []PurchasingAnalyticsData `json:"data"`
|
||||||
|
OutletData []PurchasingOutletData `json:"outlet_data,omitempty"`
|
||||||
IngredientData []PurchasingIngredientData `json:"ingredient_data"`
|
IngredientData []PurchasingIngredientData `json:"ingredient_data"`
|
||||||
VendorData []PurchasingVendorData `json:"vendor_data"`
|
VendorData []PurchasingVendorData `json:"vendor_data"`
|
||||||
}
|
}
|
||||||
@ -140,6 +141,20 @@ type PurchasingAnalyticsData struct {
|
|||||||
Vendors int64 `json:"vendors"`
|
Vendors int64 `json:"vendors"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PurchasingOutletData struct {
|
||||||
|
OutletID *uuid.UUID `json:"outlet_id,omitempty"`
|
||||||
|
OutletName string `json:"outlet_name"`
|
||||||
|
Purchases float64 `json:"purchases"`
|
||||||
|
RawMaterialPurchases float64 `json:"raw_material_purchases"`
|
||||||
|
ExpensePurchases float64 `json:"expense_purchases"`
|
||||||
|
PurchaseOrders int64 `json:"purchase_orders"`
|
||||||
|
RawMaterialPurchaseOrders int64 `json:"raw_material_purchase_orders"`
|
||||||
|
ExpenseCount int64 `json:"expense_count"`
|
||||||
|
Quantity float64 `json:"quantity"`
|
||||||
|
Ingredients int64 `json:"ingredients"`
|
||||||
|
Vendors int64 `json:"vendors"`
|
||||||
|
}
|
||||||
|
|
||||||
// PurchasingIngredientData represents purchasing analytics for an ingredient
|
// PurchasingIngredientData represents purchasing analytics for an ingredient
|
||||||
type PurchasingIngredientData struct {
|
type PurchasingIngredientData struct {
|
||||||
IngredientID uuid.UUID `json:"ingredient_id"`
|
IngredientID uuid.UUID `json:"ingredient_id"`
|
||||||
|
|||||||
@ -218,6 +218,23 @@ func (p *AnalyticsProcessorImpl) GetPurchasingAnalytics(ctx context.Context, req
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
outletData := make([]models.PurchasingOutletData, len(result.OutletData))
|
||||||
|
for i, item := range result.OutletData {
|
||||||
|
outletData[i] = models.PurchasingOutletData{
|
||||||
|
OutletID: item.OutletID,
|
||||||
|
OutletName: item.OutletName,
|
||||||
|
Purchases: item.Purchases,
|
||||||
|
RawMaterialPurchases: item.RawMaterialPurchases,
|
||||||
|
ExpensePurchases: item.ExpensePurchases,
|
||||||
|
PurchaseOrders: item.PurchaseOrders,
|
||||||
|
RawMaterialPurchaseOrders: item.RawMaterialPurchaseOrders,
|
||||||
|
ExpenseCount: item.ExpenseCount,
|
||||||
|
Quantity: item.Quantity,
|
||||||
|
Ingredients: item.Ingredients,
|
||||||
|
Vendors: item.Vendors,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ingredientData := make([]models.PurchasingIngredientData, len(result.IngredientData))
|
ingredientData := make([]models.PurchasingIngredientData, len(result.IngredientData))
|
||||||
for i, item := range result.IngredientData {
|
for i, item := range result.IngredientData {
|
||||||
ingredientData[i] = models.PurchasingIngredientData{
|
ingredientData[i] = models.PurchasingIngredientData{
|
||||||
@ -262,6 +279,7 @@ func (p *AnalyticsProcessorImpl) GetPurchasingAnalytics(ctx context.Context, req
|
|||||||
TotalVendors: result.Summary.TotalVendors,
|
TotalVendors: result.Summary.TotalVendors,
|
||||||
},
|
},
|
||||||
Data: data,
|
Data: data,
|
||||||
|
OutletData: outletData,
|
||||||
IngredientData: ingredientData,
|
IngredientData: ingredientData,
|
||||||
VendorData: vendorData,
|
VendorData: vendorData,
|
||||||
}, nil
|
}, nil
|
||||||
|
|||||||
@ -145,6 +145,52 @@ func TestAnalyticsProcessorGetPurchasingAnalyticsPassesOutletName(t *testing.T)
|
|||||||
require.Equal(t, float64(175), result.Data[0].ExpensePurchases)
|
require.Equal(t, float64(175), result.Data[0].ExpensePurchases)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAnalyticsProcessorGetPurchasingAnalyticsMapsOutletData(t *testing.T) {
|
||||||
|
outletID := uuid.New()
|
||||||
|
now := time.Date(2026, 5, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
processor := NewAnalyticsProcessorImpl(&analyticsRepositoryStub{
|
||||||
|
purchasingResult: &entities.PurchasingAnalytics{
|
||||||
|
Summary: entities.PurchasingSummary{
|
||||||
|
TotalPurchases: 500,
|
||||||
|
},
|
||||||
|
OutletData: []entities.PurchasingOutletData{
|
||||||
|
{
|
||||||
|
OutletID: &outletID,
|
||||||
|
OutletName: "Outlet A",
|
||||||
|
Purchases: 500,
|
||||||
|
RawMaterialPurchases: 350,
|
||||||
|
ExpensePurchases: 150,
|
||||||
|
PurchaseOrders: 4,
|
||||||
|
RawMaterialPurchaseOrders: 3,
|
||||||
|
ExpenseCount: 2,
|
||||||
|
Quantity: 10,
|
||||||
|
Ingredients: 5,
|
||||||
|
Vendors: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, expenseRepositoryStub{})
|
||||||
|
|
||||||
|
result, err := processor.GetPurchasingAnalytics(context.Background(), &models.PurchasingAnalyticsRequest{
|
||||||
|
OrganizationID: uuid.New(),
|
||||||
|
DateFrom: now,
|
||||||
|
DateTo: now,
|
||||||
|
GroupBy: "outlet_id",
|
||||||
|
})
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, result)
|
||||||
|
require.Equal(t, "outlet_id", result.GroupBy)
|
||||||
|
require.Empty(t, result.Data)
|
||||||
|
require.Len(t, result.OutletData, 1)
|
||||||
|
require.Equal(t, &outletID, result.OutletData[0].OutletID)
|
||||||
|
require.Equal(t, "Outlet A", result.OutletData[0].OutletName)
|
||||||
|
require.Equal(t, float64(500), result.OutletData[0].Purchases)
|
||||||
|
require.Equal(t, float64(350), result.OutletData[0].RawMaterialPurchases)
|
||||||
|
require.Equal(t, float64(150), result.OutletData[0].ExpensePurchases)
|
||||||
|
require.Equal(t, int64(4), result.OutletData[0].PurchaseOrders)
|
||||||
|
}
|
||||||
|
|
||||||
func TestAnalyticsProcessorGetProfitLossAnalyticsMapsOverviewAndReportFields(t *testing.T) {
|
func TestAnalyticsProcessorGetProfitLossAnalyticsMapsOverviewAndReportFields(t *testing.T) {
|
||||||
productID := uuid.New()
|
productID := uuid.New()
|
||||||
categoryID := uuid.New()
|
categoryID := uuid.New()
|
||||||
|
|||||||
@ -224,6 +224,39 @@ func (r *AnalyticsRepositoryImpl) getPurchaseOrderPurchasingAnalytics(ctx contex
|
|||||||
}
|
}
|
||||||
|
|
||||||
var data []entities.PurchasingAnalyticsData
|
var data []entities.PurchasingAnalyticsData
|
||||||
|
var outletData []entities.PurchasingOutletData
|
||||||
|
if groupBy == "outlet_id" {
|
||||||
|
outletQuery := r.db.WithContext(ctx).
|
||||||
|
Table("purchase_orders po").
|
||||||
|
Select(`
|
||||||
|
po.outlet_id as outlet_id,
|
||||||
|
COALESCE(o.name, 'No Outlet') as outlet_name,
|
||||||
|
COALESCE(SUM(`+purchaseOrderItemTotalAmountSQL()+`), 0) as purchases,
|
||||||
|
COALESCE(SUM(`+purchaseOrderRawMaterialAmountSQL()+`), 0) as raw_material_purchases,
|
||||||
|
COALESCE(SUM(`+purchaseOrderExpenseAmountSQL()+`), 0) as expense_purchases,
|
||||||
|
COUNT(DISTINCT po.id) as purchase_orders,
|
||||||
|
COUNT(DISTINCT CASE WHEN pc.type = '`+string(entities.PurchaseCategoryTypeRawMaterial)+`' THEN po.id END) as raw_material_purchase_orders,
|
||||||
|
COUNT(CASE WHEN pc.type = '`+string(entities.PurchaseCategoryTypeExpense)+`' THEN poi.id END) as expense_count,
|
||||||
|
COALESCE(SUM(poi.quantity), 0) as quantity,
|
||||||
|
COUNT(DISTINCT i.id) as ingredients,
|
||||||
|
COUNT(DISTINCT COALESCE(po.vendor_id::text, 'no-vendor')) as vendors
|
||||||
|
`).
|
||||||
|
Joins("LEFT JOIN outlets o ON po.outlet_id = o.id").
|
||||||
|
Joins("LEFT JOIN purchase_order_items poi ON poi.purchase_order_id = po.id").
|
||||||
|
Joins("LEFT JOIN purchase_categories pc ON poi.purchase_category_id = pc.id").
|
||||||
|
Joins("LEFT JOIN ingredients i ON poi.ingredient_id = i.id").
|
||||||
|
Joins("LEFT JOIN units u ON poi.unit_id = u.id").
|
||||||
|
Where("po.organization_id = ?", organizationID).
|
||||||
|
Where("po.status != ?", "cancelled").
|
||||||
|
Where("po.transaction_date >= ? AND po.transaction_date <= ?", dateFrom, dateTo).
|
||||||
|
Group("po.outlet_id, COALESCE(o.name, 'No Outlet')").
|
||||||
|
Order("outlet_name ASC")
|
||||||
|
outletQuery = r.applyPurchaseOrderItemOutletFilter(outletQuery, outletID)
|
||||||
|
|
||||||
|
if err := outletQuery.Scan(&outletData).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
dataQuery := r.db.WithContext(ctx).
|
dataQuery := r.db.WithContext(ctx).
|
||||||
Table("purchase_orders po").
|
Table("purchase_orders po").
|
||||||
Select(`
|
Select(`
|
||||||
@ -253,6 +286,7 @@ func (r *AnalyticsRepositoryImpl) getPurchaseOrderPurchasingAnalytics(ctx contex
|
|||||||
if err := dataQuery.Scan(&data).Error; err != nil {
|
if err := dataQuery.Scan(&data).Error; err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var ingredientData []entities.PurchasingIngredientData
|
var ingredientData []entities.PurchasingIngredientData
|
||||||
ingredientQuery := r.db.WithContext(ctx).
|
ingredientQuery := r.db.WithContext(ctx).
|
||||||
@ -315,6 +349,7 @@ func (r *AnalyticsRepositoryImpl) getPurchaseOrderPurchasingAnalytics(ctx contex
|
|||||||
OutletName: outletName,
|
OutletName: outletName,
|
||||||
Summary: summary,
|
Summary: summary,
|
||||||
Data: data,
|
Data: data,
|
||||||
|
OutletData: outletData,
|
||||||
IngredientData: ingredientData,
|
IngredientData: ingredientData,
|
||||||
VendorData: vendorData,
|
VendorData: vendorData,
|
||||||
}, nil
|
}, nil
|
||||||
|
|||||||
@ -200,6 +200,7 @@ func (s *AnalyticsServiceImpl) validatePurchasingAnalyticsRequest(req *models.Pu
|
|||||||
"hour": true,
|
"hour": true,
|
||||||
"week": true,
|
"week": true,
|
||||||
"month": true,
|
"month": true,
|
||||||
|
"outlet_id": true,
|
||||||
}
|
}
|
||||||
if !validGroupBy[req.GroupBy] {
|
if !validGroupBy[req.GroupBy] {
|
||||||
return fmt.Errorf("invalid group_by value: %s", req.GroupBy)
|
return fmt.Errorf("invalid group_by value: %s", req.GroupBy)
|
||||||
|
|||||||
@ -132,6 +132,21 @@ func TestAnalyticsServiceGetPurchasingAnalyticsAllowsEmptyGroupBy(t *testing.T)
|
|||||||
require.NotNil(t, resp)
|
require.NotNil(t, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAnalyticsServiceGetPurchasingAnalyticsAllowsOutletGroupBy(t *testing.T) {
|
||||||
|
service := NewAnalyticsServiceImpl(analyticsProcessorStub{})
|
||||||
|
now := time.Date(2026, 5, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
|
||||||
|
resp, err := service.GetPurchasingAnalytics(context.Background(), &models.PurchasingAnalyticsRequest{
|
||||||
|
OrganizationID: uuid.New(),
|
||||||
|
DateFrom: now,
|
||||||
|
DateTo: now,
|
||||||
|
GroupBy: "outlet_id",
|
||||||
|
})
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, resp)
|
||||||
|
}
|
||||||
|
|
||||||
func TestAnalyticsServiceGetProfitLossAnalyticsValidation(t *testing.T) {
|
func TestAnalyticsServiceGetProfitLossAnalyticsValidation(t *testing.T) {
|
||||||
service := NewAnalyticsServiceImpl(analyticsProcessorStub{})
|
service := NewAnalyticsServiceImpl(analyticsProcessorStub{})
|
||||||
now := time.Date(2026, 5, 1, 0, 0, 0, 0, time.UTC)
|
now := time.Date(2026, 5, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
|||||||
@ -184,6 +184,23 @@ func PurchasingAnalyticsModelToContract(resp *models.PurchasingAnalyticsResponse
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
outletData := make([]contract.PurchasingOutletData, len(resp.OutletData))
|
||||||
|
for i, item := range resp.OutletData {
|
||||||
|
outletData[i] = contract.PurchasingOutletData{
|
||||||
|
OutletID: item.OutletID,
|
||||||
|
OutletName: item.OutletName,
|
||||||
|
Purchases: item.Purchases,
|
||||||
|
RawMaterialPurchases: item.RawMaterialPurchases,
|
||||||
|
ExpensePurchases: item.ExpensePurchases,
|
||||||
|
PurchaseOrders: item.PurchaseOrders,
|
||||||
|
RawMaterialPurchaseOrders: item.RawMaterialPurchaseOrders,
|
||||||
|
ExpenseCount: item.ExpenseCount,
|
||||||
|
Quantity: item.Quantity,
|
||||||
|
Ingredients: item.Ingredients,
|
||||||
|
Vendors: item.Vendors,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ingredientData := make([]contract.PurchasingIngredientData, len(resp.IngredientData))
|
ingredientData := make([]contract.PurchasingIngredientData, len(resp.IngredientData))
|
||||||
for i, item := range resp.IngredientData {
|
for i, item := range resp.IngredientData {
|
||||||
ingredientData[i] = contract.PurchasingIngredientData{
|
ingredientData[i] = contract.PurchasingIngredientData{
|
||||||
@ -228,6 +245,7 @@ func PurchasingAnalyticsModelToContract(resp *models.PurchasingAnalyticsResponse
|
|||||||
TotalVendors: resp.Summary.TotalVendors,
|
TotalVendors: resp.Summary.TotalVendors,
|
||||||
},
|
},
|
||||||
Data: data,
|
Data: data,
|
||||||
|
OutletData: outletData,
|
||||||
IngredientData: ingredientData,
|
IngredientData: ingredientData,
|
||||||
VendorData: vendorData,
|
VendorData: vendorData,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -105,6 +105,40 @@ func TestPurchasingAnalyticsModelToContractOmitsNilOutletName(t *testing.T) {
|
|||||||
require.NotContains(t, string(payload), "outlet_name")
|
require.NotContains(t, string(payload), "outlet_name")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPurchasingAnalyticsModelToContractCopiesOutletData(t *testing.T) {
|
||||||
|
outletID := uuid.New()
|
||||||
|
|
||||||
|
result := PurchasingAnalyticsModelToContract(&models.PurchasingAnalyticsResponse{
|
||||||
|
OrganizationID: uuid.New(),
|
||||||
|
GroupBy: "outlet_id",
|
||||||
|
OutletData: []models.PurchasingOutletData{
|
||||||
|
{
|
||||||
|
OutletID: &outletID,
|
||||||
|
OutletName: "Outlet A",
|
||||||
|
Purchases: 500,
|
||||||
|
RawMaterialPurchases: 350,
|
||||||
|
ExpensePurchases: 150,
|
||||||
|
PurchaseOrders: 4,
|
||||||
|
RawMaterialPurchaseOrders: 3,
|
||||||
|
ExpenseCount: 2,
|
||||||
|
Quantity: 10,
|
||||||
|
Ingredients: 5,
|
||||||
|
Vendors: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
require.NotNil(t, result)
|
||||||
|
require.Equal(t, "outlet_id", result.GroupBy)
|
||||||
|
require.Len(t, result.OutletData, 1)
|
||||||
|
require.Equal(t, &outletID, result.OutletData[0].OutletID)
|
||||||
|
require.Equal(t, "Outlet A", result.OutletData[0].OutletName)
|
||||||
|
require.Equal(t, float64(500), result.OutletData[0].Purchases)
|
||||||
|
require.Equal(t, float64(350), result.OutletData[0].RawMaterialPurchases)
|
||||||
|
require.Equal(t, float64(150), result.OutletData[0].ExpensePurchases)
|
||||||
|
require.Equal(t, int64(4), result.OutletData[0].PurchaseOrders)
|
||||||
|
}
|
||||||
|
|
||||||
func TestProfitLossAnalyticsContractToModelParsesDateRange(t *testing.T) {
|
func TestProfitLossAnalyticsContractToModelParsesDateRange(t *testing.T) {
|
||||||
orgID := uuid.New()
|
orgID := uuid.New()
|
||||||
outletID := uuid.New().String()
|
outletID := uuid.New().String()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user