feat(purchase
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"apskel-pos-be/internal/constants"
|
||||
"apskel-pos-be/internal/entities"
|
||||
|
||||
"github.com/google/uuid"
|
||||
@@ -15,7 +16,7 @@ import (
|
||||
type AnalyticsRepository interface {
|
||||
GetPaymentMethodAnalytics(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID, dateFrom, dateTo time.Time) ([]*entities.PaymentMethodAnalytics, error)
|
||||
GetSalesAnalytics(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID, dateFrom, dateTo time.Time, groupBy string) ([]*entities.SalesAnalytics, error)
|
||||
GetPurchasingAnalytics(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID, dateFrom, dateTo time.Time, groupBy string) (*entities.PurchasingAnalytics, error)
|
||||
GetPurchasingAnalytics(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID, team *entities.PurchaseTeamFilter, dateFrom, dateTo time.Time, groupBy string) (*entities.PurchasingAnalytics, error)
|
||||
GetProductAnalytics(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID, dateFrom, dateTo time.Time, limit int) ([]*entities.ProductAnalytics, error)
|
||||
GetProductAnalyticsPerCategory(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID, dateFrom, dateTo time.Time) ([]*entities.ProductAnalyticsPerCategory, error)
|
||||
GetProductAnalyticsPerParentCategory(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID, dateFrom, dateTo time.Time) ([]*entities.ProductAnalyticsPerParentCategory, error)
|
||||
@@ -159,7 +160,7 @@ func (r *AnalyticsRepositoryImpl) GetSalesAnalytics(ctx context.Context, organiz
|
||||
return results, err
|
||||
}
|
||||
|
||||
func (r *AnalyticsRepositoryImpl) GetPurchasingAnalytics(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID, dateFrom, dateTo time.Time, groupBy string) (*entities.PurchasingAnalytics, error) {
|
||||
func (r *AnalyticsRepositoryImpl) GetPurchasingAnalytics(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID, team *entities.PurchaseTeamFilter, dateFrom, dateTo time.Time, groupBy string) (*entities.PurchasingAnalytics, error) {
|
||||
var outletName *string
|
||||
|
||||
if outletID != nil {
|
||||
@@ -179,10 +180,10 @@ func (r *AnalyticsRepositoryImpl) GetPurchasingAnalytics(ctx context.Context, or
|
||||
outletName = &outlet.Name
|
||||
}
|
||||
}
|
||||
return r.getPurchaseOrderPurchasingAnalytics(ctx, organizationID, outletID, outletName, dateFrom, dateTo, groupBy)
|
||||
return r.getPurchaseOrderPurchasingAnalytics(ctx, organizationID, outletID, team, outletName, dateFrom, dateTo, groupBy)
|
||||
}
|
||||
|
||||
func (r *AnalyticsRepositoryImpl) getPurchaseOrderPurchasingAnalytics(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID, outletName *string, dateFrom, dateTo time.Time, groupBy string) (*entities.PurchasingAnalytics, error) {
|
||||
func (r *AnalyticsRepositoryImpl) getPurchaseOrderPurchasingAnalytics(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID, team *entities.PurchaseTeamFilter, outletName *string, dateFrom, dateTo time.Time, groupBy string) (*entities.PurchasingAnalytics, error) {
|
||||
var summary entities.PurchasingSummary
|
||||
summaryQuery := r.db.WithContext(ctx).
|
||||
Table("purchase_orders po").
|
||||
@@ -210,6 +211,7 @@ func (r *AnalyticsRepositoryImpl) getPurchaseOrderPurchasingAnalytics(ctx contex
|
||||
Where("po.status != ?", "cancelled").
|
||||
Where("po.transaction_date >= ? AND po.transaction_date <= ?", dateFrom, dateTo)
|
||||
summaryQuery = r.applyPurchaseOrderItemOutletFilter(summaryQuery, outletID)
|
||||
summaryQuery = r.applyPurchaseOrderTeamFilter(summaryQuery, team)
|
||||
|
||||
if err := summaryQuery.Scan(&summary).Error; err != nil {
|
||||
return nil, err
|
||||
@@ -253,6 +255,7 @@ func (r *AnalyticsRepositoryImpl) getPurchaseOrderPurchasingAnalytics(ctx contex
|
||||
Group(dateFormat).
|
||||
Order(dateFormat)
|
||||
dataQuery = r.applyPurchaseOrderItemOutletFilter(dataQuery, outletID)
|
||||
dataQuery = r.applyPurchaseOrderTeamFilter(dataQuery, team)
|
||||
|
||||
if err := dataQuery.Scan(&data).Error; err != nil {
|
||||
return nil, err
|
||||
@@ -283,6 +286,7 @@ func (r *AnalyticsRepositoryImpl) getPurchaseOrderPurchasingAnalytics(ctx contex
|
||||
Group("i.id, i.name").
|
||||
Order("total_cost DESC")
|
||||
ingredientQuery = r.applyPurchaseOrderItemOutletFilter(ingredientQuery, outletID)
|
||||
ingredientQuery = r.applyPurchaseOrderTeamFilter(ingredientQuery, team)
|
||||
|
||||
if err := ingredientQuery.Scan(&ingredientData).Error; err != nil {
|
||||
return nil, err
|
||||
@@ -310,20 +314,105 @@ func (r *AnalyticsRepositoryImpl) getPurchaseOrderPurchasingAnalytics(ctx contex
|
||||
Group("v.id, COALESCE(v.name, 'No Vendor')").
|
||||
Order("total_cost DESC")
|
||||
vendorQuery = r.applyPurchaseOrderItemOutletFilter(vendorQuery, outletID)
|
||||
vendorQuery = r.applyPurchaseOrderTeamFilter(vendorQuery, team)
|
||||
|
||||
if err := vendorQuery.Scan(&vendorData).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
teamData, err := r.getPurchaseOrderTeamBreakdown(ctx, organizationID, outletID, team, dateFrom, dateTo, summary.TotalPurchases)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
summary.TotalTeams = int64(len(teamData))
|
||||
|
||||
return &entities.PurchasingAnalytics{
|
||||
OutletName: outletName,
|
||||
Summary: summary,
|
||||
Data: data,
|
||||
IngredientData: ingredientData,
|
||||
VendorData: vendorData,
|
||||
TeamData: teamData,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// getPurchaseOrderTeamBreakdown splits the purchases over the teams they were
|
||||
// charged to. Purchases with no team are kept as their own row rather than
|
||||
// dropped, so the rows still add up to the summary total.
|
||||
func (r *AnalyticsRepositoryImpl) getPurchaseOrderTeamBreakdown(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID, team *entities.PurchaseTeamFilter, dateFrom, dateTo time.Time, totalPurchases float64) ([]entities.PurchasingTeamData, error) {
|
||||
var rows []struct {
|
||||
Scope *string
|
||||
CategoryID *uuid.UUID
|
||||
CategoryName *string
|
||||
TotalPurchases float64
|
||||
RawMaterialPurchases float64
|
||||
ExpensePurchases float64
|
||||
PurchaseOrderCount int64
|
||||
Quantity float64
|
||||
}
|
||||
|
||||
query := r.db.WithContext(ctx).
|
||||
Table("purchase_orders po").
|
||||
Select(`
|
||||
po.team_scope as scope,
|
||||
po.team_category_id as category_id,
|
||||
c.name as category_name,
|
||||
COALESCE(SUM(`+purchaseOrderItemTotalAmountSQL()+`), 0) as total_purchases,
|
||||
COALESCE(SUM(`+purchaseOrderRawMaterialAmountSQL()+`), 0) as raw_material_purchases,
|
||||
COALESCE(SUM(`+purchaseOrderExpenseAmountSQL()+`), 0) as expense_purchases,
|
||||
COUNT(DISTINCT po.id) as purchase_order_count,
|
||||
COALESCE(SUM(poi.quantity), 0) as quantity
|
||||
`).
|
||||
Joins("LEFT JOIN purchase_order_items poi ON poi.purchase_order_id = po.id").
|
||||
Joins("LEFT JOIN purchase_categories pc ON poi.purchase_category_id = pc.id").
|
||||
Joins("LEFT JOIN categories c ON po.team_category_id = c.id").
|
||||
Where("po.organization_id = ?", organizationID).
|
||||
Where("po.status != ?", "cancelled").
|
||||
Where("po.transaction_date >= ? AND po.transaction_date <= ?", dateFrom, dateTo).
|
||||
Group("po.team_scope, po.team_category_id, c.name").
|
||||
Order("total_purchases DESC")
|
||||
query = r.applyPurchaseOrderItemOutletFilter(query, outletID)
|
||||
query = r.applyPurchaseOrderTeamFilter(query, team)
|
||||
|
||||
if err := query.Scan(&rows).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
teamData := make([]entities.PurchasingTeamData, len(rows))
|
||||
for i, row := range rows {
|
||||
entry := entities.PurchasingTeamData{
|
||||
CategoryID: row.CategoryID,
|
||||
TotalPurchases: row.TotalPurchases,
|
||||
RawMaterialPurchases: row.RawMaterialPurchases,
|
||||
ExpensePurchases: row.ExpensePurchases,
|
||||
PurchaseOrderCount: row.PurchaseOrderCount,
|
||||
Quantity: row.Quantity,
|
||||
}
|
||||
|
||||
switch {
|
||||
case row.Scope == nil:
|
||||
entry.Scope = constants.PurchaseTeamNone
|
||||
entry.Name = constants.PurchaseTeamNoneName
|
||||
case *row.Scope == constants.PurchaseTeamScopeCentral:
|
||||
entry.Scope = constants.PurchaseTeamScopeCentral
|
||||
entry.Name = constants.PurchaseTeamCentralName
|
||||
default:
|
||||
entry.Scope = *row.Scope
|
||||
if row.CategoryName != nil {
|
||||
entry.Name = *row.CategoryName
|
||||
}
|
||||
}
|
||||
|
||||
if totalPurchases != 0 {
|
||||
entry.Percentage = row.TotalPurchases / totalPurchases * 100
|
||||
}
|
||||
|
||||
teamData[i] = entry
|
||||
}
|
||||
|
||||
return teamData, nil
|
||||
}
|
||||
|
||||
func (r *AnalyticsRepositoryImpl) applyPurchaseOrderItemOutletFilter(query *gorm.DB, outletID *uuid.UUID) *gorm.DB {
|
||||
if outletID == nil {
|
||||
return query
|
||||
@@ -331,6 +420,28 @@ func (r *AnalyticsRepositoryImpl) applyPurchaseOrderItemOutletFilter(query *gorm
|
||||
return query.Where("po.outlet_id = ?", *outletID)
|
||||
}
|
||||
|
||||
// applyPurchaseOrderTeamFilter narrows a purchase order query to the team the
|
||||
// report asked for. A nil filter, or a category team without a category, leaves
|
||||
// the query spanning every team.
|
||||
func (r *AnalyticsRepositoryImpl) applyPurchaseOrderTeamFilter(query *gorm.DB, team *entities.PurchaseTeamFilter) *gorm.DB {
|
||||
if team == nil {
|
||||
return query
|
||||
}
|
||||
|
||||
switch team.Scope {
|
||||
case constants.PurchaseTeamNone:
|
||||
return query.Where("po.team_scope IS NULL")
|
||||
case constants.PurchaseTeamScopeCentral:
|
||||
return query.Where("po.team_scope = ?", constants.PurchaseTeamScopeCentral)
|
||||
case constants.PurchaseTeamScopeCategory:
|
||||
if team.CategoryID != nil {
|
||||
return query.Where("po.team_scope = ? AND po.team_category_id = ?", constants.PurchaseTeamScopeCategory, *team.CategoryID)
|
||||
}
|
||||
}
|
||||
|
||||
return query
|
||||
}
|
||||
|
||||
func (r *AnalyticsRepositoryImpl) GetProductAnalytics(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID, dateFrom, dateTo time.Time, limit int) ([]*entities.ProductAnalytics, error) {
|
||||
var results []*entities.ProductAnalytics
|
||||
|
||||
|
||||
Reference in New Issue
Block a user