276 lines
10 KiB
Go
276 lines
10 KiB
Go
package processor
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"apskel-pos-be/internal/entities"
|
|
"apskel-pos-be/internal/models"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
type analyticsRepositoryStub struct {
|
|
purchasingResult *entities.PurchasingAnalytics
|
|
profitLossResult *entities.ProfitLossAnalytics
|
|
profitLossGroup string
|
|
}
|
|
|
|
func (analyticsRepositoryStub) GetPaymentMethodAnalytics(context.Context, uuid.UUID, *uuid.UUID, time.Time, time.Time) ([]*entities.PaymentMethodAnalytics, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (analyticsRepositoryStub) GetSalesAnalytics(context.Context, uuid.UUID, *uuid.UUID, time.Time, time.Time, string) ([]*entities.SalesAnalytics, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (s analyticsRepositoryStub) GetPurchasingAnalytics(context.Context, uuid.UUID, *uuid.UUID, time.Time, time.Time, string) (*entities.PurchasingAnalytics, error) {
|
|
return s.purchasingResult, nil
|
|
}
|
|
|
|
func (analyticsRepositoryStub) GetProductAnalytics(context.Context, uuid.UUID, *uuid.UUID, time.Time, time.Time, int) ([]*entities.ProductAnalytics, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (analyticsRepositoryStub) GetProductAnalyticsPerCategory(context.Context, uuid.UUID, *uuid.UUID, time.Time, time.Time) ([]*entities.ProductAnalyticsPerCategory, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (analyticsRepositoryStub) GetDashboardOverview(context.Context, uuid.UUID, *uuid.UUID, time.Time, time.Time) (*entities.DashboardOverview, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (s analyticsRepositoryStub) GetProfitLossAnalytics(_ context.Context, _ uuid.UUID, _ *uuid.UUID, _, _ time.Time, groupBy string) (*entities.ProfitLossAnalytics, error) {
|
|
s.profitLossGroup = groupBy
|
|
return s.profitLossResult, nil
|
|
}
|
|
|
|
type expenseRepositoryStub struct{}
|
|
|
|
func (expenseRepositoryStub) Create(context.Context, *entities.Expense) error { return nil }
|
|
func (expenseRepositoryStub) GetByID(context.Context, uuid.UUID) (*entities.Expense, error) {
|
|
return nil, nil
|
|
}
|
|
func (expenseRepositoryStub) GetByIDAndOrganizationID(context.Context, uuid.UUID, uuid.UUID) (*entities.Expense, error) {
|
|
return nil, nil
|
|
}
|
|
func (expenseRepositoryStub) Update(context.Context, *entities.Expense) error { return nil }
|
|
func (expenseRepositoryStub) Delete(context.Context, uuid.UUID) error { return nil }
|
|
func (expenseRepositoryStub) List(context.Context, uuid.UUID, map[string]interface{}, int, int) ([]*entities.Expense, int64, error) {
|
|
return nil, 0, nil
|
|
}
|
|
func (expenseRepositoryStub) GetAnalytics(context.Context, uuid.UUID, *uuid.UUID, time.Time, time.Time, string) (*entities.ExpenseAnalytics, error) {
|
|
return nil, nil
|
|
}
|
|
func (expenseRepositoryStub) CreateItem(context.Context, *entities.ExpenseItem) error { return nil }
|
|
func (expenseRepositoryStub) DeleteItemsByExpenseID(context.Context, uuid.UUID) error { return nil }
|
|
|
|
func TestAnalyticsProcessorGetPurchasingAnalyticsPassesOutletName(t *testing.T) {
|
|
outletID := uuid.New()
|
|
outletName := "Main Outlet"
|
|
now := time.Date(2026, 5, 1, 0, 0, 0, 0, time.UTC)
|
|
processor := NewAnalyticsProcessorImpl(analyticsRepositoryStub{
|
|
purchasingResult: &entities.PurchasingAnalytics{
|
|
OutletName: &outletName,
|
|
Summary: entities.PurchasingSummary{
|
|
TotalPurchases: 300,
|
|
RawMaterialPurchases: 125,
|
|
ExpensePurchases: 175,
|
|
TotalPurchaseOrders: 3,
|
|
RawMaterialPurchaseOrders: 1,
|
|
ExpenseCount: 2,
|
|
},
|
|
Data: []entities.PurchasingAnalyticsData{
|
|
{
|
|
Date: now,
|
|
Purchases: 300,
|
|
RawMaterialPurchases: 125,
|
|
ExpensePurchases: 175,
|
|
PurchaseOrders: 3,
|
|
RawMaterialPurchaseOrders: 1,
|
|
ExpenseCount: 2,
|
|
},
|
|
},
|
|
},
|
|
}, expenseRepositoryStub{})
|
|
|
|
result, err := processor.GetPurchasingAnalytics(context.Background(), &models.PurchasingAnalyticsRequest{
|
|
OrganizationID: uuid.New(),
|
|
OutletID: &outletID,
|
|
DateFrom: now,
|
|
DateTo: now,
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
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.ExpensePurchases)
|
|
require.Equal(t, int64(3), result.Summary.TotalPurchaseOrders)
|
|
require.Equal(t, int64(1), result.Summary.RawMaterialPurchaseOrders)
|
|
require.Equal(t, int64(2), result.Summary.ExpenseCount)
|
|
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].ExpensePurchases)
|
|
}
|
|
|
|
func TestAnalyticsProcessorGetProfitLossAnalyticsMapsOverviewAndReportFields(t *testing.T) {
|
|
productID := uuid.New()
|
|
categoryID := uuid.New()
|
|
now := time.Date(2026, 5, 1, 0, 0, 0, 0, time.UTC)
|
|
processor := NewAnalyticsProcessorImpl(analyticsRepositoryStub{
|
|
profitLossResult: &entities.ProfitLossAnalytics{
|
|
Summary: entities.ProfitLossSummary{
|
|
TotalRevenue: 1000,
|
|
TotalCost: 400,
|
|
GrossProfit: 600,
|
|
GrossProfitMargin: 60,
|
|
TotalTax: 50,
|
|
TotalDiscount: 25,
|
|
NetProfit: 575,
|
|
NetProfitMargin: 57.5,
|
|
TotalOrders: 10,
|
|
AverageProfit: 57.5,
|
|
ProfitabilityRatio: 150,
|
|
},
|
|
Data: []entities.ProfitLossData{
|
|
{
|
|
Date: now,
|
|
Revenue: 1000,
|
|
Cost: 400,
|
|
GrossProfit: 600,
|
|
GrossProfitMargin: 60,
|
|
Tax: 50,
|
|
Discount: 25,
|
|
NetProfit: 575,
|
|
NetProfitMargin: 57.5,
|
|
Orders: 10,
|
|
},
|
|
},
|
|
ProductData: []entities.ProductProfitData{
|
|
{
|
|
ProductID: productID,
|
|
ProductName: "Nasi",
|
|
CategoryID: categoryID,
|
|
CategoryName: "Food",
|
|
QuantitySold: 5,
|
|
Revenue: 500,
|
|
Cost: 200,
|
|
GrossProfit: 300,
|
|
GrossProfitMargin: 60,
|
|
AveragePrice: 100,
|
|
AverageCost: 40,
|
|
ProfitPerUnit: 60,
|
|
},
|
|
},
|
|
TodayRevenue: 1000,
|
|
TodayCost: 400,
|
|
MtdRevenue: 2000,
|
|
MtdCost: 800,
|
|
},
|
|
}, expenseRepositoryStub{})
|
|
|
|
result, err := processor.GetProfitLossAnalytics(context.Background(), &models.ProfitLossAnalyticsRequest{
|
|
OrganizationID: uuid.New(),
|
|
DateFrom: now,
|
|
DateTo: now,
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, result)
|
|
require.Equal(t, "day", result.GroupBy)
|
|
require.Equal(t, float64(1000), result.Summary.TotalRevenue)
|
|
require.Len(t, result.Data, 1)
|
|
require.Equal(t, float64(575), result.Data[0].NetProfit)
|
|
require.Len(t, result.ProductData, 1)
|
|
require.Equal(t, productID, result.ProductData[0].ProductID)
|
|
require.NotEmpty(t, result.MainSummary)
|
|
require.Equal(t, "total_omset", result.MainSummary[0].ID)
|
|
}
|
|
|
|
func TestAnalyticsProcessorGetProfitLossAnalyticsDynamicExpenseCategories(t *testing.T) {
|
|
now := time.Date(2026, 5, 1, 0, 0, 0, 0, time.UTC)
|
|
processor := NewAnalyticsProcessorImpl(analyticsRepositoryStub{
|
|
profitLossResult: &entities.ProfitLossAnalytics{
|
|
Summary: entities.ProfitLossSummary{
|
|
TotalRevenue: 10000,
|
|
TotalCost: 4000,
|
|
},
|
|
TodayRevenue: 10000,
|
|
TodayCost: 4000,
|
|
MtdRevenue: 20000,
|
|
MtdCost: 8000,
|
|
TodayExpenseByCategory: []entities.ExpenseCategoryTotal{
|
|
{CategoryName: "Gaji", Amount: 1500},
|
|
{CategoryName: "Promosi", Amount: 300},
|
|
{CategoryName: "Sewa", Amount: 500},
|
|
},
|
|
MtdExpenseByCategory: []entities.ExpenseCategoryTotal{
|
|
{CategoryName: "Gaji", Amount: 3000},
|
|
{CategoryName: "Promosi", Amount: 600},
|
|
{CategoryName: "Sewa", Amount: 1000},
|
|
},
|
|
},
|
|
}, expenseRepositoryStub{})
|
|
|
|
result, err := processor.GetProfitLossAnalytics(context.Background(), &models.ProfitLossAnalyticsRequest{
|
|
OrganizationID: uuid.New(),
|
|
DateFrom: now,
|
|
DateTo: now,
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, result)
|
|
|
|
require.Len(t, result.MainSummary, 7)
|
|
|
|
require.Equal(t, "total_omset", result.MainSummary[0].ID)
|
|
require.Equal(t, float64(10000), result.MainSummary[0].TodayNominal)
|
|
require.Equal(t, float64(20000), result.MainSummary[0].MtdNominal)
|
|
|
|
require.Equal(t, "hpp", result.MainSummary[1].ID)
|
|
require.Equal(t, float64(4000), result.MainSummary[1].TodayNominal)
|
|
require.Equal(t, float64(8000), result.MainSummary[1].MtdNominal)
|
|
|
|
require.Equal(t, "laba_kotor", result.MainSummary[2].ID)
|
|
require.Equal(t, float64(6000), result.MainSummary[2].TodayNominal)
|
|
require.Equal(t, float64(12000), result.MainSummary[2].MtdNominal)
|
|
|
|
require.Equal(t, "biaya_ops", result.MainSummary[3].ID)
|
|
require.Equal(t, float64(800), result.MainSummary[3].TodayNominal)
|
|
require.Equal(t, float64(1600), result.MainSummary[3].MtdNominal)
|
|
require.Len(t, result.MainSummary[3].SubItems, 3) // 2 operational categories + 1 total
|
|
|
|
require.Equal(t, "by_promosi", result.MainSummary[3].SubItems[0].ID)
|
|
require.Equal(t, float64(300), result.MainSummary[3].SubItems[0].TodayNominal)
|
|
require.Equal(t, float64(600), result.MainSummary[3].SubItems[0].MtdNominal)
|
|
|
|
require.Equal(t, "by_sewa", result.MainSummary[3].SubItems[1].ID)
|
|
require.Equal(t, float64(500), result.MainSummary[3].SubItems[1].TodayNominal)
|
|
require.Equal(t, float64(1000), result.MainSummary[3].SubItems[1].MtdNominal)
|
|
|
|
require.Equal(t, "total_biaya_ops", result.MainSummary[3].SubItems[2].ID)
|
|
require.True(t, result.MainSummary[3].SubItems[2].IsBold)
|
|
require.Equal(t, float64(800), result.MainSummary[3].SubItems[2].TodayNominal)
|
|
require.Equal(t, float64(1600), result.MainSummary[3].SubItems[2].MtdNominal)
|
|
|
|
require.Equal(t, "laba_rugi_sblm_gaji", result.MainSummary[4].ID)
|
|
require.Equal(t, float64(5200), result.MainSummary[4].TodayNominal)
|
|
require.Equal(t, float64(10400), result.MainSummary[4].MtdNominal)
|
|
|
|
require.Equal(t, "biaya_gaji", result.MainSummary[5].ID)
|
|
require.Equal(t, float64(1500), result.MainSummary[5].TodayNominal)
|
|
require.Equal(t, float64(3000), result.MainSummary[5].MtdNominal)
|
|
|
|
require.Equal(t, "laba_rugi", result.MainSummary[6].ID)
|
|
require.Equal(t, float64(3700), result.MainSummary[6].TodayNominal)
|
|
require.Equal(t, float64(7400), result.MainSummary[6].MtdNominal)
|
|
require.True(t, result.MainSummary[6].IsBold)
|
|
}
|