add purchasing in analytics endpoint
This commit is contained in:
@@ -13,6 +13,7 @@ import (
|
||||
type AnalyticsService interface {
|
||||
GetPaymentMethodAnalytics(ctx context.Context, req *models.PaymentMethodAnalyticsRequest) (*models.PaymentMethodAnalyticsResponse, error)
|
||||
GetSalesAnalytics(ctx context.Context, req *models.SalesAnalyticsRequest) (*models.SalesAnalyticsResponse, error)
|
||||
GetPurchasingAnalytics(ctx context.Context, req *models.PurchasingAnalyticsRequest) (*models.PurchasingAnalyticsResponse, error)
|
||||
GetProductAnalytics(ctx context.Context, req *models.ProductAnalyticsRequest) (*models.ProductAnalyticsResponse, error)
|
||||
GetProductAnalyticsPerCategory(ctx context.Context, req *models.ProductAnalyticsPerCategoryRequest) (*models.ProductAnalyticsPerCategoryResponse, error)
|
||||
GetDashboardAnalytics(ctx context.Context, req *models.DashboardAnalyticsRequest) (*models.DashboardAnalyticsResponse, error)
|
||||
@@ -57,6 +58,19 @@ func (s *AnalyticsServiceImpl) GetSalesAnalytics(ctx context.Context, req *model
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (s *AnalyticsServiceImpl) GetPurchasingAnalytics(ctx context.Context, req *models.PurchasingAnalyticsRequest) (*models.PurchasingAnalyticsResponse, error) {
|
||||
if err := s.validatePurchasingAnalyticsRequest(req); err != nil {
|
||||
return nil, fmt.Errorf("validation error: %w", err)
|
||||
}
|
||||
|
||||
response, err := s.analyticsProcessor.GetPurchasingAnalytics(ctx, req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get purchasing analytics: %w", err)
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (s *AnalyticsServiceImpl) GetProductAnalytics(ctx context.Context, req *models.ProductAnalyticsRequest) (*models.ProductAnalyticsResponse, error) {
|
||||
// Validate request
|
||||
if err := s.validateProductAnalyticsRequest(req); err != nil {
|
||||
@@ -168,6 +182,42 @@ func (s *AnalyticsServiceImpl) validateSalesAnalyticsRequest(req *models.SalesAn
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *AnalyticsServiceImpl) validatePurchasingAnalyticsRequest(req *models.PurchasingAnalyticsRequest) error {
|
||||
if req == nil {
|
||||
return fmt.Errorf("request cannot be nil")
|
||||
}
|
||||
|
||||
if req.OrganizationID == uuid.Nil {
|
||||
return fmt.Errorf("organization ID is required")
|
||||
}
|
||||
|
||||
if req.DateFrom.IsZero() {
|
||||
return fmt.Errorf("date_from is required")
|
||||
}
|
||||
|
||||
if req.DateTo.IsZero() {
|
||||
return fmt.Errorf("date_to is required")
|
||||
}
|
||||
|
||||
if req.DateFrom.After(req.DateTo) {
|
||||
return fmt.Errorf("date_from cannot be after date_to")
|
||||
}
|
||||
|
||||
if req.GroupBy != "" {
|
||||
validGroupBy := map[string]bool{
|
||||
"day": true,
|
||||
"hour": true,
|
||||
"week": true,
|
||||
"month": true,
|
||||
}
|
||||
if !validGroupBy[req.GroupBy] {
|
||||
return fmt.Errorf("invalid group_by value: %s", req.GroupBy)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *AnalyticsServiceImpl) validateProductAnalyticsRequest(req *models.ProductAnalyticsRequest) error {
|
||||
if req.OrganizationID == uuid.Nil {
|
||||
return fmt.Errorf("organization ID is required")
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"apskel-pos-be/internal/models"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type analyticsProcessorStub struct{}
|
||||
|
||||
func (analyticsProcessorStub) GetPaymentMethodAnalytics(context.Context, *models.PaymentMethodAnalyticsRequest) (*models.PaymentMethodAnalyticsResponse, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (analyticsProcessorStub) GetSalesAnalytics(context.Context, *models.SalesAnalyticsRequest) (*models.SalesAnalyticsResponse, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (analyticsProcessorStub) GetPurchasingAnalytics(context.Context, *models.PurchasingAnalyticsRequest) (*models.PurchasingAnalyticsResponse, error) {
|
||||
return &models.PurchasingAnalyticsResponse{}, nil
|
||||
}
|
||||
|
||||
func (analyticsProcessorStub) GetProductAnalytics(context.Context, *models.ProductAnalyticsRequest) (*models.ProductAnalyticsResponse, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (analyticsProcessorStub) GetProductAnalyticsPerCategory(context.Context, *models.ProductAnalyticsPerCategoryRequest) (*models.ProductAnalyticsPerCategoryResponse, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (analyticsProcessorStub) GetDashboardAnalytics(context.Context, *models.DashboardAnalyticsRequest) (*models.DashboardAnalyticsResponse, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (analyticsProcessorStub) GetProfitLossAnalytics(context.Context, *models.ProfitLossAnalyticsRequest) (*models.ProfitLossAnalyticsResponse, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func TestAnalyticsServiceGetPurchasingAnalyticsValidation(t *testing.T) {
|
||||
service := NewAnalyticsServiceImpl(analyticsProcessorStub{})
|
||||
now := time.Date(2026, 5, 1, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
req *models.PurchasingAnalyticsRequest
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "missing organization",
|
||||
req: &models.PurchasingAnalyticsRequest{
|
||||
DateFrom: now,
|
||||
DateTo: now,
|
||||
},
|
||||
wantErr: "organization ID is required",
|
||||
},
|
||||
{
|
||||
name: "missing date_from",
|
||||
req: &models.PurchasingAnalyticsRequest{
|
||||
OrganizationID: uuid.New(),
|
||||
DateTo: now,
|
||||
},
|
||||
wantErr: "date_from is required",
|
||||
},
|
||||
{
|
||||
name: "missing date_to",
|
||||
req: &models.PurchasingAnalyticsRequest{
|
||||
OrganizationID: uuid.New(),
|
||||
DateFrom: now,
|
||||
},
|
||||
wantErr: "date_to is required",
|
||||
},
|
||||
{
|
||||
name: "reversed dates",
|
||||
req: &models.PurchasingAnalyticsRequest{
|
||||
OrganizationID: uuid.New(),
|
||||
DateFrom: now.AddDate(0, 0, 1),
|
||||
DateTo: now,
|
||||
},
|
||||
wantErr: "date_from cannot be after date_to",
|
||||
},
|
||||
{
|
||||
name: "invalid group_by",
|
||||
req: &models.PurchasingAnalyticsRequest{
|
||||
OrganizationID: uuid.New(),
|
||||
DateFrom: now,
|
||||
DateTo: now,
|
||||
GroupBy: "quarter",
|
||||
},
|
||||
wantErr: "invalid group_by value: quarter",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
resp, err := service.GetPurchasingAnalytics(context.Background(), tt.req)
|
||||
|
||||
require.Nil(t, resp)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), tt.wantErr)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnalyticsServiceGetPurchasingAnalyticsAllowsEmptyGroupBy(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,
|
||||
})
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, resp)
|
||||
}
|
||||
@@ -199,7 +199,7 @@ func (s *OrderServiceImpl) createIngredientTransactions(ctx context.Context, ord
|
||||
// Calculate waste quantities
|
||||
transactions, err := s.calculateWasteQuantities(productRecipes, float64(orderItem.Quantity))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to calculate waste quantities for product %s: %w", err)
|
||||
return nil, fmt.Errorf("failed to calculate waste quantities for product %s: %w", orderItem.ProductID, err)
|
||||
}
|
||||
|
||||
// Set common fields for all transactions
|
||||
|
||||
@@ -114,6 +114,14 @@ func (m *MockTableRepository) GetByID(ctx context.Context, id uuid.UUID) (*entit
|
||||
return args.Get(0).(*entities.Table), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockTableRepository) GetByToken(ctx context.Context, token string) (*entities.Table, error) {
|
||||
args := m.Called(ctx, token)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
return args.Get(0).(*entities.Table), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockTableRepository) GetByOutletID(ctx context.Context, outletID uuid.UUID) ([]entities.Table, error) {
|
||||
args := m.Called(ctx, outletID)
|
||||
if args.Get(0) == nil {
|
||||
@@ -182,6 +190,11 @@ func (m *MockTableRepository) GetByOrderID(ctx context.Context, orderID uuid.UUI
|
||||
return args.Get(0).(*entities.Table), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockTableRepository) UpdateToken(ctx context.Context, tableID uuid.UUID, token string) error {
|
||||
args := m.Called(ctx, tableID, token)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func TestCreateOrderWithTableOccupation(t *testing.T) {
|
||||
// Setup
|
||||
ctx := context.Background()
|
||||
|
||||
Reference in New Issue
Block a user