hpp #1
@ -104,7 +104,6 @@ func (a *App) Initialize(cfg *config.Config) error {
|
||||
validators.customerAuthValidator,
|
||||
services.customerPointsService,
|
||||
services.spinGameService,
|
||||
services.hppService,
|
||||
middleware.customerAuthMiddleware,
|
||||
)
|
||||
|
||||
@ -191,7 +190,6 @@ type repositories struct {
|
||||
customerAuthRepo repository.CustomerAuthRepository
|
||||
customerPointsRepo repository.CustomerPointsRepository
|
||||
otpRepo repository.OtpRepository
|
||||
hppRepo *repository.HPPRepositoryImpl
|
||||
txManager *repository.TxManager
|
||||
}
|
||||
|
||||
@ -237,7 +235,6 @@ 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),
|
||||
}
|
||||
}
|
||||
@ -279,7 +276,6 @@ type processors struct {
|
||||
customerAuthProcessor processor.CustomerAuthProcessor
|
||||
customerPointsProcessor *processor.CustomerPointsProcessor
|
||||
otpProcessor processor.OtpProcessor
|
||||
hppProcessor *processor.HPPProcessorImpl
|
||||
fileClient processor.FileClient
|
||||
inventoryMovementService service.InventoryMovementService
|
||||
}
|
||||
@ -327,7 +323,6 @@ 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,
|
||||
}
|
||||
@ -366,7 +361,6 @@ 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 {
|
||||
@ -402,7 +396,6 @@ 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)
|
||||
@ -440,7 +433,6 @@ func (a *App) initServices(processors *processors, repos *repositories, cfg *con
|
||||
customerAuthService: customerAuthService,
|
||||
customerPointsService: customerPointsService,
|
||||
spinGameService: spinGameService,
|
||||
hppService: hppService,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -101,18 +101,23 @@ type ProductAnalyticsResponse struct {
|
||||
Data []ProductAnalyticsData `json:"data"`
|
||||
}
|
||||
|
||||
// ProductAnalyticsData represents individual product analytics data
|
||||
type ProductAnalyticsData 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"`
|
||||
CategoryOrder int `json:"category_order"`
|
||||
QuantitySold int64 `json:"quantity_sold"`
|
||||
Revenue float64 `json:"revenue"`
|
||||
AveragePrice float64 `json:"average_price"`
|
||||
OrderCount int64 `json:"order_count"`
|
||||
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"`
|
||||
CategoryOrder int `json:"category_order"`
|
||||
QuantitySold int64 `json:"quantity_sold"`
|
||||
Revenue float64 `json:"revenue"`
|
||||
AveragePrice float64 `json:"average_price"`
|
||||
OrderCount int64 `json:"order_count"`
|
||||
StandardHppPerUnit float64 `json:"standard_hpp_per_unit"`
|
||||
StandardHppTotal float64 `json:"standard_hpp_total"`
|
||||
FifoHppPerUnit float64 `json:"fifo_hpp_per_unit"`
|
||||
FifoHppTotal float64 `json:"fifo_hpp_total"`
|
||||
MovingAverageHppPerUnit float64 `json:"moving_average_hpp_per_unit"`
|
||||
MovingAverageHppTotal float64 `json:"moving_average_hpp_total"`
|
||||
}
|
||||
|
||||
// ProductAnalyticsPerCategoryRequest represents the request for product analytics per category
|
||||
@ -125,21 +130,23 @@ type ProductAnalyticsPerCategoryRequest struct {
|
||||
|
||||
// ProductAnalyticsPerCategoryResponse represents the response for product analytics per category
|
||||
type ProductAnalyticsPerCategoryResponse 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"`
|
||||
Data []ProductAnalyticsPerCategoryData `json:"data"`
|
||||
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"`
|
||||
Data []ProductAnalyticsPerCategoryData `json:"data"`
|
||||
}
|
||||
|
||||
// ProductAnalyticsPerCategoryData represents individual category analytics data
|
||||
type ProductAnalyticsPerCategoryData struct {
|
||||
CategoryID uuid.UUID `json:"category_id"`
|
||||
CategoryName string `json:"category_name"`
|
||||
TotalRevenue float64 `json:"total_revenue"`
|
||||
TotalQuantity int64 `json:"total_quantity"`
|
||||
ProductCount int64 `json:"product_count"`
|
||||
OrderCount int64 `json:"order_count"`
|
||||
CategoryID uuid.UUID `json:"category_id"`
|
||||
CategoryName string `json:"category_name"`
|
||||
TotalRevenue float64 `json:"total_revenue"`
|
||||
TotalQuantity int64 `json:"total_quantity"`
|
||||
ProductCount int64 `json:"product_count"`
|
||||
OrderCount int64 `json:"order_count"`
|
||||
TotalStandardHpp float64 `json:"total_standard_hpp"`
|
||||
TotalFifoHpp float64 `json:"total_fifo_hpp"`
|
||||
TotalMovingAverageHpp float64 `json:"total_moving_average_hpp"`
|
||||
}
|
||||
|
||||
// DashboardAnalyticsRequest represents the request for dashboard analytics
|
||||
|
||||
@ -1,94 +0,0 @@
|
||||
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"`
|
||||
}
|
||||
@ -27,28 +27,35 @@ type SalesAnalytics struct {
|
||||
NetSales float64 `json:"net_sales"`
|
||||
}
|
||||
|
||||
// ProductAnalytics represents product analytics data
|
||||
type ProductAnalytics 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"`
|
||||
CategoryOrder int `json:"category_order"`
|
||||
QuantitySold int64 `json:"quantity_sold"`
|
||||
Revenue float64 `json:"revenue"`
|
||||
AveragePrice float64 `json:"average_price"`
|
||||
OrderCount int64 `json:"order_count"`
|
||||
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"`
|
||||
CategoryOrder int `json:"category_order"`
|
||||
QuantitySold int64 `json:"quantity_sold"`
|
||||
Revenue float64 `json:"revenue"`
|
||||
AveragePrice float64 `json:"average_price"`
|
||||
OrderCount int64 `json:"order_count"`
|
||||
StandardHppPerUnit float64 `json:"standard_hpp_per_unit"`
|
||||
StandardHppTotal float64 `json:"standard_hpp_total"`
|
||||
FifoHppPerUnit float64 `json:"fifo_hpp_per_unit"`
|
||||
FifoHppTotal float64 `json:"fifo_hpp_total"`
|
||||
MovingAverageHppPerUnit float64 `json:"moving_average_hpp_per_unit"`
|
||||
MovingAverageHppTotal float64 `json:"moving_average_hpp_total"`
|
||||
}
|
||||
|
||||
// ProductAnalyticsPerCategory represents product analytics data grouped by category
|
||||
type ProductAnalyticsPerCategory struct {
|
||||
CategoryID uuid.UUID `json:"category_id"`
|
||||
CategoryName string `json:"category_name"`
|
||||
TotalRevenue float64 `json:"total_revenue"`
|
||||
TotalQuantity int64 `json:"total_quantity"`
|
||||
ProductCount int64 `json:"product_count"`
|
||||
OrderCount int64 `json:"order_count"`
|
||||
CategoryID uuid.UUID `json:"category_id"`
|
||||
CategoryName string `json:"category_name"`
|
||||
TotalRevenue float64 `json:"total_revenue"`
|
||||
TotalQuantity int64 `json:"total_quantity"`
|
||||
ProductCount int64 `json:"product_count"`
|
||||
OrderCount int64 `json:"order_count"`
|
||||
TotalStandardHpp float64 `json:"total_standard_hpp"`
|
||||
TotalFifoHpp float64 `json:"total_fifo_hpp"`
|
||||
TotalMovingAverageHpp float64 `json:"total_moving_average_hpp"`
|
||||
}
|
||||
|
||||
// DashboardOverview represents dashboard overview data
|
||||
|
||||
@ -1,73 +0,0 @@
|
||||
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")
|
||||
}
|
||||
@ -105,18 +105,23 @@ type ProductAnalyticsResponse struct {
|
||||
Data []ProductAnalyticsData `json:"data"`
|
||||
}
|
||||
|
||||
// ProductAnalyticsData represents individual product analytics data
|
||||
type ProductAnalyticsData 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"`
|
||||
CategoryOrder int `json:"category_order"`
|
||||
QuantitySold int64 `json:"quantity_sold"`
|
||||
Revenue float64 `json:"revenue"`
|
||||
AveragePrice float64 `json:"average_price"`
|
||||
OrderCount int64 `json:"order_count"`
|
||||
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"`
|
||||
CategoryOrder int `json:"category_order"`
|
||||
QuantitySold int64 `json:"quantity_sold"`
|
||||
Revenue float64 `json:"revenue"`
|
||||
AveragePrice float64 `json:"average_price"`
|
||||
OrderCount int64 `json:"order_count"`
|
||||
StandardHppPerUnit float64 `json:"standard_hpp_per_unit"`
|
||||
StandardHppTotal float64 `json:"standard_hpp_total"`
|
||||
FifoHppPerUnit float64 `json:"fifo_hpp_per_unit"`
|
||||
FifoHppTotal float64 `json:"fifo_hpp_total"`
|
||||
MovingAverageHppPerUnit float64 `json:"moving_average_hpp_per_unit"`
|
||||
MovingAverageHppTotal float64 `json:"moving_average_hpp_total"`
|
||||
}
|
||||
|
||||
// ProductAnalyticsPerCategoryRequest represents the request for product analytics per category
|
||||
@ -129,21 +134,23 @@ type ProductAnalyticsPerCategoryRequest struct {
|
||||
|
||||
// ProductAnalyticsPerCategoryResponse represents the response for product analytics per category
|
||||
type ProductAnalyticsPerCategoryResponse 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"`
|
||||
Data []ProductAnalyticsPerCategoryData `json:"data"`
|
||||
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"`
|
||||
Data []ProductAnalyticsPerCategoryData `json:"data"`
|
||||
}
|
||||
|
||||
// ProductAnalyticsPerCategoryData represents individual category analytics data
|
||||
type ProductAnalyticsPerCategoryData struct {
|
||||
CategoryID uuid.UUID `json:"category_id"`
|
||||
CategoryName string `json:"category_name"`
|
||||
TotalRevenue float64 `json:"total_revenue"`
|
||||
TotalQuantity int64 `json:"total_quantity"`
|
||||
ProductCount int64 `json:"product_count"`
|
||||
OrderCount int64 `json:"order_count"`
|
||||
CategoryID uuid.UUID `json:"category_id"`
|
||||
CategoryName string `json:"category_name"`
|
||||
TotalRevenue float64 `json:"total_revenue"`
|
||||
TotalQuantity int64 `json:"total_quantity"`
|
||||
ProductCount int64 `json:"product_count"`
|
||||
OrderCount int64 `json:"order_count"`
|
||||
TotalStandardHpp float64 `json:"total_standard_hpp"`
|
||||
TotalFifoHpp float64 `json:"total_fifo_hpp"`
|
||||
TotalMovingAverageHpp float64 `json:"total_moving_average_hpp"`
|
||||
}
|
||||
|
||||
// DashboardAnalyticsRequest represents the request for dashboard analytics
|
||||
|
||||
@ -1,94 +0,0 @@
|
||||
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"`
|
||||
}
|
||||
@ -185,16 +185,22 @@ func (p *AnalyticsProcessorImpl) GetProductAnalytics(ctx context.Context, req *m
|
||||
var resultData []models.ProductAnalyticsData
|
||||
for _, data := range analyticsData {
|
||||
resultData = append(resultData, models.ProductAnalyticsData{
|
||||
ProductID: data.ProductID,
|
||||
ProductName: data.ProductName,
|
||||
ProductSku: data.ProductSku,
|
||||
CategoryID: data.CategoryID,
|
||||
CategoryName: data.CategoryName,
|
||||
CategoryOrder: data.CategoryOrder,
|
||||
QuantitySold: data.QuantitySold,
|
||||
Revenue: data.Revenue,
|
||||
AveragePrice: data.AveragePrice,
|
||||
OrderCount: data.OrderCount,
|
||||
ProductID: data.ProductID,
|
||||
ProductName: data.ProductName,
|
||||
ProductSku: data.ProductSku,
|
||||
CategoryID: data.CategoryID,
|
||||
CategoryName: data.CategoryName,
|
||||
CategoryOrder: data.CategoryOrder,
|
||||
QuantitySold: data.QuantitySold,
|
||||
Revenue: data.Revenue,
|
||||
AveragePrice: data.AveragePrice,
|
||||
OrderCount: data.OrderCount,
|
||||
StandardHppPerUnit: data.StandardHppPerUnit,
|
||||
StandardHppTotal: data.StandardHppTotal,
|
||||
FifoHppPerUnit: data.FifoHppPerUnit,
|
||||
FifoHppTotal: data.FifoHppTotal,
|
||||
MovingAverageHppPerUnit: data.MovingAverageHppPerUnit,
|
||||
MovingAverageHppTotal: data.MovingAverageHppTotal,
|
||||
})
|
||||
}
|
||||
|
||||
@ -223,12 +229,15 @@ func (p *AnalyticsProcessorImpl) GetProductAnalyticsPerCategory(ctx context.Cont
|
||||
var resultData []models.ProductAnalyticsPerCategoryData
|
||||
for _, data := range analyticsData {
|
||||
resultData = append(resultData, models.ProductAnalyticsPerCategoryData{
|
||||
CategoryID: data.CategoryID,
|
||||
CategoryName: data.CategoryName,
|
||||
TotalRevenue: data.TotalRevenue,
|
||||
TotalQuantity: data.TotalQuantity,
|
||||
ProductCount: data.ProductCount,
|
||||
OrderCount: data.OrderCount,
|
||||
CategoryID: data.CategoryID,
|
||||
CategoryName: data.CategoryName,
|
||||
TotalRevenue: data.TotalRevenue,
|
||||
TotalQuantity: data.TotalQuantity,
|
||||
ProductCount: data.ProductCount,
|
||||
OrderCount: data.OrderCount,
|
||||
TotalStandardHpp: data.TotalStandardHpp,
|
||||
TotalFifoHpp: data.TotalFifoHpp,
|
||||
TotalMovingAverageHpp: data.TotalMovingAverageHpp,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -1,179 +0,0 @@
|
||||
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
|
||||
}
|
||||
@ -124,11 +124,45 @@ func (r *AnalyticsRepositoryImpl) GetProductAnalytics(ctx context.Context, organ
|
||||
WHEN SUM(oi.quantity) > 0 THEN COALESCE(SUM(oi.total_price), 0) / SUM(oi.quantity)
|
||||
ELSE 0
|
||||
END as average_price,
|
||||
COUNT(DISTINCT oi.order_id) as order_count
|
||||
COUNT(DISTINCT oi.order_id) as order_count,
|
||||
COALESCE((
|
||||
SELECT SUM(pr.quantity * (1 + COALESCE(pr.waste_percentage, 0)/100.0) * i.cost)
|
||||
FROM product_recipes pr
|
||||
JOIN ingredients i ON pr.ingredient_id = i.id
|
||||
WHERE pr.product_id = p.id
|
||||
), p.cost, 0) as standard_hpp_per_unit,
|
||||
COALESCE((
|
||||
SELECT SUM(pr.quantity * (1 + COALESCE(pr.waste_percentage, 0)/100.0) * i.cost)
|
||||
FROM product_recipes pr
|
||||
JOIN ingredients i ON pr.ingredient_id = i.id
|
||||
WHERE pr.product_id = p.id
|
||||
), p.cost, 0) * COALESCE(SUM(oi.quantity), 0) as standard_hpp_total,
|
||||
CASE
|
||||
WHEN SUM(oi.quantity) > 0 THEN COALESCE(SUM(oi.total_cost), 0) / SUM(oi.quantity)
|
||||
ELSE 0
|
||||
END as fifo_hpp_per_unit,
|
||||
COALESCE(SUM(oi.total_cost), 0) as fifo_hpp_total,
|
||||
COALESCE(mahpp.hpp_per_unit, p.cost, 0) as moving_average_hpp_per_unit,
|
||||
COALESCE(mahpp.hpp_per_unit, p.cost, 0) * COALESCE(SUM(oi.quantity), 0) as moving_average_hpp_total
|
||||
`).
|
||||
Joins("JOIN products p ON oi.product_id = p.id").
|
||||
Joins("JOIN categories c ON p.category_id = c.id").
|
||||
Joins("JOIN orders o ON oi.order_id = o.id").
|
||||
Joins("LEFT JOIN (?) mahpp ON mahpp.product_id = p.id",
|
||||
r.db.Table("product_recipes pr2").
|
||||
Select("pr2.product_id, SUM(pr2.quantity * (1 + COALESCE(pr2.waste_percentage, 0)/100.0) * COALESCE(ma.moving_avg_cost, ing.cost)) as hpp_per_unit").
|
||||
Joins("JOIN ingredients ing ON pr2.ingredient_id = ing.id").
|
||||
Joins("LEFT JOIN (?) ma ON ma.ingredient_id = pr2.ingredient_id",
|
||||
r.db.Table("inventory_movements im").
|
||||
Select("im.item_id as ingredient_id, CASE WHEN SUM(im.quantity) > 0 THEN SUM(im.total_cost) / SUM(im.quantity) ELSE 0 END as moving_avg_cost").
|
||||
Where("im.movement_type = ?", "purchase").
|
||||
Where("im.item_type = ?", "INGREDIENT").
|
||||
Where("im.organization_id = ?", organizationID).
|
||||
Where("im.created_at <= ?", dateTo).
|
||||
Group("im.item_id"),
|
||||
).
|
||||
Group("pr2.product_id"),
|
||||
).
|
||||
Where("o.organization_id = ?", organizationID).
|
||||
Where("o.is_void = ?", false).
|
||||
Where("o.is_refund = ?", false).
|
||||
@ -141,7 +175,7 @@ func (r *AnalyticsRepositoryImpl) GetProductAnalytics(ctx context.Context, organ
|
||||
}
|
||||
|
||||
err := query.
|
||||
Group("p.id, p.name, c.id, c.name, c.order").
|
||||
Group("p.id, p.name, p.cost, c.id, c.name, c.order, mahpp.hpp_per_unit").
|
||||
Order("revenue DESC").
|
||||
Limit(limit).
|
||||
Scan(&results).Error
|
||||
@ -160,11 +194,30 @@ func (r *AnalyticsRepositoryImpl) GetProductAnalyticsPerCategory(ctx context.Con
|
||||
COALESCE(SUM(CASE WHEN oi.is_fully_refunded = false THEN oi.total_price - COALESCE(oi.refund_amount, 0) ELSE 0 END), 0) as total_revenue,
|
||||
COALESCE(SUM(CASE WHEN oi.is_fully_refunded = false THEN oi.quantity - COALESCE(oi.refund_quantity, 0) ELSE 0 END), 0) as total_quantity,
|
||||
COUNT(DISTINCT p.id) as product_count,
|
||||
COUNT(DISTINCT oi.order_id) as order_count
|
||||
COUNT(DISTINCT oi.order_id) as order_count,
|
||||
COALESCE(SUM(CASE WHEN oi.is_fully_refunded = false THEN COALESCE(shpp.hpp_per_unit, p.cost, 0) * (oi.quantity - COALESCE(oi.refund_quantity, 0)) ELSE 0 END), 0) as total_standard_hpp,
|
||||
COALESCE(SUM(CASE WHEN oi.is_fully_refunded = false THEN oi.total_cost * ((oi.quantity - COALESCE(oi.refund_quantity, 0))::float / NULLIF(oi.quantity, 0)) ELSE 0 END), 0) as total_fifo_hpp,
|
||||
COALESCE(SUM(CASE WHEN oi.is_fully_refunded = false THEN COALESCE(mahpp.hpp_per_unit, p.cost, 0) * (oi.quantity - COALESCE(oi.refund_quantity, 0)) ELSE 0 END), 0) as total_moving_average_hpp
|
||||
`).
|
||||
Joins("JOIN products p ON oi.product_id = p.id").
|
||||
Joins("JOIN categories c ON p.category_id = c.id").
|
||||
Joins("JOIN orders o ON oi.order_id = o.id").
|
||||
Joins("LEFT JOIN (SELECT pr.product_id, SUM(pr.quantity * (1 + COALESCE(pr.waste_percentage, 0)/100.0) * i.cost) as hpp_per_unit FROM product_recipes pr JOIN ingredients i ON pr.ingredient_id = i.id GROUP BY pr.product_id) shpp ON shpp.product_id = p.id").
|
||||
Joins("LEFT JOIN (?) mahpp ON mahpp.product_id = p.id",
|
||||
r.db.Table("product_recipes pr2").
|
||||
Select("pr2.product_id, SUM(pr2.quantity * (1 + COALESCE(pr2.waste_percentage, 0)/100.0) * COALESCE(ma.moving_avg_cost, ing.cost)) as hpp_per_unit").
|
||||
Joins("JOIN ingredients ing ON pr2.ingredient_id = ing.id").
|
||||
Joins("LEFT JOIN (?) ma ON ma.ingredient_id = pr2.ingredient_id",
|
||||
r.db.Table("inventory_movements im").
|
||||
Select("im.item_id as ingredient_id, CASE WHEN SUM(im.quantity) > 0 THEN SUM(im.total_cost) / SUM(im.quantity) ELSE 0 END as moving_avg_cost").
|
||||
Where("im.movement_type = ?", "purchase").
|
||||
Where("im.item_type = ?", "INGREDIENT").
|
||||
Where("im.organization_id = ?", organizationID).
|
||||
Where("im.created_at <= ?", dateTo).
|
||||
Group("im.item_id"),
|
||||
).
|
||||
Group("pr2.product_id"),
|
||||
).
|
||||
Where("o.organization_id = ?", organizationID).
|
||||
Where("o.is_void = ?", false).
|
||||
Where("o.is_refund = ?", false).
|
||||
|
||||
@ -1,227 +0,0 @@
|
||||
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,71 +46,11 @@ type Router struct {
|
||||
customerAuthHandler *handler.CustomerAuthHandler
|
||||
customerPointsHandler *handler.CustomerPointsHandler
|
||||
spinGameHandler *handler.SpinGameHandler
|
||||
hppHandler *handler.HPPHandler
|
||||
authMiddleware *middleware.AuthMiddleware
|
||||
customerAuthMiddleware *middleware.CustomerAuthMiddleware
|
||||
}
|
||||
|
||||
func NewRouter(cfg *config.Config,
|
||||
healthHandler *handler.HealthHandler,
|
||||
authService service.AuthService,
|
||||
authMiddleware *middleware.AuthMiddleware,
|
||||
userService *service.UserServiceImpl,
|
||||
userValidator *validator.UserValidatorImpl,
|
||||
organizationService service.OrganizationService,
|
||||
organizationValidator validator.OrganizationValidator,
|
||||
outletService service.OutletService,
|
||||
outletValidator validator.OutletValidator,
|
||||
outletSettingService service.OutletSettingService,
|
||||
categoryService service.CategoryService,
|
||||
categoryValidator validator.CategoryValidator,
|
||||
productService service.ProductService,
|
||||
productValidator validator.ProductValidator,
|
||||
productVariantService service.ProductVariantService,
|
||||
productVariantValidator validator.ProductVariantValidator,
|
||||
inventoryService service.InventoryService,
|
||||
inventoryValidator validator.InventoryValidator,
|
||||
orderService service.OrderService,
|
||||
orderValidator validator.OrderValidator,
|
||||
fileService service.FileService,
|
||||
fileValidator validator.FileValidator,
|
||||
customerService service.CustomerService,
|
||||
customerValidator validator.CustomerValidator,
|
||||
paymentMethodService service.PaymentMethodService,
|
||||
paymentMethodValidator validator.PaymentMethodValidator,
|
||||
analyticsService *service.AnalyticsServiceImpl,
|
||||
reportService service.ReportService,
|
||||
tableService *service.TableServiceImpl,
|
||||
tableValidator *validator.TableValidator,
|
||||
unitService handler.UnitService,
|
||||
ingredientService handler.IngredientService,
|
||||
productRecipeService service.ProductRecipeService,
|
||||
vendorService service.VendorService,
|
||||
vendorValidator validator.VendorValidator,
|
||||
purchaseOrderService service.PurchaseOrderService,
|
||||
purchaseOrderValidator validator.PurchaseOrderValidator,
|
||||
unitConverterService service.IngredientUnitConverterService,
|
||||
unitConverterValidator validator.IngredientUnitConverterValidator,
|
||||
chartOfAccountTypeService service.ChartOfAccountTypeService,
|
||||
chartOfAccountTypeValidator validator.ChartOfAccountTypeValidator,
|
||||
chartOfAccountService service.ChartOfAccountService,
|
||||
chartOfAccountValidator validator.ChartOfAccountValidator,
|
||||
accountService service.AccountService,
|
||||
accountValidator validator.AccountValidator,
|
||||
orderIngredientTransactionService service.OrderIngredientTransactionService,
|
||||
orderIngredientTransactionValidator validator.OrderIngredientTransactionValidator,
|
||||
gamificationService service.GamificationService,
|
||||
gamificationValidator validator.GamificationValidator,
|
||||
rewardService service.RewardService,
|
||||
rewardValidator validator.RewardValidator,
|
||||
campaignService service.CampaignService,
|
||||
campaignValidator validator.CampaignValidator,
|
||||
customerAuthService service.CustomerAuthService,
|
||||
customerAuthValidator validator.CustomerAuthValidator,
|
||||
customerPointsService service.CustomerPointsService,
|
||||
spinGameService service.SpinGameService,
|
||||
hppService *service.HPPServiceImpl,
|
||||
customerAuthMiddleware *middleware.CustomerAuthMiddleware) *Router {
|
||||
func NewRouter(cfg *config.Config, healthHandler *handler.HealthHandler, authService service.AuthService, authMiddleware *middleware.AuthMiddleware, userService *service.UserServiceImpl, userValidator *validator.UserValidatorImpl, organizationService service.OrganizationService, organizationValidator validator.OrganizationValidator, outletService service.OutletService, outletValidator validator.OutletValidator, outletSettingService service.OutletSettingService, categoryService service.CategoryService, categoryValidator validator.CategoryValidator, productService service.ProductService, productValidator validator.ProductValidator, productVariantService service.ProductVariantService, productVariantValidator validator.ProductVariantValidator, inventoryService service.InventoryService, inventoryValidator validator.InventoryValidator, orderService service.OrderService, orderValidator validator.OrderValidator, fileService service.FileService, fileValidator validator.FileValidator, customerService service.CustomerService, customerValidator validator.CustomerValidator, paymentMethodService service.PaymentMethodService, paymentMethodValidator validator.PaymentMethodValidator, analyticsService *service.AnalyticsServiceImpl, reportService service.ReportService, tableService *service.TableServiceImpl, tableValidator *validator.TableValidator, unitService handler.UnitService, ingredientService handler.IngredientService, productRecipeService service.ProductRecipeService, vendorService service.VendorService, vendorValidator validator.VendorValidator, purchaseOrderService service.PurchaseOrderService, purchaseOrderValidator validator.PurchaseOrderValidator, unitConverterService service.IngredientUnitConverterService, unitConverterValidator validator.IngredientUnitConverterValidator, chartOfAccountTypeService service.ChartOfAccountTypeService, chartOfAccountTypeValidator validator.ChartOfAccountTypeValidator, chartOfAccountService service.ChartOfAccountService, chartOfAccountValidator validator.ChartOfAccountValidator, accountService service.AccountService, accountValidator validator.AccountValidator, orderIngredientTransactionService service.OrderIngredientTransactionService, orderIngredientTransactionValidator validator.OrderIngredientTransactionValidator, gamificationService service.GamificationService, gamificationValidator validator.GamificationValidator, rewardService service.RewardService, rewardValidator validator.RewardValidator, campaignService service.CampaignService, campaignValidator validator.CampaignValidator, customerAuthService service.CustomerAuthService, customerAuthValidator validator.CustomerAuthValidator, customerPointsService service.CustomerPointsService, spinGameService service.SpinGameService, customerAuthMiddleware *middleware.CustomerAuthMiddleware) *Router {
|
||||
|
||||
return &Router{
|
||||
config: cfg,
|
||||
@ -146,7 +86,6 @@ 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),
|
||||
@ -363,13 +302,6 @@ 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())
|
||||
{
|
||||
|
||||
@ -1,78 +0,0 @@
|
||||
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
|
||||
}
|
||||
@ -155,16 +155,22 @@ func ProductAnalyticsModelToContract(resp *models.ProductAnalyticsResponse) *con
|
||||
var data []contract.ProductAnalyticsData
|
||||
for _, item := range resp.Data {
|
||||
data = append(data, contract.ProductAnalyticsData{
|
||||
ProductID: item.ProductID,
|
||||
ProductName: item.ProductName,
|
||||
ProductSku: item.ProductSku,
|
||||
CategoryID: item.CategoryID,
|
||||
CategoryName: item.CategoryName,
|
||||
CategoryOrder: item.CategoryOrder,
|
||||
QuantitySold: item.QuantitySold,
|
||||
Revenue: item.Revenue,
|
||||
AveragePrice: item.AveragePrice,
|
||||
OrderCount: item.OrderCount,
|
||||
ProductID: item.ProductID,
|
||||
ProductName: item.ProductName,
|
||||
ProductSku: item.ProductSku,
|
||||
CategoryID: item.CategoryID,
|
||||
CategoryName: item.CategoryName,
|
||||
CategoryOrder: item.CategoryOrder,
|
||||
QuantitySold: item.QuantitySold,
|
||||
Revenue: item.Revenue,
|
||||
AveragePrice: item.AveragePrice,
|
||||
OrderCount: item.OrderCount,
|
||||
StandardHppPerUnit: item.StandardHppPerUnit,
|
||||
StandardHppTotal: item.StandardHppTotal,
|
||||
FifoHppPerUnit: item.FifoHppPerUnit,
|
||||
FifoHppTotal: item.FifoHppTotal,
|
||||
MovingAverageHppPerUnit: item.MovingAverageHppPerUnit,
|
||||
MovingAverageHppTotal: item.MovingAverageHppTotal,
|
||||
})
|
||||
}
|
||||
|
||||
@ -208,12 +214,15 @@ func ProductAnalyticsPerCategoryModelToContract(resp *models.ProductAnalyticsPer
|
||||
var data []contract.ProductAnalyticsPerCategoryData
|
||||
for _, item := range resp.Data {
|
||||
data = append(data, contract.ProductAnalyticsPerCategoryData{
|
||||
CategoryID: item.CategoryID,
|
||||
CategoryName: item.CategoryName,
|
||||
TotalRevenue: item.TotalRevenue,
|
||||
TotalQuantity: item.TotalQuantity,
|
||||
ProductCount: item.ProductCount,
|
||||
OrderCount: item.OrderCount,
|
||||
CategoryID: item.CategoryID,
|
||||
CategoryName: item.CategoryName,
|
||||
TotalRevenue: item.TotalRevenue,
|
||||
TotalQuantity: item.TotalQuantity,
|
||||
ProductCount: item.ProductCount,
|
||||
OrderCount: item.OrderCount,
|
||||
TotalStandardHpp: item.TotalStandardHpp,
|
||||
TotalFifoHpp: item.TotalFifoHpp,
|
||||
TotalMovingAverageHpp: item.TotalMovingAverageHpp,
|
||||
})
|
||||
}
|
||||
|
||||
@ -257,14 +266,20 @@ func DashboardAnalyticsModelToContract(resp *models.DashboardAnalyticsResponse)
|
||||
var topProducts []contract.ProductAnalyticsData
|
||||
for _, item := range resp.TopProducts {
|
||||
topProducts = append(topProducts, contract.ProductAnalyticsData{
|
||||
ProductID: item.ProductID,
|
||||
ProductName: item.ProductName,
|
||||
CategoryID: item.CategoryID,
|
||||
CategoryName: item.CategoryName,
|
||||
QuantitySold: item.QuantitySold,
|
||||
Revenue: item.Revenue,
|
||||
AveragePrice: item.AveragePrice,
|
||||
OrderCount: item.OrderCount,
|
||||
ProductID: item.ProductID,
|
||||
ProductName: item.ProductName,
|
||||
CategoryID: item.CategoryID,
|
||||
CategoryName: item.CategoryName,
|
||||
QuantitySold: item.QuantitySold,
|
||||
Revenue: item.Revenue,
|
||||
AveragePrice: item.AveragePrice,
|
||||
OrderCount: item.OrderCount,
|
||||
StandardHppPerUnit: item.StandardHppPerUnit,
|
||||
StandardHppTotal: item.StandardHppTotal,
|
||||
FifoHppPerUnit: item.FifoHppPerUnit,
|
||||
FifoHppTotal: item.FifoHppTotal,
|
||||
MovingAverageHppPerUnit: item.MovingAverageHppPerUnit,
|
||||
MovingAverageHppTotal: item.MovingAverageHppTotal,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -1,136 +0,0 @@
|
||||
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