Compare commits

..

No commits in common. "cd61ad0eb9f99883a55b7c5af2bfeeca37cedbad" and "957c1ae53da0e2825161c39bab684485320424a4" have entirely different histories.

22 changed files with 122 additions and 231 deletions

View File

@ -110,7 +110,6 @@ type OrderItemResponse struct {
CreatedAt time.Time `json:"created_at"` CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"` UpdatedAt time.Time `json:"updated_at"`
PrinterType string `json:"printer_type"` PrinterType string `json:"printer_type"`
PrintToChecker bool `json:"print_to_checker"`
PaidQuantity int `json:"paid_quantity"` PaidQuantity int `json:"paid_quantity"`
} }

View File

@ -17,7 +17,6 @@ type CreateProductRequest struct {
BusinessType *string `json:"business_type,omitempty"` BusinessType *string `json:"business_type,omitempty"`
ImageURL *string `json:"image_url,omitempty" validate:"omitempty,max=500"` ImageURL *string `json:"image_url,omitempty" validate:"omitempty,max=500"`
PrinterType *string `json:"printer_type,omitempty" validate:"omitempty,max=50"` PrinterType *string `json:"printer_type,omitempty" validate:"omitempty,max=50"`
PrintToChecker *bool `json:"print_to_checker,omitempty"`
Metadata map[string]interface{} `json:"metadata,omitempty"` Metadata map[string]interface{} `json:"metadata,omitempty"`
IsActive *bool `json:"is_active,omitempty"` IsActive *bool `json:"is_active,omitempty"`
Variants []CreateProductVariantRequest `json:"variants,omitempty"` Variants []CreateProductVariantRequest `json:"variants,omitempty"`
@ -27,20 +26,19 @@ type CreateProductRequest struct {
} }
type UpdateProductRequest struct { type UpdateProductRequest struct {
OutletID *uuid.UUID `json:"outlet_id,omitempty"` OutletID *uuid.UUID `json:"outlet_id,omitempty"`
CategoryID *uuid.UUID `json:"category_id,omitempty"` CategoryID *uuid.UUID `json:"category_id,omitempty"`
SKU *string `json:"sku,omitempty"` SKU *string `json:"sku,omitempty"`
Name *string `json:"name,omitempty" validate:"omitempty,min=1,max=255"` Name *string `json:"name,omitempty" validate:"omitempty,min=1,max=255"`
Description *string `json:"description,omitempty"` Description *string `json:"description,omitempty"`
Price *float64 `json:"price,omitempty" validate:"omitempty,min=0"` Price *float64 `json:"price,omitempty" validate:"omitempty,min=0"`
Cost *float64 `json:"cost,omitempty" validate:"omitempty,min=0"` Cost *float64 `json:"cost,omitempty" validate:"omitempty,min=0"`
BusinessType *string `json:"business_type,omitempty"` BusinessType *string `json:"business_type,omitempty"`
ImageURL *string `json:"image_url,omitempty" validate:"omitempty,max=500"` ImageURL *string `json:"image_url,omitempty" validate:"omitempty,max=500"`
PrinterType *string `json:"printer_type,omitempty" validate:"omitempty,max=50"` PrinterType *string `json:"printer_type,omitempty" validate:"omitempty,max=50"`
PrintToChecker *bool `json:"print_to_checker,omitempty"` Metadata map[string]interface{} `json:"metadata,omitempty"`
Metadata map[string]interface{} `json:"metadata,omitempty"` IsActive *bool `json:"is_active,omitempty"`
IsActive *bool `json:"is_active,omitempty"` ReorderLevel *int `json:"reorder_level,omitempty" validate:"omitempty,min=0"`
ReorderLevel *int `json:"reorder_level,omitempty" validate:"omitempty,min=0"`
} }
type CreateProductVariantRequest struct { type CreateProductVariantRequest struct {
@ -73,7 +71,6 @@ type ProductResponse struct {
BusinessType string `json:"business_type"` BusinessType string `json:"business_type"`
ImageURL *string `json:"image_url"` ImageURL *string `json:"image_url"`
PrinterType string `json:"printer_type"` PrinterType string `json:"printer_type"`
PrintToChecker bool `json:"print_to_checker"`
Metadata map[string]interface{} `json:"metadata"` Metadata map[string]interface{} `json:"metadata"`
IsActive bool `json:"is_active"` IsActive bool `json:"is_active"`
CreatedAt time.Time `json:"created_at"` CreatedAt time.Time `json:"created_at"`

View File

@ -7,26 +7,23 @@ import (
) )
type CreateProductOutletPriceRequest struct { type CreateProductOutletPriceRequest struct {
ProductID uuid.UUID `json:"product_id" validate:"required"` ProductID uuid.UUID `json:"product_id" validate:"required"`
OutletID uuid.UUID `json:"outlet_id" validate:"required"` OutletID uuid.UUID `json:"outlet_id" validate:"required"`
Price float64 `json:"price" validate:"required,min=0"` Price float64 `json:"price" validate:"required,min=0"`
PrintToChecker bool `json:"print_to_checker"`
} }
type UpdateProductOutletPriceRequest struct { type UpdateProductOutletPriceRequest struct {
Price float64 `json:"price" validate:"required,min=0"` Price float64 `json:"price" validate:"required,min=0"`
PrintToChecker *bool `json:"print_to_checker"`
} }
type ProductOutletPriceResponse struct { type ProductOutletPriceResponse struct {
ID uuid.UUID `json:"id,omitempty"` ID uuid.UUID `json:"id,omitempty"`
ProductID uuid.UUID `json:"product_id,omitempty"` ProductID uuid.UUID `json:"product_id,omitempty"`
OutletID uuid.UUID `json:"outlet_id"` OutletID uuid.UUID `json:"outlet_id"`
OutletName string `json:"outlet_name,omitempty"` OutletName string `json:"outlet_name,omitempty"`
Price float64 `json:"price"` Price float64 `json:"price"`
PrintToChecker bool `json:"print_to_checker"` CreatedAt time.Time `json:"created_at,omitempty"`
CreatedAt time.Time `json:"created_at,omitempty"` UpdatedAt time.Time `json:"updated_at,omitempty"`
UpdatedAt time.Time `json:"updated_at,omitempty"`
} }
type ListProductOutletPricesResponse struct { type ListProductOutletPricesResponse struct {
@ -40,7 +37,6 @@ type BulkCreateProductOutletPriceRequest struct {
} }
type CreateProductOutletPricePerOutletRequest struct { type CreateProductOutletPricePerOutletRequest struct {
OutletID uuid.UUID `json:"outlet_id" validate:"required"` OutletID uuid.UUID `json:"outlet_id" validate:"required"`
Price float64 `json:"price" validate:"required,min=0"` Price float64 `json:"price" validate:"required,min=0"`
PrintToChecker bool `json:"print_to_checker"`
} }

View File

@ -26,14 +26,13 @@ type Product struct {
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"` CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"` UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
Organization Organization `gorm:"foreignKey:OrganizationID" json:"organization,omitempty"` Organization Organization `gorm:"foreignKey:OrganizationID" json:"organization,omitempty"`
Category Category `gorm:"foreignKey:CategoryID" json:"category,omitempty"` Category Category `gorm:"foreignKey:CategoryID" json:"category,omitempty"`
Unit *Unit `gorm:"foreignKey:UnitID" json:"unit,omitempty"` Unit *Unit `gorm:"foreignKey:UnitID" json:"unit,omitempty"`
ProductVariants []ProductVariant `gorm:"foreignKey:ProductID" json:"variants,omitempty"` ProductVariants []ProductVariant `gorm:"foreignKey:ProductID" json:"variants,omitempty"`
ProductRecipes []ProductRecipe `gorm:"foreignKey:ProductID" json:"product_recipes,omitempty"` ProductRecipes []ProductRecipe `gorm:"foreignKey:ProductID" json:"product_recipes,omitempty"`
Inventory []Inventory `gorm:"foreignKey:ProductID" json:"inventory,omitempty"` Inventory []Inventory `gorm:"foreignKey:ProductID" json:"inventory,omitempty"`
OrderItems []OrderItem `gorm:"foreignKey:ProductID" json:"order_items,omitempty"` OrderItems []OrderItem `gorm:"foreignKey:ProductID" json:"order_items,omitempty"`
ProductOutletPrices []ProductOutletPrice `gorm:"foreignKey:ProductID" json:"product_outlet_prices,omitempty"`
} }
func (p *Product) BeforeCreate(tx *gorm.DB) error { func (p *Product) BeforeCreate(tx *gorm.DB) error {

View File

@ -8,13 +8,12 @@ import (
) )
type ProductOutletPrice struct { type ProductOutletPrice struct {
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"` ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
ProductID uuid.UUID `gorm:"type:uuid;not null;index" json:"product_id"` ProductID uuid.UUID `gorm:"type:uuid;not null;index" json:"product_id"`
OutletID uuid.UUID `gorm:"type:uuid;not null;index" json:"outlet_id"` OutletID uuid.UUID `gorm:"type:uuid;not null;index" json:"outlet_id"`
Price float64 `gorm:"type:decimal(10,2);not null" json:"price"` Price float64 `gorm:"type:decimal(10,2);not null" json:"price"`
PrintToChecker bool `gorm:"not null;default:true" json:"print_to_checker"` CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"` UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
Product Product `gorm:"foreignKey:ProductID" json:"product,omitempty"` Product Product `gorm:"foreignKey:ProductID" json:"product,omitempty"`
Outlet Outlet `gorm:"foreignKey:OutletID" json:"outlet,omitempty"` Outlet Outlet `gorm:"foreignKey:OutletID" json:"outlet,omitempty"`

View File

@ -82,7 +82,7 @@ func OrderEntityToResponse(order *entities.Order) *models.OrderResponse {
} }
for i, item := range order.OrderItems { for i, item := range order.OrderItems {
resp := OrderItemEntityToResponse(&item, order.OutletID) resp := OrderItemEntityToResponse(&item)
if resp != nil { if resp != nil {
resp.PaidQuantity = paidQtyByOrderItem[item.ID] resp.PaidQuantity = paidQtyByOrderItem[item.ID]
response.OrderItems[i] = *resp response.OrderItems[i] = *resp
@ -101,20 +101,11 @@ func OrderEntityToResponse(order *entities.Order) *models.OrderResponse {
return response return response
} }
func OrderItemEntityToResponse(item *entities.OrderItem, outletID uuid.UUID) *models.OrderItemResponse { func OrderItemEntityToResponse(item *entities.OrderItem) *models.OrderItemResponse {
if item == nil { if item == nil {
return nil return nil
} }
// Resolve print_to_checker from preloaded outlet prices
printToChecker := true // default
for _, op := range item.Product.ProductOutletPrices {
if op.OutletID == outletID {
printToChecker = op.PrintToChecker
break
}
}
response := &models.OrderItemResponse{ response := &models.OrderItemResponse{
ID: item.ID, ID: item.ID,
OrderID: item.OrderID, OrderID: item.OrderID,
@ -139,7 +130,6 @@ func OrderItemEntityToResponse(item *entities.OrderItem, outletID uuid.UUID) *mo
CreatedAt: item.CreatedAt, CreatedAt: item.CreatedAt,
UpdatedAt: item.UpdatedAt, UpdatedAt: item.UpdatedAt,
PrinterType: item.Product.PrinterType, PrinterType: item.Product.PrinterType,
PrintToChecker: printToChecker,
} }
if item.Product.ID != uuid.Nil { if item.Product.ID != uuid.Nil {
@ -334,14 +324,14 @@ func OrderEntitiesToResponses(orders []*entities.Order) []models.OrderResponse {
return responses return responses
} }
func OrderItemEntitiesToResponses(items []*entities.OrderItem, outletID uuid.UUID) []models.OrderItemResponse { func OrderItemEntitiesToResponses(items []*entities.OrderItem) []models.OrderItemResponse {
if items == nil { if items == nil {
return nil return nil
} }
responses := make([]models.OrderItemResponse, len(items)) responses := make([]models.OrderItemResponse, len(items))
for i, item := range items { for i, item := range items {
response := OrderItemEntityToResponse(item, outletID) response := OrderItemEntityToResponse(item)
if response != nil { if response != nil {
responses[i] = *response responses[i] = *response
} }

View File

@ -45,7 +45,7 @@ func TestOrderItemEntityToResponse_WithProductNames(t *testing.T) {
} }
// Act // Act
result := OrderItemEntityToResponse(orderItem, uuid.Nil) result := OrderItemEntityToResponse(orderItem)
// Assert // Assert
assert.NotNil(t, result) assert.NotNil(t, result)
@ -89,7 +89,7 @@ func TestOrderItemEntityToResponse_WithoutProductVariant(t *testing.T) {
} }
// Act // Act
result := OrderItemEntityToResponse(orderItem, uuid.Nil) result := OrderItemEntityToResponse(orderItem)
// Assert // Assert
assert.NotNil(t, result) assert.NotNil(t, result)
@ -129,7 +129,7 @@ func TestOrderItemEntityToResponse_WithoutProductPreload(t *testing.T) {
} }
// Act // Act
result := OrderItemEntityToResponse(orderItem, uuid.Nil) result := OrderItemEntityToResponse(orderItem)
// Assert // Assert
assert.NotNil(t, result) assert.NotNil(t, result)

View File

@ -11,13 +11,12 @@ func ProductOutletPriceEntityToModel(entity *entities.ProductOutletPrice) *model
} }
return &models.ProductOutletPrice{ return &models.ProductOutletPrice{
ID: entity.ID, ID: entity.ID,
ProductID: entity.ProductID, ProductID: entity.ProductID,
OutletID: entity.OutletID, OutletID: entity.OutletID,
Price: entity.Price, Price: entity.Price,
PrintToChecker: entity.PrintToChecker, CreatedAt: entity.CreatedAt,
CreatedAt: entity.CreatedAt, UpdatedAt: entity.UpdatedAt,
UpdatedAt: entity.UpdatedAt,
} }
} }
@ -27,13 +26,12 @@ func ProductOutletPriceModelToEntity(model *models.ProductOutletPrice) *entities
} }
return &entities.ProductOutletPrice{ return &entities.ProductOutletPrice{
ID: model.ID, ID: model.ID,
ProductID: model.ProductID, ProductID: model.ProductID,
OutletID: model.OutletID, OutletID: model.OutletID,
Price: model.Price, Price: model.Price,
PrintToChecker: model.PrintToChecker, CreatedAt: model.CreatedAt,
CreatedAt: model.CreatedAt, UpdatedAt: model.UpdatedAt,
UpdatedAt: model.UpdatedAt,
} }
} }

View File

@ -209,7 +209,6 @@ type OrderItemResponse struct {
CreatedAt time.Time CreatedAt time.Time
UpdatedAt time.Time UpdatedAt time.Time
PrinterType string PrinterType string
PrintToChecker bool
PaidQuantity int PaidQuantity int
} }

View File

@ -50,7 +50,6 @@ type CreateProductRequest struct {
BusinessType constants.BusinessType `validate:"required"` BusinessType constants.BusinessType `validate:"required"`
ImageURL *string `validate:"omitempty,max=500"` ImageURL *string `validate:"omitempty,max=500"`
PrinterType *string `validate:"omitempty,max=50"` PrinterType *string `validate:"omitempty,max=50"`
PrintToChecker *bool `validate:"omitempty"`
UnitID *uuid.UUID `validate:"omitempty"` UnitID *uuid.UUID `validate:"omitempty"`
HasIngredients bool `validate:"omitempty"` HasIngredients bool `validate:"omitempty"`
Metadata map[string]interface{} Metadata map[string]interface{}
@ -71,7 +70,6 @@ type UpdateProductRequest struct {
Cost *float64 `validate:"omitempty,min=0"` Cost *float64 `validate:"omitempty,min=0"`
ImageURL *string `validate:"omitempty,max=500"` ImageURL *string `validate:"omitempty,max=500"`
PrinterType *string `validate:"omitempty,max=50"` PrinterType *string `validate:"omitempty,max=50"`
PrintToChecker *bool `validate:"omitempty"`
UnitID *uuid.UUID `validate:"omitempty"` UnitID *uuid.UUID `validate:"omitempty"`
HasIngredients *bool `validate:"omitempty"` HasIngredients *bool `validate:"omitempty"`
Metadata map[string]interface{} Metadata map[string]interface{}
@ -110,7 +108,6 @@ type ProductResponse struct {
BusinessType constants.BusinessType BusinessType constants.BusinessType
ImageURL *string ImageURL *string
PrinterType string PrinterType string
PrintToChecker bool
UnitID *uuid.UUID UnitID *uuid.UUID
HasIngredients bool HasIngredients bool
Metadata map[string]interface{} Metadata map[string]interface{}
@ -121,10 +118,9 @@ type ProductResponse struct {
} }
type OutletPrice struct { type OutletPrice struct {
OutletID uuid.UUID OutletID uuid.UUID
OutletName string OutletName string
Price float64 Price float64
PrintToChecker bool
} }
type ProductVariantResponse struct { type ProductVariantResponse struct {

View File

@ -7,25 +7,22 @@ import (
) )
type ProductOutletPrice struct { type ProductOutletPrice struct {
ID uuid.UUID ID uuid.UUID
ProductID uuid.UUID ProductID uuid.UUID
OutletID uuid.UUID OutletID uuid.UUID
Price float64 Price float64
PrintToChecker bool CreatedAt time.Time
CreatedAt time.Time UpdatedAt time.Time
UpdatedAt time.Time
} }
type CreateProductOutletPriceRequest struct { type CreateProductOutletPriceRequest struct {
ProductID uuid.UUID `validate:"required"` ProductID uuid.UUID `validate:"required"`
OutletID uuid.UUID `validate:"required"` OutletID uuid.UUID `validate:"required"`
Price float64 `validate:"required,min=0"` Price float64 `validate:"required,min=0"`
PrintToChecker bool
} }
type UpdateProductOutletPriceRequest struct { type UpdateProductOutletPriceRequest struct {
Price *float64 `validate:"required,min=0"` Price *float64 `validate:"required,min=0"`
PrintToChecker *bool
} }
type ProductOutletPriceResponse struct { type ProductOutletPriceResponse struct {

View File

@ -387,7 +387,7 @@ func (p *OrderProcessorImpl) AddToOrder(ctx context.Context, orderID uuid.UUID,
return nil, fmt.Errorf("failed to create order item: %w", err) return nil, fmt.Errorf("failed to create order item: %w", err)
} }
itemResponse := mappers.OrderItemEntityToResponse(orderItem, order.OutletID) itemResponse := mappers.OrderItemEntityToResponse(orderItem)
if itemResponse != nil { if itemResponse != nil {
addedItemResponses = append(addedItemResponses, *itemResponse) addedItemResponses = append(addedItemResponses, *itemResponse)
} }

View File

@ -46,10 +46,9 @@ func (p *ProductOutletPriceProcessorImpl) Upsert(ctx context.Context, req *model
} }
entity := &entities.ProductOutletPrice{ entity := &entities.ProductOutletPrice{
ProductID: req.ProductID, ProductID: req.ProductID,
OutletID: req.OutletID, OutletID: req.OutletID,
Price: req.Price, Price: req.Price,
PrintToChecker: req.PrintToChecker,
} }
if err := p.repo.Upsert(ctx, entity); err != nil { if err := p.repo.Upsert(ctx, entity); err != nil {

View File

@ -5,7 +5,6 @@ import (
"fmt" "fmt"
"apskel-pos-be/internal/entities" "apskel-pos-be/internal/entities"
"apskel-pos-be/internal/logger"
"apskel-pos-be/internal/mappers" "apskel-pos-be/internal/mappers"
"apskel-pos-be/internal/models" "apskel-pos-be/internal/models"
"apskel-pos-be/internal/repository" "apskel-pos-be/internal/repository"
@ -126,15 +125,10 @@ func (p *ProductProcessorImpl) CreateProduct(ctx context.Context, req *models.Cr
// Upsert outlet-specific price if outlet context is present // Upsert outlet-specific price if outlet context is present
if req.OutletID != uuid.Nil { if req.OutletID != uuid.Nil {
printToChecker := true // default
if req.PrintToChecker != nil {
printToChecker = *req.PrintToChecker
}
outletPriceEntity := &entities.ProductOutletPrice{ outletPriceEntity := &entities.ProductOutletPrice{
ProductID: productEntity.ID, ProductID: productEntity.ID,
OutletID: req.OutletID, OutletID: req.OutletID,
Price: req.Price, Price: req.Price,
PrintToChecker: printToChecker,
} }
if err := p.outletPriceRepo.Upsert(ctx, outletPriceEntity); err != nil { if err := p.outletPriceRepo.Upsert(ctx, outletPriceEntity); err != nil {
return nil, fmt.Errorf("failed to assign outlet price: %w", err) return nil, fmt.Errorf("failed to assign outlet price: %w", err)
@ -202,39 +196,16 @@ func (p *ProductProcessorImpl) UpdateProduct(ctx context.Context, id uuid.UUID,
} }
} }
// Upsert outlet-specific price if outlet context is present and price or print_to_checker is provided // Upsert outlet-specific price if outlet context is present
if req.OutletID != uuid.Nil && (req.Price != nil || req.PrintToChecker != nil) { if req.OutletID != uuid.Nil && req.Price != nil {
// Fetch existing outlet price to use as fallback for fields not provided
existing, _ := p.outletPriceRepo.GetByProductAndOutlet(ctx, id, req.OutletID)
price := float64(0)
if existing != nil {
price = existing.Price
}
if req.Price != nil {
price = *req.Price
}
printToChecker := true // default
if existing != nil {
printToChecker = existing.PrintToChecker
}
if req.PrintToChecker != nil {
printToChecker = *req.PrintToChecker
}
outletPriceEntity := &entities.ProductOutletPrice{ outletPriceEntity := &entities.ProductOutletPrice{
ProductID: id, ProductID: id,
OutletID: req.OutletID, OutletID: req.OutletID,
Price: price, Price: *req.Price,
PrintToChecker: printToChecker,
} }
logger.FromContext(ctx).Infof("ProductProcessor::UpdateProduct -> upserting outlet price: productID=%s outletID=%s price=%f printToChecker=%v", id, req.OutletID, price, printToChecker)
if err := p.outletPriceRepo.Upsert(ctx, outletPriceEntity); err != nil { if err := p.outletPriceRepo.Upsert(ctx, outletPriceEntity); err != nil {
return nil, fmt.Errorf("failed to assign outlet price: %w", err) return nil, fmt.Errorf("failed to assign outlet price: %w", err)
} }
} else {
logger.FromContext(ctx).Infof("ProductProcessor::UpdateProduct -> skipping outlet price upsert: outletID=%s price=%v printToChecker=%v", req.OutletID, req.Price, req.PrintToChecker)
} }
productWithCategory, err := p.productRepo.GetWithCategory(ctx, id) productWithCategory, err := p.productRepo.GetWithCategory(ctx, id)
@ -285,7 +256,6 @@ func (p *ProductProcessorImpl) GetProductByID(ctx context.Context, id uuid.UUID,
outletPrice, err := p.outletPriceRepo.GetByProductAndOutlet(ctx, id, outletID) outletPrice, err := p.outletPriceRepo.GetByProductAndOutlet(ctx, id, outletID)
if err == nil { if err == nil {
response.OutletPrice = &outletPrice.Price response.OutletPrice = &outletPrice.Price
response.PrintToChecker = outletPrice.PrintToChecker
} }
} else { } else {
// No outlet context — return all outlet prices for this product // No outlet context — return all outlet prices for this product
@ -294,10 +264,9 @@ func (p *ProductProcessorImpl) GetProductByID(ctx context.Context, id uuid.UUID,
prices := make([]models.OutletPrice, len(outletPrices)) prices := make([]models.OutletPrice, len(outletPrices))
for i, op := range outletPrices { for i, op := range outletPrices {
prices[i] = models.OutletPrice{ prices[i] = models.OutletPrice{
OutletID: op.OutletID, OutletID: op.OutletID,
OutletName: op.Outlet.Name, OutletName: op.Outlet.Name,
Price: op.Price, Price: op.Price,
PrintToChecker: op.PrintToChecker,
} }
} }
response.OutletPrices = prices response.OutletPrices = prices
@ -334,35 +303,10 @@ func (p *ProductProcessorImpl) ListProducts(ctx context.Context, filters map[str
} }
responses := make([]models.ProductResponse, len(productEntities)) responses := make([]models.ProductResponse, len(productEntities))
if outletID != uuid.Nil && len(productEntities) > 0 { for i, entity := range productEntities {
// Bulk-fetch outlet prices to populate OutletPrice and PrintToChecker per product response := mappers.ProductEntityToResponse(entity)
productIDs := make([]uuid.UUID, len(productEntities)) if response != nil {
for i, e := range productEntities { responses[i] = *response
productIDs[i] = e.ID
}
outletPrices, opErr := p.outletPriceRepo.GetByProductsAndOutlet(ctx, productIDs, outletID)
priceMap := make(map[uuid.UUID]*entities.ProductOutletPrice)
if opErr == nil {
for _, op := range outletPrices {
priceMap[op.ProductID] = op
}
}
for i, entity := range productEntities {
response := mappers.ProductEntityToResponse(entity)
if response != nil {
if op, ok := priceMap[entity.ID]; ok {
response.OutletPrice = &op.Price
response.PrintToChecker = op.PrintToChecker
}
responses[i] = *response
}
}
} else {
for i, entity := range productEntities {
response := mappers.ProductEntityToResponse(entity)
if response != nil {
responses[i] = *response
}
} }
} }

View File

@ -61,7 +61,6 @@ func (r *OrderRepositoryImpl) GetWithRelations(ctx context.Context, id uuid.UUID
Preload("OrderItems"). Preload("OrderItems").
Preload("OrderItems.Product"). Preload("OrderItems.Product").
Preload("OrderItems.Product.Category"). Preload("OrderItems.Product.Category").
Preload("OrderItems.Product.ProductOutletPrices").
Preload("OrderItems.ProductVariant"). Preload("OrderItems.ProductVariant").
Preload("Payments"). Preload("Payments").
Preload("Payments.PaymentMethod"). Preload("Payments.PaymentMethod").
@ -142,7 +141,6 @@ func (r *OrderRepositoryImpl) List(ctx context.Context, filters map[string]inter
Preload("OrderItems"). Preload("OrderItems").
Preload("OrderItems.Product"). Preload("OrderItems.Product").
Preload("OrderItems.Product.Category"). Preload("OrderItems.Product.Category").
Preload("OrderItems.Product.ProductOutletPrices").
Preload("OrderItems.ProductVariant"). Preload("OrderItems.ProductVariant").
Preload("Payments"). Preload("Payments").
Preload("Payments.PaymentMethod"). Preload("Payments.PaymentMethod").
@ -160,7 +158,6 @@ func (r *OrderRepositoryImpl) ListBySessionID(ctx context.Context, sessionID str
Preload("OrderItems"). Preload("OrderItems").
Preload("OrderItems.Product"). Preload("OrderItems.Product").
Preload("OrderItems.Product.Category"). Preload("OrderItems.Product.Category").
Preload("OrderItems.Product.ProductOutletPrices").
Preload("OrderItems.ProductVariant"). Preload("OrderItems.ProductVariant").
Preload("Payments"). Preload("Payments").
Preload("Payments.PaymentMethod"). Preload("Payments.PaymentMethod").

View File

@ -7,6 +7,7 @@ import (
"github.com/google/uuid" "github.com/google/uuid"
"gorm.io/gorm" "gorm.io/gorm"
"gorm.io/gorm/clause"
) )
type ProductOutletPriceRepository interface { type ProductOutletPriceRepository interface {
@ -52,18 +53,10 @@ func (r *ProductOutletPriceRepositoryImpl) GetByOutlet(ctx context.Context, outl
} }
func (r *ProductOutletPriceRepositoryImpl) Upsert(ctx context.Context, price *entities.ProductOutletPrice) error { func (r *ProductOutletPriceRepositoryImpl) Upsert(ctx context.Context, price *entities.ProductOutletPrice) error {
if price.ID == uuid.Nil { return r.db.WithContext(ctx).Clauses(clause.OnConflict{
price.ID = uuid.New() Columns: []clause.Column{{Name: "product_id"}, {Name: "outlet_id"}},
} DoUpdates: clause.AssignmentColumns([]string{"price", "updated_at"}),
return r.db.WithContext(ctx).Exec(` }).Create(price).Error
INSERT INTO product_outlet_prices (id, product_id, outlet_id, price, print_to_checker, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, NOW(), NOW())
ON CONFLICT (product_id, outlet_id)
DO UPDATE SET
price = EXCLUDED.price,
print_to_checker = EXCLUDED.print_to_checker,
updated_at = NOW()
`, price.ID, price.ProductID, price.OutletID, price.Price, price.PrintToChecker).Error
} }
func (r *ProductOutletPriceRepositoryImpl) Delete(ctx context.Context, id uuid.UUID) error { func (r *ProductOutletPriceRepositoryImpl) Delete(ctx context.Context, id uuid.UUID) error {

View File

@ -105,10 +105,9 @@ func (s *ProductOutletPriceServiceImpl) BulkUpsert(ctx context.Context, req *con
prices := make([]models.CreateProductOutletPriceRequest, len(req.Prices)) prices := make([]models.CreateProductOutletPriceRequest, len(req.Prices))
for i, p := range req.Prices { for i, p := range req.Prices {
prices[i] = models.CreateProductOutletPriceRequest{ prices[i] = models.CreateProductOutletPriceRequest{
ProductID: req.ProductID, ProductID: req.ProductID,
OutletID: p.OutletID, OutletID: p.OutletID,
Price: p.Price, Price: p.Price,
PrintToChecker: p.PrintToChecker,
} }
} }

View File

@ -112,7 +112,6 @@ func OrderModelToContract(resp *models.OrderResponse) *contract.OrderResponse {
CreatedAt: item.CreatedAt, CreatedAt: item.CreatedAt,
UpdatedAt: item.UpdatedAt, UpdatedAt: item.UpdatedAt,
PrinterType: item.PrinterType, PrinterType: item.PrinterType,
PrintToChecker: item.PrintToChecker,
PaidQuantity: item.PaidQuantity, PaidQuantity: item.PaidQuantity,
} }
} }
@ -182,7 +181,6 @@ func AddToOrderModelToContract(resp *models.AddToOrderResponse) *contract.AddToO
Status: string(item.Status), Status: string(item.Status),
CreatedAt: item.CreatedAt, CreatedAt: item.CreatedAt,
UpdatedAt: item.UpdatedAt, UpdatedAt: item.UpdatedAt,
PrintToChecker: item.PrintToChecker,
} }
} }
return &contract.AddToOrderResponse{ return &contract.AddToOrderResponse{

View File

@ -11,10 +11,9 @@ func CreateProductOutletPriceRequestToModel(req *contract.CreateProductOutletPri
} }
return &models.CreateProductOutletPriceRequest{ return &models.CreateProductOutletPriceRequest{
ProductID: req.ProductID, ProductID: req.ProductID,
OutletID: req.OutletID, OutletID: req.OutletID,
Price: req.Price, Price: req.Price,
PrintToChecker: req.PrintToChecker,
} }
} }
@ -24,8 +23,7 @@ func UpdateProductOutletPriceRequestToModel(req *contract.UpdateProductOutletPri
} }
return &models.UpdateProductOutletPriceRequest{ return &models.UpdateProductOutletPriceRequest{
Price: &req.Price, Price: &req.Price,
PrintToChecker: req.PrintToChecker,
} }
} }
@ -35,13 +33,12 @@ func ProductOutletPriceModelToResponse(m *models.ProductOutletPrice) *contract.P
} }
return &contract.ProductOutletPriceResponse{ return &contract.ProductOutletPriceResponse{
ID: m.ID, ID: m.ID,
ProductID: m.ProductID, ProductID: m.ProductID,
OutletID: m.OutletID, OutletID: m.OutletID,
Price: m.Price, Price: m.Price,
PrintToChecker: m.PrintToChecker, CreatedAt: m.CreatedAt,
CreatedAt: m.CreatedAt, UpdatedAt: m.UpdatedAt,
UpdatedAt: m.UpdatedAt,
} }
} }

View File

@ -57,7 +57,6 @@ func CreateProductRequestToModel(apctx *appcontext.ContextInfo, req *contract.Cr
BusinessType: businessType, BusinessType: businessType,
ImageURL: req.ImageURL, ImageURL: req.ImageURL,
PrinterType: req.PrinterType, PrinterType: req.PrinterType,
PrintToChecker: req.PrintToChecker,
Metadata: metadata, Metadata: metadata,
Variants: variants, Variants: variants,
} }
@ -76,18 +75,17 @@ func UpdateProductRequestToModel(apctx *appcontext.ContextInfo, req *contract.Up
} }
return &models.UpdateProductRequest{ return &models.UpdateProductRequest{
OutletID: outletID, OutletID: outletID,
CategoryID: req.CategoryID, CategoryID: req.CategoryID,
SKU: req.SKU, SKU: req.SKU,
Name: req.Name, Name: req.Name,
Description: req.Description, Description: req.Description,
Price: req.Price, Price: req.Price,
Cost: req.Cost, Cost: req.Cost,
ImageURL: req.ImageURL, ImageURL: req.ImageURL,
PrinterType: req.PrinterType, PrinterType: req.PrinterType,
PrintToChecker: req.PrintToChecker, Metadata: metadata,
Metadata: metadata, IsActive: req.IsActive,
IsActive: req.IsActive,
} }
} }
@ -121,10 +119,9 @@ func ProductModelResponseToResponse(prod *models.ProductResponse) *contract.Prod
outletPriceResponses = make([]contract.ProductOutletPriceResponse, len(prod.OutletPrices)) outletPriceResponses = make([]contract.ProductOutletPriceResponse, len(prod.OutletPrices))
for i, op := range prod.OutletPrices { for i, op := range prod.OutletPrices {
outletPriceResponses[i] = contract.ProductOutletPriceResponse{ outletPriceResponses[i] = contract.ProductOutletPriceResponse{
OutletID: op.OutletID, OutletID: op.OutletID,
OutletName: op.OutletName, OutletName: op.OutletName,
Price: op.Price, Price: op.Price,
PrintToChecker: op.PrintToChecker,
} }
} }
} }
@ -144,7 +141,6 @@ func ProductModelResponseToResponse(prod *models.ProductResponse) *contract.Prod
BusinessType: string(prod.BusinessType), BusinessType: string(prod.BusinessType),
ImageURL: prod.ImageURL, ImageURL: prod.ImageURL,
PrinterType: prod.PrinterType, PrinterType: prod.PrinterType,
PrintToChecker: prod.PrintToChecker,
Metadata: prod.Metadata, Metadata: prod.Metadata,
IsActive: prod.IsActive, IsActive: prod.IsActive,
CreatedAt: prod.CreatedAt, CreatedAt: prod.CreatedAt,

View File

@ -1 +0,0 @@
ALTER TABLE product_outlet_prices DROP COLUMN IF EXISTS print_to_checker;

View File

@ -1 +0,0 @@
ALTER TABLE product_outlet_prices ADD COLUMN print_to_checker BOOLEAN NOT NULL DEFAULT TRUE;