package processor import ( "context" "testing" "time" "apskel-pos-be/internal/entities" "github.com/google/uuid" "github.com/stretchr/testify/require" ) func jakarta(t *testing.T) *time.Location { t.Helper() loc, err := time.LoadLocation("Asia/Jakarta") require.NoError(t, err) return loc } func TestStartOfWeekLandsOnMonday(t *testing.T) { loc := jakarta(t) // 3 Aug 2026 is a Monday, so the whole week must collapse onto it monday := time.Date(2026, 8, 3, 0, 0, 0, 0, loc) for offset := 0; offset < 7; offset++ { day := monday.AddDate(0, 0, offset).Add(13 * time.Hour) got := startOfWeek(day) require.Equal(t, monday, got, "day %s should map to %s", day, monday) require.Equal(t, time.Monday, got.Weekday()) } } func TestEndOfWeekLandsOnSunday(t *testing.T) { loc := jakarta(t) // Sunday 9 Aug 2026 closes the week that starts Monday 3 Aug got := endOfWeek(time.Date(2026, 8, 5, 9, 30, 0, 0, loc)) require.Equal(t, time.Sunday, got.Weekday()) require.Equal(t, 2026, got.Year()) require.Equal(t, time.August, got.Month()) require.Equal(t, 9, got.Day()) require.Equal(t, 23, got.Hour()) require.Equal(t, 59, got.Minute()) } func TestBuildBudgetCutOffWidensToWholeWeeks(t *testing.T) { loc := jakarta(t) processor := &AnalyticsProcessorImpl{analyticsRepo: &analyticsRepositoryStub{}} // Saturday 1 Aug to Monday 31 Aug 2026: both ends fall mid-week from := time.Date(2026, 8, 1, 0, 0, 0, 0, loc) to := time.Date(2026, 8, 31, 23, 59, 59, 0, loc) budget, err := processor.buildBudgetCutOff(context.Background(), uuid.New(), nil, nil, from, to) require.NoError(t, err) // Reaches back into July and forward into September to keep weeks whole require.Equal(t, time.Monday, budget.CutOffFrom.Weekday()) require.Equal(t, time.July, budget.CutOffFrom.Month()) require.Equal(t, 27, budget.CutOffFrom.Day()) require.Equal(t, time.Sunday, budget.CutOffTo.Weekday()) require.Equal(t, time.September, budget.CutOffTo.Month()) require.Equal(t, 6, budget.CutOffTo.Day()) require.Len(t, budget.Weekly, 6) for _, week := range budget.Weekly { require.Equal(t, time.Monday, week.PeriodStart.Weekday()) require.Equal(t, time.Sunday, week.PeriodEnd.Weekday()) } // A week is filed under the month of its Monday, so the 27 Jul week counts as July require.Len(t, budget.Monthly, 2) require.Equal(t, "2026-07", budget.Monthly[0].Month) require.Equal(t, 1, budget.Monthly[0].WeekCount) require.Equal(t, "2026-08", budget.Monthly[1].Month) require.Equal(t, 5, budget.Monthly[1].WeekCount) } func TestBuildBudgetCutOffAppliesLimits(t *testing.T) { loc := jakarta(t) weekStart := time.Date(2026, 8, 3, 0, 0, 0, 0, loc) stub := &analyticsRepositoryStub{budgetCutOffWeeks: []*entities.BudgetCutOffWeek{ {WeekStart: weekStart, Revenue: 10_000_000, OrderCount: 120}, }} processor := &AnalyticsProcessorImpl{analyticsRepo: stub} budget, err := processor.buildBudgetCutOff(context.Background(), uuid.New(), nil, nil, weekStart, weekStart.AddDate(0, 0, 6)) require.NoError(t, err) require.Len(t, budget.Weekly, 1) // 60 / 20 / 20 of the week's revenue week := budget.Weekly[0] require.Equal(t, float64(6_000_000), week.LimitPurchase) require.Equal(t, float64(2_000_000), week.LimitOwner) require.Equal(t, float64(2_000_000), week.LimitTeam) require.Equal(t, int64(120), week.OrderCount) // Totals mirror the single week require.Equal(t, week.Revenue, budget.Total.Revenue) require.Equal(t, week.LimitPurchase, budget.Total.LimitPurchase) } func TestBuildBudgetCutOffAccumulatesMonthlyFromWeeks(t *testing.T) { loc := jakarta(t) first := time.Date(2026, 8, 3, 0, 0, 0, 0, loc) stub := &analyticsRepositoryStub{budgetCutOffWeeks: []*entities.BudgetCutOffWeek{ {WeekStart: first, Revenue: 10_000_000, OrderCount: 100}, {WeekStart: first.AddDate(0, 0, 7), Revenue: 5_000_000, OrderCount: 60}, }} processor := &AnalyticsProcessorImpl{analyticsRepo: stub} budget, err := processor.buildBudgetCutOff(context.Background(), uuid.New(), nil, nil, first, first.AddDate(0, 0, 9)) require.NoError(t, err) require.Len(t, budget.Monthly, 1) month := budget.Monthly[0] require.Equal(t, "2026-08", month.Month) require.Equal(t, 2, month.WeekCount) require.Equal(t, float64(15_000_000), month.Revenue) require.Equal(t, int64(160), month.OrderCount) // The month limit is the accumulation of its weeks require.Equal(t, float64(9_000_000), month.LimitPurchase) require.Equal(t, budget.Weekly[0].LimitPurchase+budget.Weekly[1].LimitPurchase, month.LimitPurchase) } func TestBuildBudgetCutOffEmitsWeeksWithoutSales(t *testing.T) { loc := jakarta(t) first := time.Date(2026, 8, 3, 0, 0, 0, 0, loc) // Only the third week has sales; the two quiet weeks must still be reported stub := &analyticsRepositoryStub{budgetCutOffWeeks: []*entities.BudgetCutOffWeek{ {WeekStart: first.AddDate(0, 0, 14), Revenue: 4_000_000, OrderCount: 40}, }} processor := &AnalyticsProcessorImpl{analyticsRepo: stub} budget, err := processor.buildBudgetCutOff(context.Background(), uuid.New(), nil, nil, first, first.AddDate(0, 0, 16)) require.NoError(t, err) require.Len(t, budget.Weekly, 3) require.Zero(t, budget.Weekly[0].Revenue) require.Zero(t, budget.Weekly[0].LimitPurchase) require.Zero(t, budget.Weekly[1].Revenue) require.Equal(t, float64(4_000_000), budget.Weekly[2].Revenue) require.Equal(t, float64(4_000_000), budget.Total.Revenue) }