init
This commit is contained in:
@@ -0,0 +1,347 @@
|
||||
package processor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"apskel-pos-be/internal/models"
|
||||
"apskel-pos-be/internal/repository"
|
||||
)
|
||||
|
||||
type AnalyticsProcessor 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)
|
||||
GetDashboardAnalytics(ctx context.Context, req *models.DashboardAnalyticsRequest) (*models.DashboardAnalyticsResponse, error)
|
||||
GetProfitLossAnalytics(ctx context.Context, req *models.ProfitLossAnalyticsRequest) (*models.ProfitLossAnalyticsResponse, error)
|
||||
}
|
||||
|
||||
type AnalyticsProcessorImpl struct {
|
||||
analyticsRepo repository.AnalyticsRepository
|
||||
}
|
||||
|
||||
func NewAnalyticsProcessorImpl(analyticsRepo repository.AnalyticsRepository) *AnalyticsProcessorImpl {
|
||||
return &AnalyticsProcessorImpl{
|
||||
analyticsRepo: analyticsRepo,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *AnalyticsProcessorImpl) GetPaymentMethodAnalytics(ctx context.Context, req *models.PaymentMethodAnalyticsRequest) (*models.PaymentMethodAnalyticsResponse, error) {
|
||||
if req.DateFrom.After(req.DateTo) {
|
||||
return nil, fmt.Errorf("date_from cannot be after date_to")
|
||||
}
|
||||
|
||||
analyticsData, err := p.analyticsRepo.GetPaymentMethodAnalytics(ctx, req.OrganizationID, req.OutletID, req.DateFrom, req.DateTo)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get payment method analytics: %w", err)
|
||||
}
|
||||
|
||||
// Calculate summary
|
||||
var totalAmount float64
|
||||
var totalOrders int64
|
||||
var totalPayments int64
|
||||
|
||||
for _, data := range analyticsData {
|
||||
totalAmount += data.TotalAmount
|
||||
totalOrders += data.OrderCount
|
||||
totalPayments += data.PaymentCount
|
||||
}
|
||||
|
||||
var averageOrderValue float64
|
||||
if totalOrders > 0 {
|
||||
averageOrderValue = totalAmount / float64(totalOrders)
|
||||
}
|
||||
|
||||
// Calculate percentages
|
||||
var resultData []models.PaymentMethodAnalyticsData
|
||||
for _, data := range analyticsData {
|
||||
var percentage float64
|
||||
if totalAmount > 0 {
|
||||
percentage = (data.TotalAmount / totalAmount) * 100
|
||||
}
|
||||
|
||||
resultData = append(resultData, models.PaymentMethodAnalyticsData{
|
||||
PaymentMethodID: data.PaymentMethodID,
|
||||
PaymentMethodName: data.PaymentMethodName,
|
||||
PaymentMethodType: data.PaymentMethodType,
|
||||
TotalAmount: data.TotalAmount,
|
||||
OrderCount: data.OrderCount,
|
||||
PaymentCount: data.PaymentCount,
|
||||
Percentage: percentage,
|
||||
})
|
||||
}
|
||||
|
||||
summary := models.PaymentMethodSummary{
|
||||
TotalAmount: totalAmount,
|
||||
TotalOrders: totalOrders,
|
||||
TotalPayments: totalPayments,
|
||||
AverageOrderValue: averageOrderValue,
|
||||
}
|
||||
|
||||
return &models.PaymentMethodAnalyticsResponse{
|
||||
OrganizationID: req.OrganizationID,
|
||||
OutletID: req.OutletID,
|
||||
DateFrom: req.DateFrom,
|
||||
DateTo: req.DateTo,
|
||||
GroupBy: req.GroupBy,
|
||||
Summary: summary,
|
||||
Data: resultData,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *AnalyticsProcessorImpl) GetSalesAnalytics(ctx context.Context, req *models.SalesAnalyticsRequest) (*models.SalesAnalyticsResponse, error) {
|
||||
// Validate date range
|
||||
if req.DateFrom.After(req.DateTo) {
|
||||
return nil, fmt.Errorf("date_from cannot be after date_to")
|
||||
}
|
||||
|
||||
// Validate groupBy
|
||||
if req.GroupBy == "" {
|
||||
req.GroupBy = "day"
|
||||
}
|
||||
|
||||
// Get analytics data from repository
|
||||
analyticsData, err := p.analyticsRepo.GetSalesAnalytics(ctx, req.OrganizationID, req.OutletID, req.DateFrom, req.DateTo, req.GroupBy)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get sales analytics: %w", err)
|
||||
}
|
||||
|
||||
// Calculate summary
|
||||
var totalSales float64
|
||||
var totalOrders int64
|
||||
var totalItems int64
|
||||
var totalTax float64
|
||||
var totalDiscount float64
|
||||
var netSales float64
|
||||
|
||||
for _, data := range analyticsData {
|
||||
totalSales += data.Sales
|
||||
totalOrders += data.Orders
|
||||
totalItems += data.Items
|
||||
totalTax += data.Tax
|
||||
totalDiscount += data.Discount
|
||||
netSales += data.NetSales
|
||||
}
|
||||
|
||||
var averageOrderValue float64
|
||||
if totalOrders > 0 {
|
||||
averageOrderValue = totalSales / float64(totalOrders)
|
||||
}
|
||||
|
||||
// Transform data
|
||||
var resultData []models.SalesAnalyticsData
|
||||
for _, data := range analyticsData {
|
||||
resultData = append(resultData, models.SalesAnalyticsData{
|
||||
Date: data.Date,
|
||||
Sales: data.Sales,
|
||||
Orders: data.Orders,
|
||||
Items: data.Items,
|
||||
Tax: data.Tax,
|
||||
Discount: data.Discount,
|
||||
NetSales: data.NetSales,
|
||||
})
|
||||
}
|
||||
|
||||
summary := models.SalesSummary{
|
||||
TotalSales: totalSales,
|
||||
TotalOrders: totalOrders,
|
||||
TotalItems: totalItems,
|
||||
AverageOrderValue: averageOrderValue,
|
||||
TotalTax: totalTax,
|
||||
TotalDiscount: totalDiscount,
|
||||
NetSales: netSales,
|
||||
}
|
||||
|
||||
return &models.SalesAnalyticsResponse{
|
||||
OrganizationID: req.OrganizationID,
|
||||
OutletID: req.OutletID,
|
||||
DateFrom: req.DateFrom,
|
||||
DateTo: req.DateTo,
|
||||
GroupBy: req.GroupBy,
|
||||
Summary: summary,
|
||||
Data: resultData,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *AnalyticsProcessorImpl) GetProductAnalytics(ctx context.Context, req *models.ProductAnalyticsRequest) (*models.ProductAnalyticsResponse, error) {
|
||||
// Validate date range
|
||||
if req.DateFrom.After(req.DateTo) {
|
||||
return nil, fmt.Errorf("date_from cannot be after date_to")
|
||||
}
|
||||
|
||||
// Set default limit
|
||||
if req.Limit <= 0 {
|
||||
req.Limit = 10
|
||||
}
|
||||
|
||||
// Get analytics data from repository
|
||||
analyticsData, err := p.analyticsRepo.GetProductAnalytics(ctx, req.OrganizationID, req.OutletID, req.DateFrom, req.DateTo, req.Limit)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get product analytics: %w", err)
|
||||
}
|
||||
|
||||
// Transform data
|
||||
var resultData []models.ProductAnalyticsData
|
||||
for _, data := range analyticsData {
|
||||
resultData = append(resultData, models.ProductAnalyticsData{
|
||||
ProductID: data.ProductID,
|
||||
ProductName: data.ProductName,
|
||||
CategoryID: data.CategoryID,
|
||||
CategoryName: data.CategoryName,
|
||||
QuantitySold: data.QuantitySold,
|
||||
Revenue: data.Revenue,
|
||||
AveragePrice: data.AveragePrice,
|
||||
OrderCount: data.OrderCount,
|
||||
})
|
||||
}
|
||||
|
||||
return &models.ProductAnalyticsResponse{
|
||||
OrganizationID: req.OrganizationID,
|
||||
OutletID: req.OutletID,
|
||||
DateFrom: req.DateFrom,
|
||||
DateTo: req.DateTo,
|
||||
Data: resultData,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *AnalyticsProcessorImpl) GetDashboardAnalytics(ctx context.Context, req *models.DashboardAnalyticsRequest) (*models.DashboardAnalyticsResponse, error) {
|
||||
// Validate date range
|
||||
if req.DateFrom.After(req.DateTo) {
|
||||
return nil, fmt.Errorf("date_from cannot be after date_to")
|
||||
}
|
||||
|
||||
// Get dashboard overview
|
||||
overview, err := p.analyticsRepo.GetDashboardOverview(ctx, req.OrganizationID, req.OutletID, req.DateFrom, req.DateTo)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get dashboard overview: %w", err)
|
||||
}
|
||||
|
||||
// Get top products (limit to 5 for dashboard)
|
||||
productReq := &models.ProductAnalyticsRequest{
|
||||
OrganizationID: req.OrganizationID,
|
||||
OutletID: req.OutletID,
|
||||
DateFrom: req.DateFrom,
|
||||
DateTo: req.DateTo,
|
||||
Limit: 5,
|
||||
}
|
||||
topProducts, err := p.GetProductAnalytics(ctx, productReq)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get top products: %w", err)
|
||||
}
|
||||
|
||||
// Get payment methods
|
||||
paymentReq := &models.PaymentMethodAnalyticsRequest{
|
||||
OrganizationID: req.OrganizationID,
|
||||
OutletID: req.OutletID,
|
||||
DateFrom: req.DateFrom,
|
||||
DateTo: req.DateTo,
|
||||
GroupBy: "day",
|
||||
}
|
||||
paymentMethods, err := p.GetPaymentMethodAnalytics(ctx, paymentReq)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get payment methods: %w", err)
|
||||
}
|
||||
|
||||
// Get recent sales (last 7 days)
|
||||
recentDateFrom := time.Now().AddDate(0, 0, -7)
|
||||
salesReq := &models.SalesAnalyticsRequest{
|
||||
OrganizationID: req.OrganizationID,
|
||||
OutletID: req.OutletID,
|
||||
DateFrom: recentDateFrom,
|
||||
DateTo: req.DateTo,
|
||||
GroupBy: "day",
|
||||
}
|
||||
recentSales, err := p.GetSalesAnalytics(ctx, salesReq)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get recent sales: %w", err)
|
||||
}
|
||||
|
||||
return &models.DashboardAnalyticsResponse{
|
||||
OrganizationID: req.OrganizationID,
|
||||
OutletID: req.OutletID,
|
||||
DateFrom: req.DateFrom,
|
||||
DateTo: req.DateTo,
|
||||
Overview: models.DashboardOverview{
|
||||
TotalSales: overview.TotalSales,
|
||||
TotalOrders: overview.TotalOrders,
|
||||
AverageOrderValue: overview.AverageOrderValue,
|
||||
TotalCustomers: overview.TotalCustomers,
|
||||
VoidedOrders: overview.VoidedOrders,
|
||||
RefundedOrders: overview.RefundedOrders,
|
||||
},
|
||||
TopProducts: topProducts.Data,
|
||||
PaymentMethods: paymentMethods.Data,
|
||||
RecentSales: recentSales.Data,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *AnalyticsProcessorImpl) GetProfitLossAnalytics(ctx context.Context, req *models.ProfitLossAnalyticsRequest) (*models.ProfitLossAnalyticsResponse, error) {
|
||||
if req.DateFrom.After(req.DateTo) {
|
||||
return nil, fmt.Errorf("date_from cannot be after date_to")
|
||||
}
|
||||
|
||||
// Get analytics data from repository
|
||||
result, err := p.analyticsRepo.GetProfitLossAnalytics(ctx, req.OrganizationID, req.OutletID, req.DateFrom, req.DateTo, req.GroupBy)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get profit/loss analytics: %w", err)
|
||||
}
|
||||
|
||||
// Transform entities to models
|
||||
data := make([]models.ProfitLossData, len(result.Data))
|
||||
for i, item := range result.Data {
|
||||
data[i] = models.ProfitLossData{
|
||||
Date: item.Date,
|
||||
Revenue: item.Revenue,
|
||||
Cost: item.Cost,
|
||||
GrossProfit: item.GrossProfit,
|
||||
GrossProfitMargin: item.GrossProfitMargin,
|
||||
Tax: item.Tax,
|
||||
Discount: item.Discount,
|
||||
NetProfit: item.NetProfit,
|
||||
NetProfitMargin: item.NetProfitMargin,
|
||||
Orders: item.Orders,
|
||||
}
|
||||
}
|
||||
|
||||
productData := make([]models.ProductProfitData, len(result.ProductData))
|
||||
for i, item := range result.ProductData {
|
||||
productData[i] = models.ProductProfitData{
|
||||
ProductID: item.ProductID,
|
||||
ProductName: item.ProductName,
|
||||
CategoryID: item.CategoryID,
|
||||
CategoryName: item.CategoryName,
|
||||
QuantitySold: item.QuantitySold,
|
||||
Revenue: item.Revenue,
|
||||
Cost: item.Cost,
|
||||
GrossProfit: item.GrossProfit,
|
||||
GrossProfitMargin: item.GrossProfitMargin,
|
||||
AveragePrice: item.AveragePrice,
|
||||
AverageCost: item.AverageCost,
|
||||
ProfitPerUnit: item.ProfitPerUnit,
|
||||
}
|
||||
}
|
||||
|
||||
return &models.ProfitLossAnalyticsResponse{
|
||||
OrganizationID: req.OrganizationID,
|
||||
OutletID: req.OutletID,
|
||||
DateFrom: req.DateFrom,
|
||||
DateTo: req.DateTo,
|
||||
GroupBy: req.GroupBy,
|
||||
Summary: models.ProfitLossSummary{
|
||||
TotalRevenue: result.Summary.TotalRevenue,
|
||||
TotalCost: result.Summary.TotalCost,
|
||||
GrossProfit: result.Summary.GrossProfit,
|
||||
GrossProfitMargin: result.Summary.GrossProfitMargin,
|
||||
TotalTax: result.Summary.TotalTax,
|
||||
TotalDiscount: result.Summary.TotalDiscount,
|
||||
NetProfit: result.Summary.NetProfit,
|
||||
NetProfitMargin: result.Summary.NetProfitMargin,
|
||||
TotalOrders: result.Summary.TotalOrders,
|
||||
AverageProfit: result.Summary.AverageProfit,
|
||||
ProfitabilityRatio: result.Summary.ProfitabilityRatio,
|
||||
},
|
||||
Data: data,
|
||||
ProductData: productData,
|
||||
}, nil
|
||||
}
|
||||
Reference in New Issue
Block a user