hpp #1
@ -104,6 +104,7 @@ func (a *App) Initialize(cfg *config.Config) error {
|
||||
validators.customerAuthValidator,
|
||||
services.customerPointsService,
|
||||
services.spinGameService,
|
||||
services.hppService,
|
||||
middleware.customerAuthMiddleware,
|
||||
)
|
||||
|
||||
@ -190,6 +191,7 @@ type repositories struct {
|
||||
customerAuthRepo repository.CustomerAuthRepository
|
||||
customerPointsRepo repository.CustomerPointsRepository
|
||||
otpRepo repository.OtpRepository
|
||||
hppRepo *repository.HPPRepositoryImpl
|
||||
txManager *repository.TxManager
|
||||
}
|
||||
|
||||
@ -235,6 +237,7 @@ func (a *App) initRepositories() *repositories {
|
||||
customerAuthRepo: repository.NewCustomerAuthRepository(a.db),
|
||||
customerPointsRepo: repository.NewCustomerPointsRepository(a.db),
|
||||
otpRepo: repository.NewOtpRepository(a.db),
|
||||
hppRepo: repository.NewHPPRepositoryImpl(a.db),
|
||||
txManager: repository.NewTxManager(a.db),
|
||||
}
|
||||
}
|
||||
@ -276,6 +279,7 @@ type processors struct {
|
||||
customerAuthProcessor processor.CustomerAuthProcessor
|
||||
customerPointsProcessor *processor.CustomerPointsProcessor
|
||||
otpProcessor processor.OtpProcessor
|
||||
hppProcessor *processor.HPPProcessorImpl
|
||||
fileClient processor.FileClient
|
||||
inventoryMovementService service.InventoryMovementService
|
||||
}
|
||||
@ -323,6 +327,7 @@ func (a *App) initProcessors(cfg *config.Config, repos *repositories) *processor
|
||||
customerAuthProcessor: processor.NewCustomerAuthProcessor(repos.customerAuthRepo, otpProcessor, repos.otpRepo, cfg.GetCustomerJWTSecret(), cfg.GetCustomerJWTExpiresTTL()),
|
||||
customerPointsProcessor: processor.NewCustomerPointsProcessor(repos.customerPointsRepo, repos.gameRepo),
|
||||
otpProcessor: otpProcessor,
|
||||
hppProcessor: processor.NewHPPProcessorImpl(repos.hppRepo),
|
||||
fileClient: fileClient,
|
||||
inventoryMovementService: inventoryMovementService,
|
||||
}
|
||||
@ -361,6 +366,7 @@ type services struct {
|
||||
customerAuthService service.CustomerAuthService
|
||||
customerPointsService service.CustomerPointsService
|
||||
spinGameService service.SpinGameService
|
||||
hppService *service.HPPServiceImpl
|
||||
}
|
||||
|
||||
func (a *App) initServices(processors *processors, repos *repositories, cfg *config.Config) *services {
|
||||
@ -396,6 +402,7 @@ func (a *App) initServices(processors *processors, repos *repositories, cfg *con
|
||||
customerAuthService := service.NewCustomerAuthService(processors.customerAuthProcessor)
|
||||
customerPointsService := service.NewCustomerPointsService(processors.customerPointsProcessor)
|
||||
spinGameService := service.NewSpinGameService(processors.gamePlayProcessor, repos.txManager)
|
||||
hppService := service.NewHPPServiceImpl(processors.hppProcessor)
|
||||
|
||||
// Update order service with order ingredient transaction service
|
||||
orderService = service.NewOrderServiceImpl(processors.orderProcessor, repos.tableRepo, orderIngredientTransactionService, processors.orderIngredientTransactionProcessor, *repos.productRecipeRepo, repos.txManager)
|
||||
@ -433,6 +440,7 @@ func (a *App) initServices(processors *processors, repos *repositories, cfg *con
|
||||
customerAuthService: customerAuthService,
|
||||
customerPointsService: customerPointsService,
|
||||
spinGameService: spinGameService,
|
||||
hppService: hppService,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
94
internal/contract/hpp_contract.go
Normal file
94
internal/contract/hpp_contract.go
Normal file
@ -0,0 +1,94 @@
|
||||
package contract
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type StandardHPPRequest struct {
|
||||
OrganizationID uuid.UUID `form:"-"`
|
||||
ProductID *uuid.UUID `form:"product_id,omitempty"`
|
||||
CategoryID *uuid.UUID `form:"category_id,omitempty"`
|
||||
}
|
||||
|
||||
type StandardHPPResponse struct {
|
||||
OrganizationID uuid.UUID `json:"organization_id"`
|
||||
Summary HPPSummary `json:"summary"`
|
||||
Products []StandardHPPProductData `json:"products"`
|
||||
Ingredients []StandardHPPIngredientData `json:"ingredients,omitempty"`
|
||||
}
|
||||
|
||||
type StandardHPPProductData struct {
|
||||
ProductID uuid.UUID `json:"product_id"`
|
||||
ProductName string `json:"product_name"`
|
||||
ProductSku string `json:"product_sku"`
|
||||
CategoryID uuid.UUID `json:"category_id"`
|
||||
CategoryName string `json:"category_name"`
|
||||
SellingPrice float64 `json:"selling_price"`
|
||||
ProductCost float64 `json:"product_cost"`
|
||||
StandardCost float64 `json:"standard_cost"`
|
||||
StandardHPPPercentage float64 `json:"standard_hpp_percentage"`
|
||||
HasRecipe bool `json:"has_recipe"`
|
||||
}
|
||||
|
||||
type StandardHPPIngredientData struct {
|
||||
ProductID uuid.UUID `json:"product_id"`
|
||||
IngredientID uuid.UUID `json:"ingredient_id"`
|
||||
IngredientName string `json:"ingredient_name"`
|
||||
Quantity float64 `json:"quantity"`
|
||||
UnitName string `json:"unit_name"`
|
||||
CostPerUnit float64 `json:"cost_per_unit"`
|
||||
WastePercentage float64 `json:"waste_percentage"`
|
||||
TotalCost float64 `json:"total_cost"`
|
||||
}
|
||||
|
||||
type RealHPPRequest struct {
|
||||
OrganizationID uuid.UUID `form:"-"`
|
||||
OutletID *uuid.UUID `form:"outlet_id,omitempty"`
|
||||
ProductID *uuid.UUID `form:"product_id,omitempty"`
|
||||
CategoryID *uuid.UUID `form:"category_id,omitempty"`
|
||||
DateFrom string `form:"date_from" validate:"required"`
|
||||
DateTo string `form:"date_to" validate:"required"`
|
||||
GroupBy string `form:"group_by,default=day" validate:"omitempty,oneof=day hour week month"`
|
||||
}
|
||||
|
||||
type RealHPPResponse struct {
|
||||
OrganizationID uuid.UUID `json:"organization_id"`
|
||||
OutletID *uuid.UUID `json:"outlet_id,omitempty"`
|
||||
DateFrom time.Time `json:"date_from"`
|
||||
DateTo time.Time `json:"date_to"`
|
||||
GroupBy string `json:"group_by"`
|
||||
Summary HPPSummary `json:"summary"`
|
||||
Products []RealHPPProductData `json:"products"`
|
||||
TimeSeries []RealHPPTimeSeriesData `json:"time_series,omitempty"`
|
||||
}
|
||||
|
||||
type RealHPPProductData struct {
|
||||
ProductID uuid.UUID `json:"product_id"`
|
||||
ProductName string `json:"product_name"`
|
||||
ProductSku string `json:"product_sku"`
|
||||
CategoryID uuid.UUID `json:"category_id"`
|
||||
CategoryName string `json:"category_name"`
|
||||
SellingPrice float64 `json:"selling_price"`
|
||||
RealTotalCost float64 `json:"real_total_cost"`
|
||||
RealTotalRevenue float64 `json:"real_total_revenue"`
|
||||
RealHPPPercentage float64 `json:"real_hpp_percentage"`
|
||||
TotalQuantitySold int64 `json:"total_quantity_sold"`
|
||||
TotalOrders int64 `json:"total_orders"`
|
||||
}
|
||||
|
||||
type RealHPPTimeSeriesData struct {
|
||||
Date time.Time `json:"date"`
|
||||
RealTotalCost float64 `json:"real_total_cost"`
|
||||
RealTotalRevenue float64 `json:"real_total_revenue"`
|
||||
RealHPPPercentage float64 `json:"real_hpp_percentage"`
|
||||
TotalOrders int64 `json:"total_orders"`
|
||||
}
|
||||
|
||||
type HPPSummary struct {
|
||||
AverageStandardHPP float64 `json:"average_standard_hpp"`
|
||||
AverageRealHPP float64 `json:"average_real_hpp"`
|
||||
HPPVariance float64 `json:"hpp_variance"`
|
||||
TotalProducts int64 `json:"total_products"`
|
||||
}
|
||||
60
internal/entities/hpp.go
Normal file
60
internal/entities/hpp.go
Normal file
@ -0,0 +1,60 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type StandardHPPProduct struct {
|
||||
ProductID uuid.UUID `json:"product_id"`
|
||||
ProductName string `json:"product_name"`
|
||||
ProductSku string `json:"product_sku"`
|
||||
CategoryID uuid.UUID `json:"category_id"`
|
||||
CategoryName string `json:"category_name"`
|
||||
SellingPrice float64 `json:"selling_price"`
|
||||
ProductCost float64 `json:"product_cost"`
|
||||
StandardCost float64 `json:"standard_cost"`
|
||||
StandardHPPPercentage float64 `json:"standard_hpp_percentage"`
|
||||
HasRecipe bool `json:"has_recipe"`
|
||||
}
|
||||
|
||||
type StandardHPPIngredient struct {
|
||||
ProductID uuid.UUID `json:"product_id"`
|
||||
IngredientID uuid.UUID `json:"ingredient_id"`
|
||||
IngredientName string `json:"ingredient_name"`
|
||||
Quantity float64 `json:"quantity"`
|
||||
UnitName string `json:"unit_name"`
|
||||
CostPerUnit float64 `json:"cost_per_unit"`
|
||||
WastePercentage float64 `json:"waste_percentage"`
|
||||
TotalCost float64 `json:"total_cost"`
|
||||
}
|
||||
|
||||
type RealHPPProduct struct {
|
||||
ProductID uuid.UUID `json:"product_id"`
|
||||
ProductName string `json:"product_name"`
|
||||
ProductSku string `json:"product_sku"`
|
||||
CategoryID uuid.UUID `json:"category_id"`
|
||||
CategoryName string `json:"category_name"`
|
||||
SellingPrice float64 `json:"selling_price"`
|
||||
RealTotalCost float64 `json:"real_total_cost"`
|
||||
RealTotalRevenue float64 `json:"real_total_revenue"`
|
||||
RealHPPPercentage float64 `json:"real_hpp_percentage"`
|
||||
TotalQuantitySold int64 `json:"total_quantity_sold"`
|
||||
TotalOrders int64 `json:"total_orders"`
|
||||
}
|
||||
|
||||
type RealHPPTimeSeries struct {
|
||||
Date time.Time `json:"date"`
|
||||
RealTotalCost float64 `json:"real_total_cost"`
|
||||
RealTotalRevenue float64 `json:"real_total_revenue"`
|
||||
RealHPPPercentage float64 `json:"real_hpp_percentage"`
|
||||
TotalOrders int64 `json:"total_orders"`
|
||||
}
|
||||
|
||||
type HPPSummary struct {
|
||||
AverageStandardHPP float64 `json:"average_standard_hpp"`
|
||||
AverageRealHPP float64 `json:"average_real_hpp"`
|
||||
HPPVariance float64 `json:"hpp_variance"`
|
||||
TotalProducts int64 `json:"total_products"`
|
||||
}
|
||||
73
internal/handler/hpp_handler.go
Normal file
73
internal/handler/hpp_handler.go
Normal file
@ -0,0 +1,73 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"apskel-pos-be/internal/appcontext"
|
||||
"apskel-pos-be/internal/contract"
|
||||
"apskel-pos-be/internal/service"
|
||||
"apskel-pos-be/internal/transformer"
|
||||
"apskel-pos-be/internal/util"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type HPPHandler struct {
|
||||
hppService service.HPPService
|
||||
transformer transformer.Transformer
|
||||
}
|
||||
|
||||
func NewHPPHandler(hppService service.HPPService, t transformer.Transformer) *HPPHandler {
|
||||
return &HPPHandler{
|
||||
hppService: hppService,
|
||||
transformer: t,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *HPPHandler) GetStandardHPP(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
contextInfo := appcontext.FromGinContext(ctx)
|
||||
|
||||
var req contract.StandardHPPRequest
|
||||
if err := c.ShouldBindQuery(&req); err != nil {
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("invalid_request", "HPPHandler::GetStandardHPP", err.Error())}), "HPPHandler::GetStandardHPP")
|
||||
return
|
||||
}
|
||||
|
||||
req.OrganizationID = contextInfo.OrganizationID
|
||||
modelReq := transformer.StandardHPPContractToModel(&req)
|
||||
|
||||
response, err := h.hppService.GetStandardHPP(ctx, modelReq)
|
||||
if err != nil {
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("internal_error", "HPPHandler::GetStandardHPP", err.Error())}), "HPPHandler::GetStandardHPP")
|
||||
return
|
||||
}
|
||||
|
||||
contractResp := transformer.StandardHPPModelToContract(response)
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(contractResp), "HPPHandler::GetStandardHPP")
|
||||
}
|
||||
|
||||
func (h *HPPHandler) GetRealHPP(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
contextInfo := appcontext.FromGinContext(ctx)
|
||||
|
||||
var req contract.RealHPPRequest
|
||||
if err := c.ShouldBindQuery(&req); err != nil {
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("invalid_request", "HPPHandler::GetRealHPP", err.Error())}), "HPPHandler::GetRealHPP")
|
||||
return
|
||||
}
|
||||
|
||||
req.OrganizationID = contextInfo.OrganizationID
|
||||
if contextInfo.OutletID != uuid.Nil {
|
||||
req.OutletID = &contextInfo.OutletID
|
||||
}
|
||||
modelReq := transformer.RealHPPContractToModel(&req)
|
||||
|
||||
response, err := h.hppService.GetRealHPP(ctx, modelReq)
|
||||
if err != nil {
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("internal_error", "HPPHandler::GetRealHPP", err.Error())}), "HPPHandler::GetRealHPP")
|
||||
return
|
||||
}
|
||||
|
||||
contractResp := transformer.RealHPPModelToContract(response)
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(contractResp), "HPPHandler::GetRealHPP")
|
||||
}
|
||||
94
internal/models/hpp.go
Normal file
94
internal/models/hpp.go
Normal file
@ -0,0 +1,94 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type StandardHPPRequest struct {
|
||||
OrganizationID uuid.UUID `validate:"required"`
|
||||
ProductID *uuid.UUID `validate:"omitempty"`
|
||||
CategoryID *uuid.UUID `validate:"omitempty"`
|
||||
}
|
||||
|
||||
type StandardHPPResponse struct {
|
||||
OrganizationID uuid.UUID `json:"organization_id"`
|
||||
Summary HPPSummary `json:"summary"`
|
||||
Products []StandardHPPProduct `json:"products"`
|
||||
Ingredients []StandardHPPIngredient `json:"ingredients,omitempty"`
|
||||
}
|
||||
|
||||
type StandardHPPProduct struct {
|
||||
ProductID uuid.UUID `json:"product_id"`
|
||||
ProductName string `json:"product_name"`
|
||||
ProductSku string `json:"product_sku"`
|
||||
CategoryID uuid.UUID `json:"category_id"`
|
||||
CategoryName string `json:"category_name"`
|
||||
SellingPrice float64 `json:"selling_price"`
|
||||
ProductCost float64 `json:"product_cost"`
|
||||
StandardCost float64 `json:"standard_cost"`
|
||||
StandardHPPPercentage float64 `json:"standard_hpp_percentage"`
|
||||
HasRecipe bool `json:"has_recipe"`
|
||||
}
|
||||
|
||||
type StandardHPPIngredient struct {
|
||||
ProductID uuid.UUID `json:"product_id"`
|
||||
IngredientID uuid.UUID `json:"ingredient_id"`
|
||||
IngredientName string `json:"ingredient_name"`
|
||||
Quantity float64 `json:"quantity"`
|
||||
UnitName string `json:"unit_name"`
|
||||
CostPerUnit float64 `json:"cost_per_unit"`
|
||||
WastePercentage float64 `json:"waste_percentage"`
|
||||
TotalCost float64 `json:"total_cost"`
|
||||
}
|
||||
|
||||
type RealHPPRequest struct {
|
||||
OrganizationID uuid.UUID `validate:"required"`
|
||||
OutletID *uuid.UUID `validate:"omitempty"`
|
||||
ProductID *uuid.UUID `validate:"omitempty"`
|
||||
CategoryID *uuid.UUID `validate:"omitempty"`
|
||||
DateFrom time.Time `validate:"required"`
|
||||
DateTo time.Time `validate:"required"`
|
||||
GroupBy string `validate:"omitempty,oneof=day hour week month"`
|
||||
}
|
||||
|
||||
type RealHPPResponse struct {
|
||||
OrganizationID uuid.UUID `json:"organization_id"`
|
||||
OutletID *uuid.UUID `json:"outlet_id,omitempty"`
|
||||
DateFrom time.Time `json:"date_from"`
|
||||
DateTo time.Time `json:"date_to"`
|
||||
GroupBy string `json:"group_by"`
|
||||
Summary HPPSummary `json:"summary"`
|
||||
Products []RealHPPProduct `json:"products"`
|
||||
TimeSeries []RealHPPTimeSeries `json:"time_series,omitempty"`
|
||||
}
|
||||
|
||||
type RealHPPProduct struct {
|
||||
ProductID uuid.UUID `json:"product_id"`
|
||||
ProductName string `json:"product_name"`
|
||||
ProductSku string `json:"product_sku"`
|
||||
CategoryID uuid.UUID `json:"category_id"`
|
||||
CategoryName string `json:"category_name"`
|
||||
SellingPrice float64 `json:"selling_price"`
|
||||
RealTotalCost float64 `json:"real_total_cost"`
|
||||
RealTotalRevenue float64 `json:"real_total_revenue"`
|
||||
RealHPPPercentage float64 `json:"real_hpp_percentage"`
|
||||
TotalQuantitySold int64 `json:"total_quantity_sold"`
|
||||
TotalOrders int64 `json:"total_orders"`
|
||||
}
|
||||
|
||||
type RealHPPTimeSeries struct {
|
||||
Date time.Time `json:"date"`
|
||||
RealTotalCost float64 `json:"real_total_cost"`
|
||||
RealTotalRevenue float64 `json:"real_total_revenue"`
|
||||
RealHPPPercentage float64 `json:"real_hpp_percentage"`
|
||||
TotalOrders int64 `json:"total_orders"`
|
||||
}
|
||||
|
||||
type HPPSummary struct {
|
||||
AverageStandardHPP float64 `json:"average_standard_hpp"`
|
||||
AverageRealHPP float64 `json:"average_real_hpp"`
|
||||
HPPVariance float64 `json:"hpp_variance"`
|
||||
TotalProducts int64 `json:"total_products"`
|
||||
}
|
||||
179
internal/processor/hpp_processor.go
Normal file
179
internal/processor/hpp_processor.go
Normal file
@ -0,0 +1,179 @@
|
||||
package processor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"apskel-pos-be/internal/entities"
|
||||
"apskel-pos-be/internal/models"
|
||||
"apskel-pos-be/internal/repository"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type HPPProcessor interface {
|
||||
GetStandardHPP(ctx context.Context, req *models.StandardHPPRequest) (*models.StandardHPPResponse, error)
|
||||
GetRealHPP(ctx context.Context, req *models.RealHPPRequest) (*models.RealHPPResponse, error)
|
||||
}
|
||||
|
||||
type HPPProcessorImpl struct {
|
||||
hppRepo repository.HPPRepository
|
||||
}
|
||||
|
||||
func NewHPPProcessorImpl(hppRepo repository.HPPRepository) *HPPProcessorImpl {
|
||||
return &HPPProcessorImpl{
|
||||
hppRepo: hppRepo,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *HPPProcessorImpl) GetStandardHPP(ctx context.Context, req *models.StandardHPPRequest) (*models.StandardHPPResponse, error) {
|
||||
products, err := p.hppRepo.GetStandardHPP(ctx, req.OrganizationID, req.ProductID, req.CategoryID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get standard HPP: %w", err)
|
||||
}
|
||||
|
||||
productIDs := make([]uuid.UUID, 0)
|
||||
for _, pp := range products {
|
||||
if pp.HasRecipe {
|
||||
productIDs = append(productIDs, pp.ProductID)
|
||||
}
|
||||
}
|
||||
|
||||
ingredients, err := p.hppRepo.GetStandardHPPIngredients(ctx, req.OrganizationID, productIDs)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get standard HPP ingredients: %w", err)
|
||||
}
|
||||
|
||||
productData := make([]models.StandardHPPProduct, 0, len(products))
|
||||
var totalHPP float64
|
||||
var hppCount int64
|
||||
|
||||
for _, pp := range products {
|
||||
productData = append(productData, models.StandardHPPProduct{
|
||||
ProductID: pp.ProductID,
|
||||
ProductName: pp.ProductName,
|
||||
ProductSku: pp.ProductSku,
|
||||
CategoryID: pp.CategoryID,
|
||||
CategoryName: pp.CategoryName,
|
||||
SellingPrice: pp.SellingPrice,
|
||||
ProductCost: pp.ProductCost,
|
||||
StandardCost: pp.StandardCost,
|
||||
StandardHPPPercentage: pp.StandardHPPPercentage,
|
||||
HasRecipe: pp.HasRecipe,
|
||||
})
|
||||
if pp.HasRecipe {
|
||||
totalHPP += pp.StandardHPPPercentage
|
||||
hppCount++
|
||||
}
|
||||
}
|
||||
|
||||
ingredientData := make([]models.StandardHPPIngredient, 0, len(ingredients))
|
||||
for _, ing := range ingredients {
|
||||
ingredientData = append(ingredientData, models.StandardHPPIngredient{
|
||||
ProductID: ing.ProductID,
|
||||
IngredientID: ing.IngredientID,
|
||||
IngredientName: ing.IngredientName,
|
||||
Quantity: ing.Quantity,
|
||||
UnitName: ing.UnitName,
|
||||
CostPerUnit: ing.CostPerUnit,
|
||||
WastePercentage: ing.WastePercentage,
|
||||
TotalCost: ing.TotalCost,
|
||||
})
|
||||
}
|
||||
|
||||
var avgHPP float64
|
||||
if hppCount > 0 {
|
||||
avgHPP = totalHPP / float64(hppCount)
|
||||
}
|
||||
|
||||
return &models.StandardHPPResponse{
|
||||
OrganizationID: req.OrganizationID,
|
||||
Summary: models.HPPSummary{
|
||||
AverageStandardHPP: avgHPP,
|
||||
AverageRealHPP: 0,
|
||||
HPPVariance: 0,
|
||||
TotalProducts: int64(len(products)),
|
||||
},
|
||||
Products: productData,
|
||||
Ingredients: ingredientData,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *HPPProcessorImpl) GetRealHPP(ctx context.Context, req *models.RealHPPRequest) (*models.RealHPPResponse, error) {
|
||||
if req.DateFrom.After(req.DateTo) {
|
||||
return nil, fmt.Errorf("date_from cannot be after date_to")
|
||||
}
|
||||
|
||||
if req.GroupBy == "" {
|
||||
req.GroupBy = "day"
|
||||
}
|
||||
|
||||
products, err := p.hppRepo.GetRealHPP(ctx, req.OrganizationID, req.OutletID, req.ProductID, req.CategoryID, req.DateFrom, req.DateTo)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get real HPP: %w", err)
|
||||
}
|
||||
|
||||
var timeSeries []*entities.RealHPPTimeSeries
|
||||
if req.GroupBy != "" {
|
||||
timeSeries, err = p.hppRepo.GetRealHPPTimeSeries(ctx, req.OrganizationID, req.OutletID, req.ProductID, req.CategoryID, req.DateFrom, req.DateTo, req.GroupBy)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get real HPP time series: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
productData := make([]models.RealHPPProduct, 0, len(products))
|
||||
var totalHPP float64
|
||||
var totalRevenue float64
|
||||
var hppCount int64
|
||||
|
||||
for _, pp := range products {
|
||||
productData = append(productData, models.RealHPPProduct{
|
||||
ProductID: pp.ProductID,
|
||||
ProductName: pp.ProductName,
|
||||
ProductSku: pp.ProductSku,
|
||||
CategoryID: pp.CategoryID,
|
||||
CategoryName: pp.CategoryName,
|
||||
SellingPrice: pp.SellingPrice,
|
||||
RealTotalCost: pp.RealTotalCost,
|
||||
RealTotalRevenue: pp.RealTotalRevenue,
|
||||
RealHPPPercentage: pp.RealHPPPercentage,
|
||||
TotalQuantitySold: pp.TotalQuantitySold,
|
||||
TotalOrders: pp.TotalOrders,
|
||||
})
|
||||
if pp.RealTotalRevenue > 0 {
|
||||
totalHPP += pp.RealHPPPercentage
|
||||
totalRevenue += pp.RealTotalRevenue
|
||||
hppCount++
|
||||
}
|
||||
}
|
||||
|
||||
var avgHPP float64
|
||||
if hppCount > 0 {
|
||||
avgHPP = totalHPP / float64(hppCount)
|
||||
}
|
||||
|
||||
tsData := make([]models.RealHPPTimeSeries, 0, len(timeSeries))
|
||||
for _, ts := range timeSeries {
|
||||
tsData = append(tsData, models.RealHPPTimeSeries{
|
||||
Date: ts.Date,
|
||||
RealTotalCost: ts.RealTotalCost,
|
||||
RealTotalRevenue: ts.RealTotalRevenue,
|
||||
RealHPPPercentage: ts.RealHPPPercentage,
|
||||
TotalOrders: ts.TotalOrders,
|
||||
})
|
||||
}
|
||||
|
||||
return &models.RealHPPResponse{
|
||||
OrganizationID: req.OrganizationID,
|
||||
OutletID: req.OutletID,
|
||||
DateFrom: req.DateFrom,
|
||||
DateTo: req.DateTo,
|
||||
GroupBy: req.GroupBy,
|
||||
Summary: models.HPPSummary{
|
||||
AverageRealHPP: avgHPP,
|
||||
TotalProducts: int64(len(products)),
|
||||
},
|
||||
Products: productData,
|
||||
TimeSeries: tsData,
|
||||
}, nil
|
||||
}
|
||||
227
internal/repository/hpp_repository.go
Normal file
227
internal/repository/hpp_repository.go
Normal file
@ -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
|
||||
}
|
||||
@ -46,6 +46,7 @@ type Router struct {
|
||||
customerAuthHandler *handler.CustomerAuthHandler
|
||||
customerPointsHandler *handler.CustomerPointsHandler
|
||||
spinGameHandler *handler.SpinGameHandler
|
||||
hppHandler *handler.HPPHandler
|
||||
authMiddleware *middleware.AuthMiddleware
|
||||
customerAuthMiddleware *middleware.CustomerAuthMiddleware
|
||||
}
|
||||
@ -108,6 +109,7 @@ func NewRouter(cfg *config.Config,
|
||||
customerAuthValidator validator.CustomerAuthValidator,
|
||||
customerPointsService service.CustomerPointsService,
|
||||
spinGameService service.SpinGameService,
|
||||
hppService *service.HPPServiceImpl,
|
||||
customerAuthMiddleware *middleware.CustomerAuthMiddleware) *Router {
|
||||
|
||||
return &Router{
|
||||
@ -144,6 +146,7 @@ func NewRouter(cfg *config.Config,
|
||||
customerAuthHandler: handler.NewCustomerAuthHandler(customerAuthService, customerAuthValidator),
|
||||
customerPointsHandler: handler.NewCustomerPointsHandler(customerPointsService),
|
||||
spinGameHandler: handler.NewSpinGameHandler(spinGameService),
|
||||
hppHandler: handler.NewHPPHandler(hppService, transformer.NewTransformer()),
|
||||
authMiddleware: authMiddleware,
|
||||
customerAuthMiddleware: customerAuthMiddleware,
|
||||
productVariantHandler: handler.NewProductVariantHandler(productVariantService, productVariantValidator),
|
||||
@ -360,6 +363,13 @@ func (r *Router) addAppRoutes(rg *gin.Engine) {
|
||||
analytics.GET("/profit-loss", r.analyticsHandler.GetProfitLossAnalytics)
|
||||
}
|
||||
|
||||
hpp := protected.Group("/hpp")
|
||||
hpp.Use(r.authMiddleware.RequireAdminOrManager())
|
||||
{
|
||||
hpp.GET("/standard", r.hppHandler.GetStandardHPP)
|
||||
hpp.GET("/real", r.hppHandler.GetRealHPP)
|
||||
}
|
||||
|
||||
tables := protected.Group("/tables")
|
||||
tables.Use(r.authMiddleware.RequireAdminOrManager())
|
||||
{
|
||||
|
||||
78
internal/service/hpp_service.go
Normal file
78
internal/service/hpp_service.go
Normal file
@ -0,0 +1,78 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"apskel-pos-be/internal/models"
|
||||
"apskel-pos-be/internal/processor"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type HPPService interface {
|
||||
GetStandardHPP(ctx context.Context, req *models.StandardHPPRequest) (*models.StandardHPPResponse, error)
|
||||
GetRealHPP(ctx context.Context, req *models.RealHPPRequest) (*models.RealHPPResponse, error)
|
||||
}
|
||||
|
||||
type HPPServiceImpl struct {
|
||||
hppProcessor processor.HPPProcessor
|
||||
}
|
||||
|
||||
func NewHPPServiceImpl(hppProcessor processor.HPPProcessor) *HPPServiceImpl {
|
||||
return &HPPServiceImpl{
|
||||
hppProcessor: hppProcessor,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *HPPServiceImpl) GetStandardHPP(ctx context.Context, req *models.StandardHPPRequest) (*models.StandardHPPResponse, error) {
|
||||
if err := s.validateStandardHPPRequest(req); err != nil {
|
||||
return nil, fmt.Errorf("validation error: %w", err)
|
||||
}
|
||||
|
||||
response, err := s.hppProcessor.GetStandardHPP(ctx, req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get standard HPP: %w", err)
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (s *HPPServiceImpl) GetRealHPP(ctx context.Context, req *models.RealHPPRequest) (*models.RealHPPResponse, error) {
|
||||
if err := s.validateRealHPPRequest(req); err != nil {
|
||||
return nil, fmt.Errorf("validation error: %w", err)
|
||||
}
|
||||
|
||||
response, err := s.hppProcessor.GetRealHPP(ctx, req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get real HPP: %w", err)
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (s *HPPServiceImpl) validateStandardHPPRequest(req *models.StandardHPPRequest) error {
|
||||
if req.OrganizationID == uuid.Nil {
|
||||
return fmt.Errorf("organization_id is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *HPPServiceImpl) validateRealHPPRequest(req *models.RealHPPRequest) 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 != "" && 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
|
||||
}
|
||||
136
internal/transformer/hpp_transformer.go
Normal file
136
internal/transformer/hpp_transformer.go
Normal file
@ -0,0 +1,136 @@
|
||||
package transformer
|
||||
|
||||
import (
|
||||
"apskel-pos-be/internal/contract"
|
||||
"apskel-pos-be/internal/models"
|
||||
"apskel-pos-be/internal/util"
|
||||
"time"
|
||||
)
|
||||
|
||||
func StandardHPPContractToModel(req *contract.StandardHPPRequest) *models.StandardHPPRequest {
|
||||
return &models.StandardHPPRequest{
|
||||
OrganizationID: req.OrganizationID,
|
||||
ProductID: req.ProductID,
|
||||
CategoryID: req.CategoryID,
|
||||
}
|
||||
}
|
||||
|
||||
func StandardHPPModelToContract(resp *models.StandardHPPResponse) *contract.StandardHPPResponse {
|
||||
if resp == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
products := make([]contract.StandardHPPProductData, len(resp.Products))
|
||||
for i, p := range resp.Products {
|
||||
products[i] = contract.StandardHPPProductData{
|
||||
ProductID: p.ProductID,
|
||||
ProductName: p.ProductName,
|
||||
ProductSku: p.ProductSku,
|
||||
CategoryID: p.CategoryID,
|
||||
CategoryName: p.CategoryName,
|
||||
SellingPrice: p.SellingPrice,
|
||||
ProductCost: p.ProductCost,
|
||||
StandardCost: p.StandardCost,
|
||||
StandardHPPPercentage: p.StandardHPPPercentage,
|
||||
HasRecipe: p.HasRecipe,
|
||||
}
|
||||
}
|
||||
|
||||
ingredients := make([]contract.StandardHPPIngredientData, len(resp.Ingredients))
|
||||
for i, ing := range resp.Ingredients {
|
||||
ingredients[i] = contract.StandardHPPIngredientData{
|
||||
ProductID: ing.ProductID,
|
||||
IngredientID: ing.IngredientID,
|
||||
IngredientName: ing.IngredientName,
|
||||
Quantity: ing.Quantity,
|
||||
UnitName: ing.UnitName,
|
||||
CostPerUnit: ing.CostPerUnit,
|
||||
WastePercentage: ing.WastePercentage,
|
||||
TotalCost: ing.TotalCost,
|
||||
}
|
||||
}
|
||||
|
||||
return &contract.StandardHPPResponse{
|
||||
OrganizationID: resp.OrganizationID,
|
||||
Summary: hppSummaryModelToContract(resp.Summary),
|
||||
Products: products,
|
||||
Ingredients: ingredients,
|
||||
}
|
||||
}
|
||||
|
||||
func RealHPPContractToModel(req *contract.RealHPPRequest) *models.RealHPPRequest {
|
||||
var dateFrom, dateTo time.Time
|
||||
|
||||
if fromTime, toTime, err := util.ParseDateRangeToJakartaTime(req.DateFrom, req.DateTo); err == nil {
|
||||
if fromTime != nil {
|
||||
dateFrom = *fromTime
|
||||
}
|
||||
if toTime != nil {
|
||||
dateTo = *toTime
|
||||
}
|
||||
}
|
||||
|
||||
return &models.RealHPPRequest{
|
||||
OrganizationID: req.OrganizationID,
|
||||
OutletID: req.OutletID,
|
||||
ProductID: req.ProductID,
|
||||
CategoryID: req.CategoryID,
|
||||
DateFrom: dateFrom,
|
||||
DateTo: dateTo,
|
||||
GroupBy: req.GroupBy,
|
||||
}
|
||||
}
|
||||
|
||||
func RealHPPModelToContract(resp *models.RealHPPResponse) *contract.RealHPPResponse {
|
||||
if resp == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
products := make([]contract.RealHPPProductData, len(resp.Products))
|
||||
for i, p := range resp.Products {
|
||||
products[i] = contract.RealHPPProductData{
|
||||
ProductID: p.ProductID,
|
||||
ProductName: p.ProductName,
|
||||
ProductSku: p.ProductSku,
|
||||
CategoryID: p.CategoryID,
|
||||
CategoryName: p.CategoryName,
|
||||
SellingPrice: p.SellingPrice,
|
||||
RealTotalCost: p.RealTotalCost,
|
||||
RealTotalRevenue: p.RealTotalRevenue,
|
||||
RealHPPPercentage: p.RealHPPPercentage,
|
||||
TotalQuantitySold: p.TotalQuantitySold,
|
||||
TotalOrders: p.TotalOrders,
|
||||
}
|
||||
}
|
||||
|
||||
timeSeries := make([]contract.RealHPPTimeSeriesData, len(resp.TimeSeries))
|
||||
for i, ts := range resp.TimeSeries {
|
||||
timeSeries[i] = contract.RealHPPTimeSeriesData{
|
||||
Date: ts.Date,
|
||||
RealTotalCost: ts.RealTotalCost,
|
||||
RealTotalRevenue: ts.RealTotalRevenue,
|
||||
RealHPPPercentage: ts.RealHPPPercentage,
|
||||
TotalOrders: ts.TotalOrders,
|
||||
}
|
||||
}
|
||||
|
||||
return &contract.RealHPPResponse{
|
||||
OrganizationID: resp.OrganizationID,
|
||||
OutletID: resp.OutletID,
|
||||
DateFrom: resp.DateFrom,
|
||||
DateTo: resp.DateTo,
|
||||
GroupBy: resp.GroupBy,
|
||||
Summary: hppSummaryModelToContract(resp.Summary),
|
||||
Products: products,
|
||||
TimeSeries: timeSeries,
|
||||
}
|
||||
}
|
||||
|
||||
func hppSummaryModelToContract(s models.HPPSummary) contract.HPPSummary {
|
||||
return contract.HPPSummary{
|
||||
AverageStandardHPP: s.AverageStandardHPP,
|
||||
AverageRealHPP: s.AverageRealHPP,
|
||||
HPPVariance: s.HPPVariance,
|
||||
TotalProducts: s.TotalProducts,
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user