apskel-pos-backend/internal/service/analytics_service.go

277 lines
7.9 KiB
Go
Raw Permalink Normal View History

2025-07-18 20:10:29 +07:00
package service
import (
"context"
"fmt"
"apskel-pos-be/internal/models"
"apskel-pos-be/internal/processor"
"github.com/google/uuid"
)
type AnalyticsService interface {
GetPaymentMethodAnalytics(ctx context.Context, req *models.PaymentMethodAnalyticsRequest) (*models.PaymentMethodAnalyticsResponse, error)
GetSalesAnalytics(ctx context.Context, req *models.SalesAnalyticsRequest) (*models.SalesAnalyticsResponse, error)
GetProductAnalytics(ctx context.Context, req *models.ProductAnalyticsRequest) (*models.ProductAnalyticsResponse, error)
2025-08-14 00:45:14 +07:00
GetProductAnalyticsPerCategory(ctx context.Context, req *models.ProductAnalyticsPerCategoryRequest) (*models.ProductAnalyticsPerCategoryResponse, error)
2025-07-18 20:10:29 +07:00
GetDashboardAnalytics(ctx context.Context, req *models.DashboardAnalyticsRequest) (*models.DashboardAnalyticsResponse, error)
GetProfitLossAnalytics(ctx context.Context, req *models.ProfitLossAnalyticsRequest) (*models.ProfitLossAnalyticsResponse, error)
}
type AnalyticsServiceImpl struct {
analyticsProcessor processor.AnalyticsProcessor
}
func NewAnalyticsServiceImpl(analyticsProcessor processor.AnalyticsProcessor) *AnalyticsServiceImpl {
return &AnalyticsServiceImpl{
analyticsProcessor: analyticsProcessor,
}
}
func (s *AnalyticsServiceImpl) GetPaymentMethodAnalytics(ctx context.Context, req *models.PaymentMethodAnalyticsRequest) (*models.PaymentMethodAnalyticsResponse, error) {
if err := s.validatePaymentMethodAnalyticsRequest(req); err != nil {
return nil, fmt.Errorf("validation error: %w", err)
}
response, err := s.analyticsProcessor.GetPaymentMethodAnalytics(ctx, req)
if err != nil {
return nil, fmt.Errorf("failed to get payment method analytics: %w", err)
}
return response, nil
}
func (s *AnalyticsServiceImpl) GetSalesAnalytics(ctx context.Context, req *models.SalesAnalyticsRequest) (*models.SalesAnalyticsResponse, error) {
// Validate request
if err := s.validateSalesAnalyticsRequest(req); err != nil {
return nil, fmt.Errorf("validation error: %w", err)
}
// Process analytics request
response, err := s.analyticsProcessor.GetSalesAnalytics(ctx, req)
if err != nil {
return nil, fmt.Errorf("failed to get sales 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 {
return nil, fmt.Errorf("validation error: %w", err)
}
// Process analytics request
response, err := s.analyticsProcessor.GetProductAnalytics(ctx, req)
if err != nil {
return nil, fmt.Errorf("failed to get product analytics: %w", err)
}
return response, nil
}
2025-08-14 00:45:14 +07:00
func (s *AnalyticsServiceImpl) GetProductAnalyticsPerCategory(ctx context.Context, req *models.ProductAnalyticsPerCategoryRequest) (*models.ProductAnalyticsPerCategoryResponse, error) {
// Validate request
if err := s.validateProductAnalyticsPerCategoryRequest(req); err != nil {
return nil, fmt.Errorf("validation error: %w", err)
}
// Process analytics request
response, err := s.analyticsProcessor.GetProductAnalyticsPerCategory(ctx, req)
if err != nil {
return nil, fmt.Errorf("failed to get product analytics per category: %w", err)
}
return response, nil
}
2025-07-18 20:10:29 +07:00
func (s *AnalyticsServiceImpl) GetDashboardAnalytics(ctx context.Context, req *models.DashboardAnalyticsRequest) (*models.DashboardAnalyticsResponse, error) {
// Validate request
if err := s.validateDashboardAnalyticsRequest(req); err != nil {
return nil, fmt.Errorf("validation error: %w", err)
}
// Process analytics request
response, err := s.analyticsProcessor.GetDashboardAnalytics(ctx, req)
if err != nil {
return nil, fmt.Errorf("failed to get dashboard analytics: %w", err)
}
return response, nil
}
// Validation methods
func (s *AnalyticsServiceImpl) validatePaymentMethodAnalyticsRequest(req *models.PaymentMethodAnalyticsRequest) error {
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) validateSalesAnalyticsRequest(req *models.SalesAnalyticsRequest) error {
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")
}
// Validate groupBy if provided
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")
}
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")
}
2025-09-22 22:12:54 +07:00
if req.Limit < 1 || req.Limit > 1000 {
return fmt.Errorf("limit must be between 1 and 1000")
2025-07-18 20:10:29 +07:00
}
return nil
}
2025-08-14 00:45:14 +07:00
func (s *AnalyticsServiceImpl) validateProductAnalyticsPerCategoryRequest(req *models.ProductAnalyticsPerCategoryRequest) error {
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")
}
return nil
}
2025-07-18 20:10:29 +07:00
func (s *AnalyticsServiceImpl) validateDashboardAnalyticsRequest(req *models.DashboardAnalyticsRequest) error {
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")
}
return nil
}
func (s *AnalyticsServiceImpl) GetProfitLossAnalytics(ctx context.Context, req *models.ProfitLossAnalyticsRequest) (*models.ProfitLossAnalyticsResponse, error) {
// Validate request
if err := s.validateProfitLossAnalyticsRequest(req); err != nil {
return nil, fmt.Errorf("validation error: %w", err)
}
// Call processor
response, err := s.analyticsProcessor.GetProfitLossAnalytics(ctx, req)
if err != nil {
return nil, fmt.Errorf("failed to get profit/loss analytics: %w", err)
}
return response, nil
}
func (s *AnalyticsServiceImpl) validateProfitLossAnalyticsRequest(req *models.ProfitLossAnalyticsRequest) 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 != "" && req.GroupBy != "hour" && req.GroupBy != "day" && req.GroupBy != "week" && req.GroupBy != "month" {
return fmt.Errorf("invalid group_by value, must be one of: hour, day, week, month")
}
return nil
}