Implementasi HPP std dan real
This commit is contained in:
@@ -0,0 +1,227 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user