package repository import ( "context" "time" "apskel-pos-be/internal/entities" "github.com/google/uuid" "gorm.io/gorm" ) type HPPRepository interface { GetStandardHPP(ctx context.Context, organizationID uuid.UUID, productID *uuid.UUID, categoryID *uuid.UUID) ([]*entities.StandardHPPProduct, error) GetStandardHPPIngredients(ctx context.Context, organizationID uuid.UUID, productIDs []uuid.UUID) ([]*entities.StandardHPPIngredient, error) GetRealHPP(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID, productID *uuid.UUID, categoryID *uuid.UUID, dateFrom, dateTo time.Time) ([]*entities.RealHPPProduct, error) GetRealHPPTimeSeries(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID, productID *uuid.UUID, categoryID *uuid.UUID, dateFrom, dateTo time.Time, groupBy string) ([]*entities.RealHPPTimeSeries, error) } type HPPRepositoryImpl struct { db *gorm.DB } func NewHPPRepositoryImpl(db *gorm.DB) *HPPRepositoryImpl { return &HPPRepositoryImpl{db: db} } func (r *HPPRepositoryImpl) GetStandardHPP(ctx context.Context, organizationID uuid.UUID, productID *uuid.UUID, categoryID *uuid.UUID) ([]*entities.StandardHPPProduct, error) { var results []*entities.StandardHPPProduct query := r.db.WithContext(ctx). Table("products p"). Select(` p.id as product_id, p.name as product_name, COALESCE(p.sku, '') as product_sku, c.id as category_id, c.name as category_name, p.price as selling_price, p.cost as product_cost, COALESCE(SUM( pr.quantity * i.cost * (1 + COALESCE(pr.waste_percentage, 0) / 100.0) ), 0) as standard_cost, CASE WHEN p.price > 0 THEN COALESCE(SUM(pr.quantity * i.cost * (1 + COALESCE(pr.waste_percentage, 0) / 100.0)), 0) / p.price * 100 ELSE 0 END as standard_hpp_percentage, CASE WHEN COUNT(pr.id) > 0 THEN true ELSE false END as has_recipe `). Joins("LEFT JOIN product_recipes pr ON pr.product_id = p.id AND pr.organization_id = ?", organizationID). Joins("LEFT JOIN ingredients i ON pr.ingredient_id = i.id"). Joins("JOIN categories c ON p.category_id = c.id"). Where("p.organization_id = ?", organizationID). Where("p.is_active = ?", true) if productID != nil { query = query.Where("p.id = ?", *productID) } if categoryID != nil { query = query.Where("p.category_id = ?", *categoryID) } err := query. Group("p.id, p.name, p.sku, c.id, c.name, p.price, p.cost"). Order("p.name ASC"). Scan(&results).Error return results, err } func (r *HPPRepositoryImpl) GetStandardHPPIngredients(ctx context.Context, organizationID uuid.UUID, productIDs []uuid.UUID) ([]*entities.StandardHPPIngredient, error) { if len(productIDs) == 0 { return []*entities.StandardHPPIngredient{}, nil } var results []*entities.StandardHPPIngredient err := r.db.WithContext(ctx). Table("product_recipes pr"). Select(` pr.product_id, pr.ingredient_id, i.name as ingredient_name, pr.quantity, COALESCE(u.name, '') as unit_name, i.cost as cost_per_unit, COALESCE(pr.waste_percentage, 0) as waste_percentage, pr.quantity * i.cost * (1 + COALESCE(pr.waste_percentage, 0) / 100.0) as total_cost `). Joins("JOIN ingredients i ON pr.ingredient_id = i.id"). Joins("LEFT JOIN units u ON i.unit_id = u.id"). Where("pr.organization_id = ?", organizationID). Where("pr.product_id IN ?", productIDs). Order("pr.product_id, i.name"). Scan(&results).Error return results, err } func (r *HPPRepositoryImpl) GetRealHPP(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID, productID *uuid.UUID, categoryID *uuid.UUID, dateFrom, dateTo time.Time) ([]*entities.RealHPPProduct, error) { var results []*entities.RealHPPProduct query := r.db.WithContext(ctx). Table("products p"). Select(` p.id as product_id, p.name as product_name, COALESCE(p.sku, '') as product_sku, c.id as category_id, c.name as category_name, p.price as selling_price, COALESCE(SUM( CASE WHEN oi.is_fully_refunded = false THEN oi.unit_cost * (oi.quantity - COALESCE(oi.refund_quantity, 0)) ELSE 0 END ), 0) as real_total_cost, COALESCE(SUM( CASE WHEN oi.is_fully_refunded = false THEN oi.total_price - COALESCE(oi.refund_amount, 0) ELSE 0 END ), 0) as real_total_revenue, CASE WHEN COALESCE(SUM( CASE WHEN oi.is_fully_refunded = false THEN oi.total_price - COALESCE(oi.refund_amount, 0) ELSE 0 END ), 0) > 0 THEN COALESCE(SUM( CASE WHEN oi.is_fully_refunded = false THEN oi.unit_cost * (oi.quantity - COALESCE(oi.refund_quantity, 0)) ELSE 0 END ), 0) / COALESCE(SUM( CASE WHEN oi.is_fully_refunded = false THEN oi.total_price - COALESCE(oi.refund_amount, 0) ELSE 0 END ), 0) * 100 ELSE 0 END as real_hpp_percentage, COALESCE(SUM( CASE WHEN oi.is_fully_refunded = false THEN oi.quantity - COALESCE(oi.refund_quantity, 0) ELSE 0 END ), 0) as total_quantity_sold, COALESCE(COUNT(DISTINCT oi.order_id), 0) as total_orders `). Joins("JOIN categories c ON p.category_id = c.id"). Joins("LEFT JOIN order_items oi ON oi.product_id = p.id AND oi.status != 'cancelled'"). Joins("LEFT JOIN orders o ON oi.order_id = o.id AND o.is_void = false AND o.is_refund = false AND o.status = 'completed' AND o.payment_status = 'completed' AND o.organization_id = ? AND o.created_at >= ? AND o.created_at <= ?", organizationID, dateFrom, dateTo). Where("p.organization_id = ?", organizationID). Where("p.is_active = ?", true) if outletID != nil { query = query.Where("o.outlet_id = ? OR o.id IS NULL", *outletID) } if productID != nil { query = query.Where("p.id = ?", *productID) } if categoryID != nil { query = query.Where("p.category_id = ?", *categoryID) } err := query. Group("p.id, p.name, p.sku, c.id, c.name, p.price"). Order("p.name ASC"). Scan(&results).Error return results, err } func (r *HPPRepositoryImpl) GetRealHPPTimeSeries(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID, productID *uuid.UUID, categoryID *uuid.UUID, dateFrom, dateTo time.Time, groupBy string) ([]*entities.RealHPPTimeSeries, error) { var results []*entities.RealHPPTimeSeries var timeFormat string switch groupBy { case "hour": timeFormat = "DATE_TRUNC('hour', o.created_at)" case "week": timeFormat = "DATE_TRUNC('week', o.created_at)" case "month": timeFormat = "DATE_TRUNC('month', o.created_at)" default: timeFormat = "DATE_TRUNC('day', o.created_at)" } query := r.db.WithContext(ctx). Table("orders o"). Select(` `+timeFormat+` as date, COALESCE(SUM( oi.unit_cost * (oi.quantity - COALESCE(oi.refund_quantity, 0)) ), 0) as real_total_cost, COALESCE(SUM( oi.total_price - COALESCE(oi.refund_amount, 0) ), 0) as real_total_revenue, CASE WHEN COALESCE(SUM(oi.total_price - COALESCE(oi.refund_amount, 0)), 0) > 0 THEN COALESCE(SUM(oi.unit_cost * (oi.quantity - COALESCE(oi.refund_quantity, 0))), 0) / COALESCE(SUM(oi.total_price - COALESCE(oi.refund_amount, 0)), 0) * 100 ELSE 0 END as real_hpp_percentage, COUNT(DISTINCT o.id) as total_orders `). Joins("JOIN order_items oi ON oi.order_id = o.id AND oi.status != 'cancelled' AND oi.is_fully_refunded = false"). Where("o.organization_id = ?", organizationID). Where("o.is_void = false"). Where("o.is_refund = false"). Where("o.status = 'completed'"). Where("o.payment_status = 'completed'"). Where("o.created_at >= ? AND o.created_at <= ?", dateFrom, dateTo) if outletID != nil { query = query.Where("o.outlet_id = ?", *outletID) } if productID != nil { query = query.Where("oi.product_id = ?", *productID) } if categoryID != nil { query = query.Where("oi.product_id IN (SELECT id FROM products WHERE category_id = ?)", *categoryID) } err := query. Group(timeFormat). Order(timeFormat). Scan(&results).Error return results, err }