Compare commits
12
Commits
b3359fa6ff
...
hpp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
75fdb8e847 | ||
|
|
8efa644680 | ||
|
|
ce99aef289 | ||
|
|
ba970229a9 | ||
|
|
9b606b4c8b | ||
|
|
d3dddea1c7 | ||
|
|
80a78137a0 | ||
|
|
3826a6b7a9 | ||
|
|
d695bedc97 | ||
|
|
3db4afbce6 | ||
|
|
535e4c84f6 | ||
|
|
f25ec1c06f |
@@ -2,6 +2,7 @@ package appcontext
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
@@ -77,5 +78,18 @@ func FromContext(ctx context.Context) *ContextInfo {
|
||||
if info, ok := ctx.Value(ctxKey).(*ContextInfo); ok {
|
||||
return info
|
||||
}
|
||||
return nil
|
||||
// Fallback: construct ContextInfo from individual context values
|
||||
return &ContextInfo{
|
||||
CorrelationID: value(ctx, CorrelationIDKey),
|
||||
UserID: uuidValue(ctx, UserIDKey),
|
||||
OutletID: uuidValue(ctx, OutletIDKey),
|
||||
OrganizationID: uuidValue(ctx, OrganizationIDKey),
|
||||
AppVersion: value(ctx, AppVersionKey),
|
||||
AppID: value(ctx, AppIDKey),
|
||||
AppType: value(ctx, AppTypeKey),
|
||||
Platform: value(ctx, PlatformKey),
|
||||
DeviceOS: value(ctx, DeviceOSKey),
|
||||
UserLocale: value(ctx, UserLocaleKey),
|
||||
UserRole: value(ctx, UserRoleKey),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -185,7 +185,7 @@ type CreatePaymentRequest struct {
|
||||
|
||||
type CreatePaymentOrderItemRequest struct {
|
||||
OrderItemID uuid.UUID `json:"order_item_id" validate:"required"`
|
||||
Amount float64 `json:"amount" validate:"required,min=0"`
|
||||
Amount float64 `json:"amount" validate:"min=0"`
|
||||
}
|
||||
|
||||
type PaymentResponse struct {
|
||||
|
||||
@@ -59,6 +59,7 @@ type ProductResponse struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
OrganizationID uuid.UUID `json:"organization_id"`
|
||||
CategoryID uuid.UUID `json:"category_id"`
|
||||
CategoryName string `json:"category_name"`
|
||||
SKU *string `json:"sku"`
|
||||
Name string `json:"name"`
|
||||
Description *string `json:"description"`
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -19,5 +19,5 @@ type Ingredient struct {
|
||||
Metadata Metadata `gorm:"type:jsonb;default:'{}'" json:"metadata"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
||||
Unit *Unit `gorm:"foreignKey:UnitID" json:"unit,omitempty"`
|
||||
Unit *Unit `gorm:"foreignKey:UnitID;references:ID" json:"unit,omitempty"`
|
||||
}
|
||||
|
||||
@@ -102,6 +102,37 @@ func (h *OutletHandler) GetOutlet(c *gin.Context) {
|
||||
util.HandleResponse(c.Writer, c.Request, outletResponse, "OutletHandler::GetOutlet")
|
||||
}
|
||||
|
||||
func (h *OutletHandler) CreateOutlet(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
contextInfo := appcontext.FromGinContext(ctx)
|
||||
|
||||
var req contract.CreateOutletRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
logger.FromContext(ctx).WithError(err).Error("OutletHandler::CreateOutlet -> Failed to bind JSON")
|
||||
validationResponseError := contract.NewResponseError(constants.MalformedFieldErrorCode, constants.RequestEntity, "Invalid request body")
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "OutletHandler::CreateOutlet")
|
||||
return
|
||||
}
|
||||
|
||||
req.OrganizationID = contextInfo.OrganizationID
|
||||
|
||||
validationError, validationErrorCode := h.outletValidator.ValidateCreateOutletRequest(&req)
|
||||
if validationError != nil {
|
||||
logger.FromContext(ctx).WithError(validationError).Error("OutletHandler::CreateOutlet -> request validation failed")
|
||||
validationResponseError := contract.NewResponseError(validationErrorCode, constants.RequestEntity, validationError.Error())
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "OutletHandler::CreateOutlet")
|
||||
return
|
||||
}
|
||||
|
||||
outletResponse := h.outletService.CreateOutlet(ctx, &req)
|
||||
if outletResponse.HasErrors() {
|
||||
errorResp := outletResponse.GetErrors()[0]
|
||||
logger.FromContext(ctx).WithError(errorResp).Error("OutletHandler::CreateOutlet -> Failed to create outlet from service")
|
||||
}
|
||||
|
||||
util.HandleResponse(c.Writer, c.Request, outletResponse, "OutletHandler::CreateOutlet")
|
||||
}
|
||||
|
||||
func (h *OutletHandler) UpdateOutlet(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
contextInfo := appcontext.FromGinContext(ctx)
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"apskel-pos-be/internal/constants"
|
||||
"apskel-pos-be/internal/entities"
|
||||
"apskel-pos-be/internal/models"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
func ProductEntityToModel(entity *entities.Product) *models.Product {
|
||||
@@ -118,10 +120,17 @@ func ProductEntityToResponse(entity *entities.Product) *models.ProductResponse {
|
||||
}
|
||||
}
|
||||
|
||||
// Get category name from the Category relation
|
||||
categoryName := ""
|
||||
if entity.Category.ID != uuid.Nil {
|
||||
categoryName = entity.Category.Name
|
||||
}
|
||||
|
||||
return &models.ProductResponse{
|
||||
ID: entity.ID,
|
||||
OrganizationID: entity.OrganizationID,
|
||||
CategoryID: entity.CategoryID,
|
||||
CategoryName: categoryName,
|
||||
SKU: entity.SKU,
|
||||
Name: entity.Name,
|
||||
Description: entity.Description,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -95,6 +95,7 @@ type ProductResponse struct {
|
||||
ID uuid.UUID
|
||||
OrganizationID uuid.UUID
|
||||
CategoryID uuid.UUID
|
||||
CategoryName string
|
||||
SKU *string
|
||||
Name string
|
||||
Description *string
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -180,7 +180,10 @@ func (p *IngredientProcessorImpl) UpdateIngredient(ctx context.Context, id uuid.
|
||||
}
|
||||
|
||||
// Update fields
|
||||
existingIngredient.OutletID = req.OutletID
|
||||
if req.OutletID != nil {
|
||||
existingIngredient.OutletID = req.OutletID
|
||||
}
|
||||
|
||||
existingIngredient.Name = req.Name
|
||||
existingIngredient.UnitID = req.UnitID
|
||||
existingIngredient.Cost = req.Cost
|
||||
|
||||
@@ -64,7 +64,7 @@ func (p *OutletProcessorImpl) GetOutletByID(ctx context.Context, organizationID,
|
||||
func (p *OutletProcessorImpl) CreateOutlet(ctx context.Context, req *models.CreateOutletRequest) (*models.OutletResponse, error) {
|
||||
// Get organization ID from context
|
||||
contextInfo := appcontext.FromContext(ctx)
|
||||
if contextInfo.OrganizationID == uuid.Nil {
|
||||
if contextInfo == nil || contextInfo.OrganizationID == uuid.Nil {
|
||||
return nil, fmt.Errorf("organization ID not found in context")
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@ func (p *OutletProcessorImpl) UpdateOutlet(ctx context.Context, outletID uuid.UU
|
||||
|
||||
func (p *OutletProcessorImpl) DeleteOutlet(ctx context.Context, outletID uuid.UUID) error {
|
||||
contextInfo := appcontext.FromContext(ctx)
|
||||
if contextInfo.OrganizationID == uuid.Nil {
|
||||
if contextInfo == nil || contextInfo.OrganizationID == uuid.Nil {
|
||||
return fmt.Errorf("organization ID not found in context")
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
package processor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"apskel-pos-be/internal/entities"
|
||||
"apskel-pos-be/internal/mappers"
|
||||
"apskel-pos-be/internal/models"
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
@@ -368,6 +367,8 @@ func (p *PurchaseOrderProcessorImpl) UpdatePurchaseOrderStatus(ctx context.Conte
|
||||
return nil, fmt.Errorf("purchase order not found: %w", err)
|
||||
}
|
||||
|
||||
fmt.Println("status:", po.Status)
|
||||
|
||||
// Check if status is changing to "received" and current status is not "received"
|
||||
if status == "received" && po.Status != "received" {
|
||||
// Get purchase order with items for inventory update
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -66,7 +66,11 @@ func (r *IngredientRepository) GetAll(ctx context.Context, organizationID uuid.U
|
||||
}
|
||||
|
||||
func (r *IngredientRepository) Update(ctx context.Context, ingredient *entities.Ingredient) error {
|
||||
result := r.db.WithContext(ctx).Where("id = ? AND organization_id = ?", ingredient.ID, ingredient.OrganizationID).Save(ingredient)
|
||||
result := r.db.WithContext(ctx).
|
||||
Model(&entities.Ingredient{}).
|
||||
Where("id = ? AND organization_id = ?", ingredient.ID, ingredient.OrganizationID).
|
||||
Select("outlet_id", "name", "unit_id", "cost", "stock", "is_semi_finished", "is_active", "metadata", "updated_at").
|
||||
Updates(ingredient)
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ func (r *ProductRecipeRepository) GetByID(ctx context.Context, id, organizationI
|
||||
Preload("Product").
|
||||
Preload("ProductVariant").
|
||||
Preload("Ingredient").
|
||||
Preload("Ingredient.Unit").
|
||||
Where("id = ? AND organization_id = ?", id, organizationID).
|
||||
First(&productRecipe).Error
|
||||
if err != nil {
|
||||
@@ -41,6 +42,7 @@ func (r *ProductRecipeRepository) GetByProductID(ctx context.Context, productID,
|
||||
Preload("Product").
|
||||
Preload("ProductVariant").
|
||||
Preload("Ingredient").
|
||||
Preload("Ingredient.Unit").
|
||||
Where("product_id = ? AND organization_id = ?", productID, organizationID).
|
||||
Order("created_at DESC").
|
||||
Find(&productRecipes).Error
|
||||
@@ -56,6 +58,7 @@ func (r *ProductRecipeRepository) GetByProductAndVariantID(ctx context.Context,
|
||||
Preload("Product").
|
||||
Preload("ProductVariant").
|
||||
Preload("Ingredient").
|
||||
Preload("Ingredient.Unit").
|
||||
Where("product_id = ? AND organization_id = ?", productID, organizationID)
|
||||
|
||||
if variantID != nil {
|
||||
@@ -77,6 +80,7 @@ func (r *ProductRecipeRepository) GetByIngredientID(ctx context.Context, ingredi
|
||||
Preload("Product").
|
||||
Preload("ProductVariant").
|
||||
Preload("Ingredient").
|
||||
Preload("Ingredient.Unit").
|
||||
Where("ingredient_id = ? AND organization_id = ?", ingredientID, organizationID).
|
||||
Order("created_at DESC").
|
||||
Find(&productRecipes).Error
|
||||
|
||||
+11
-59
@@ -50,65 +50,7 @@ type Router struct {
|
||||
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,
|
||||
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,6 +88,7 @@ func NewRouter(cfg *config.Config,
|
||||
spinGameHandler: handler.NewSpinGameHandler(spinGameService),
|
||||
authMiddleware: authMiddleware,
|
||||
customerAuthMiddleware: customerAuthMiddleware,
|
||||
productVariantHandler: handler.NewProductVariantHandler(productVariantService, productVariantValidator),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,6 +213,14 @@ func (r *Router) addAppRoutes(rg *gin.Engine) {
|
||||
products.DELETE("/:id", r.productHandler.DeleteProduct)
|
||||
}
|
||||
|
||||
productVariants := protected.Group("/product-variants")
|
||||
{
|
||||
productVariants.POST("", r.productVariantHandler.CreateProductVariant)
|
||||
productVariants.PUT("/:id", r.productVariantHandler.UpdateProductVariant)
|
||||
productVariants.DELETE("/:id", r.productVariantHandler.DeleteProductVariant)
|
||||
productVariants.GET("/:id", r.productVariantHandler.GetProductVariant)
|
||||
}
|
||||
|
||||
inventory := protected.Group("/inventory")
|
||||
inventory.Use(r.authMiddleware.RequireAdminOrManager())
|
||||
{
|
||||
@@ -594,6 +545,7 @@ func (r *Router) addAppRoutes(rg *gin.Engine) {
|
||||
outlets := protected.Group("/outlets")
|
||||
outlets.Use(r.authMiddleware.RequireAdminOrManager())
|
||||
{
|
||||
outlets.POST("", r.outletHandler.CreateOutlet)
|
||||
outlets.GET("/list", r.outletHandler.ListOutlets)
|
||||
outlets.GET("/detail/:id", r.outletHandler.GetOutlet)
|
||||
outlets.PUT("/detail/:id", r.outletHandler.UpdateOutlet)
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -101,6 +101,7 @@ func ProductModelResponseToResponse(prod *models.ProductResponse) *contract.Prod
|
||||
ID: prod.ID,
|
||||
OrganizationID: prod.OrganizationID,
|
||||
CategoryID: prod.CategoryID,
|
||||
CategoryName: prod.CategoryName,
|
||||
SKU: prod.SKU,
|
||||
Name: prod.Name,
|
||||
Description: prod.Description,
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
-- =========================
|
||||
-- DROP ORDER DISCOUNTS
|
||||
-- =========================
|
||||
DROP INDEX IF EXISTS idx_order_discounts_discount;
|
||||
DROP INDEX IF EXISTS idx_order_discounts_outlet;
|
||||
DROP INDEX IF EXISTS idx_order_discounts_order;
|
||||
DROP TABLE IF EXISTS order_discounts;
|
||||
|
||||
-- =========================
|
||||
-- DROP DISCOUNT CATEGORIES
|
||||
-- =========================
|
||||
DROP INDEX IF EXISTS idx_discount_categories_category;
|
||||
DROP INDEX IF EXISTS idx_discount_categories_discount;
|
||||
DROP TABLE IF EXISTS discount_categories;
|
||||
|
||||
-- =========================
|
||||
-- DROP DISCOUNT PRODUCTS
|
||||
-- =========================
|
||||
DROP INDEX IF EXISTS idx_discount_products_product;
|
||||
DROP INDEX IF EXISTS idx_discount_products_discount;
|
||||
DROP TABLE IF EXISTS discount_products;
|
||||
|
||||
-- =========================
|
||||
-- DROP DISCOUNT OUTLETS
|
||||
-- =========================
|
||||
DROP INDEX IF EXISTS idx_discount_outlets_outlet;
|
||||
DROP INDEX IF EXISTS idx_discount_outlets_discount;
|
||||
DROP TABLE IF EXISTS discount_outlets;
|
||||
|
||||
-- =========================
|
||||
-- DROP DISCOUNTS
|
||||
-- =========================
|
||||
DROP INDEX IF EXISTS idx_discounts_customer_type;
|
||||
DROP INDEX IF EXISTS idx_discounts_dates;
|
||||
DROP INDEX IF EXISTS idx_discounts_active;
|
||||
DROP INDEX IF EXISTS idx_discounts_code;
|
||||
DROP INDEX IF EXISTS idx_discounts_organization;
|
||||
DROP INDEX IF EXISTS idx_discounts_campaign;
|
||||
DROP TABLE IF EXISTS discounts;
|
||||
@@ -0,0 +1,91 @@
|
||||
CREATE TABLE discounts (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
campaign_id UUID NULL,
|
||||
organization_id UUID NOT NULL,
|
||||
code VARCHAR(50) NOT NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
type VARCHAR(20) NOT NULL CHECK (type IN ('percentage', 'fixed_amount', 'free_product')),
|
||||
value DECIMAL(15,2) NOT NULL,
|
||||
min_purchase_qty INT DEFAULT 0,
|
||||
min_purchase_amount DECIMAL(15,2) DEFAULT 0,
|
||||
start_date DATE NOT NULL,
|
||||
end_date DATE NOT NULL,
|
||||
usage_limit_per_customer INT DEFAULT NULL,
|
||||
usage_limit_total INT DEFAULT NULL,
|
||||
usage_count INT DEFAULT 0,
|
||||
customer_type VARCHAR(20) DEFAULT 'all' CHECK (customer_type IN ('all', 'member', 'vip')),
|
||||
is_stackable BOOLEAN DEFAULT FALSE,
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
priority INT DEFAULT 0,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (campaign_id) REFERENCES campaigns(id) ON DELETE SET NULL,
|
||||
FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE,
|
||||
CONSTRAINT chk_discount_ownership CHECK (campaign_id IS NOT NULL OR organization_id IS NOT NULL),
|
||||
CONSTRAINT unique_code_per_org UNIQUE (organization_id, code)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_discounts_campaign ON discounts(campaign_id);
|
||||
CREATE INDEX idx_discounts_organization ON discounts(organization_id);
|
||||
CREATE INDEX idx_discounts_code ON discounts(code);
|
||||
CREATE INDEX idx_discounts_active ON discounts(is_active);
|
||||
CREATE INDEX idx_discounts_dates ON discounts(start_date, end_date);
|
||||
CREATE INDEX idx_discounts_customer_type ON discounts(customer_type);
|
||||
|
||||
CREATE TABLE discount_outlets (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
discount_id UUID NOT NULL,
|
||||
outlet_id UUID NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (discount_id) REFERENCES discounts(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (outlet_id) REFERENCES outlets(id) ON DELETE CASCADE,
|
||||
CONSTRAINT unique_discount_outlet UNIQUE (discount_id, outlet_id)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_discount_outlets_discount ON discount_outlets(discount_id);
|
||||
CREATE INDEX idx_discount_outlets_outlet ON discount_outlets(outlet_id);
|
||||
|
||||
CREATE TABLE discount_products (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
discount_id UUID NOT NULL,
|
||||
product_id UUID NOT NULL,
|
||||
rule_type VARCHAR(20) NOT NULL CHECK (rule_type IN ('required', 'free', 'excluded')),
|
||||
quantity INT DEFAULT 1,
|
||||
free_quantity INT DEFAULT 0,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (discount_id) REFERENCES discounts(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX idx_discount_products_discount ON discount_products(discount_id);
|
||||
CREATE INDEX idx_discount_products_product ON discount_products(product_id);
|
||||
|
||||
CREATE TABLE discount_categories (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
discount_id UUID NOT NULL,
|
||||
category_id UUID NOT NULL,
|
||||
rule_type VARCHAR(20) NOT NULL CHECK (rule_type IN ('included', 'excluded')),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (discount_id) REFERENCES discounts(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX idx_discount_categories_discount ON discount_categories(discount_id);
|
||||
CREATE INDEX idx_discount_categories_category ON discount_categories(category_id);
|
||||
|
||||
CREATE TABLE order_discounts (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
order_id UUID NOT NULL,
|
||||
outlet_id UUID NOT NULL,
|
||||
discount_id UUID NOT NULL,
|
||||
discount_amount DECIMAL(15,2) NOT NULL,
|
||||
applied_rules JSON,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (outlet_id) REFERENCES outlets(id) ON DELETE RESTRICT,
|
||||
FOREIGN KEY (discount_id) REFERENCES discounts(id) ON DELETE RESTRICT
|
||||
);
|
||||
|
||||
CREATE INDEX idx_order_discounts_order ON order_discounts(order_id);
|
||||
CREATE INDEX idx_order_discounts_outlet ON order_discounts(outlet_id);
|
||||
CREATE INDEX idx_order_discounts_discount ON order_discounts(discount_id);
|
||||
Reference in New Issue
Block a user