fix
This commit is contained in:
parent
75ec5274d2
commit
4f6208e479
@ -167,7 +167,6 @@ type repositories struct {
|
|||||||
chartOfAccountRepo *repository.ChartOfAccountRepositoryImpl
|
chartOfAccountRepo *repository.ChartOfAccountRepositoryImpl
|
||||||
accountRepo *repository.AccountRepositoryImpl
|
accountRepo *repository.AccountRepositoryImpl
|
||||||
orderIngredientTransactionRepo *repository.OrderIngredientTransactionRepositoryImpl
|
orderIngredientTransactionRepo *repository.OrderIngredientTransactionRepositoryImpl
|
||||||
productIngredientRepo *repository.ProductIngredientRepository
|
|
||||||
txManager *repository.TxManager
|
txManager *repository.TxManager
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -201,10 +200,6 @@ func (a *App) initRepositories() *repositories {
|
|||||||
chartOfAccountRepo: repository.NewChartOfAccountRepositoryImpl(a.db),
|
chartOfAccountRepo: repository.NewChartOfAccountRepositoryImpl(a.db),
|
||||||
accountRepo: repository.NewAccountRepositoryImpl(a.db),
|
accountRepo: repository.NewAccountRepositoryImpl(a.db),
|
||||||
orderIngredientTransactionRepo: repository.NewOrderIngredientTransactionRepositoryImpl(a.db).(*repository.OrderIngredientTransactionRepositoryImpl),
|
orderIngredientTransactionRepo: repository.NewOrderIngredientTransactionRepositoryImpl(a.db).(*repository.OrderIngredientTransactionRepositoryImpl),
|
||||||
productIngredientRepo: func() *repository.ProductIngredientRepository {
|
|
||||||
db, _ := a.db.DB()
|
|
||||||
return repository.NewProductIngredientRepository(db)
|
|
||||||
}(),
|
|
||||||
txManager: repository.NewTxManager(a.db),
|
txManager: repository.NewTxManager(a.db),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -266,7 +261,7 @@ func (a *App) initProcessors(cfg *config.Config, repos *repositories) *processor
|
|||||||
chartOfAccountTypeProcessor: processor.NewChartOfAccountTypeProcessorImpl(repos.chartOfAccountTypeRepo),
|
chartOfAccountTypeProcessor: processor.NewChartOfAccountTypeProcessorImpl(repos.chartOfAccountTypeRepo),
|
||||||
chartOfAccountProcessor: processor.NewChartOfAccountProcessorImpl(repos.chartOfAccountRepo, repos.chartOfAccountTypeRepo),
|
chartOfAccountProcessor: processor.NewChartOfAccountProcessorImpl(repos.chartOfAccountRepo, repos.chartOfAccountTypeRepo),
|
||||||
accountProcessor: processor.NewAccountProcessorImpl(repos.accountRepo, repos.chartOfAccountRepo),
|
accountProcessor: processor.NewAccountProcessorImpl(repos.accountRepo, repos.chartOfAccountRepo),
|
||||||
orderIngredientTransactionProcessor: processor.NewOrderIngredientTransactionProcessorImpl(repos.orderIngredientTransactionRepo, repos.productIngredientRepo, repos.ingredientRepo, repos.unitRepo).(*processor.OrderIngredientTransactionProcessorImpl),
|
orderIngredientTransactionProcessor: processor.NewOrderIngredientTransactionProcessorImpl(repos.orderIngredientTransactionRepo, repos.productRecipeRepo, repos.ingredientRepo, repos.unitRepo).(*processor.OrderIngredientTransactionProcessorImpl),
|
||||||
fileClient: fileClient,
|
fileClient: fileClient,
|
||||||
inventoryMovementService: inventoryMovementService,
|
inventoryMovementService: inventoryMovementService,
|
||||||
}
|
}
|
||||||
@ -312,7 +307,7 @@ func (a *App) initServices(processors *processors, repos *repositories, cfg *con
|
|||||||
productService := service.NewProductService(processors.productProcessor)
|
productService := service.NewProductService(processors.productProcessor)
|
||||||
productVariantService := service.NewProductVariantService(processors.productVariantProcessor)
|
productVariantService := service.NewProductVariantService(processors.productVariantProcessor)
|
||||||
inventoryService := service.NewInventoryService(processors.inventoryProcessor)
|
inventoryService := service.NewInventoryService(processors.inventoryProcessor)
|
||||||
orderService := service.NewOrderServiceImpl(processors.orderProcessor, repos.tableRepo, nil, processors.orderIngredientTransactionProcessor, *repos.productIngredientRepo, repos.txManager) // Will be updated after orderIngredientTransactionService is created
|
orderService := service.NewOrderServiceImpl(processors.orderProcessor, repos.tableRepo, nil, processors.orderIngredientTransactionProcessor, *repos.productRecipeRepo, repos.txManager) // Will be updated after orderIngredientTransactionService is created
|
||||||
paymentMethodService := service.NewPaymentMethodService(processors.paymentMethodProcessor)
|
paymentMethodService := service.NewPaymentMethodService(processors.paymentMethodProcessor)
|
||||||
fileService := service.NewFileServiceImpl(processors.fileProcessor)
|
fileService := service.NewFileServiceImpl(processors.fileProcessor)
|
||||||
var customerService service.CustomerService = service.NewCustomerService(processors.customerProcessor)
|
var customerService service.CustomerService = service.NewCustomerService(processors.customerProcessor)
|
||||||
@ -331,7 +326,7 @@ func (a *App) initServices(processors *processors, repos *repositories, cfg *con
|
|||||||
orderIngredientTransactionService := service.NewOrderIngredientTransactionService(processors.orderIngredientTransactionProcessor, repos.txManager)
|
orderIngredientTransactionService := service.NewOrderIngredientTransactionService(processors.orderIngredientTransactionProcessor, repos.txManager)
|
||||||
|
|
||||||
// Update order service with order ingredient transaction service
|
// Update order service with order ingredient transaction service
|
||||||
orderService = service.NewOrderServiceImpl(processors.orderProcessor, repos.tableRepo, orderIngredientTransactionService, processors.orderIngredientTransactionProcessor, *repos.productIngredientRepo, repos.txManager)
|
orderService = service.NewOrderServiceImpl(processors.orderProcessor, repos.tableRepo, orderIngredientTransactionService, processors.orderIngredientTransactionProcessor, *repos.productRecipeRepo, repos.txManager)
|
||||||
|
|
||||||
return &services{
|
return &services{
|
||||||
userService: service.NewUserService(processors.userProcessor),
|
userService: service.NewUserService(processors.userProcessor),
|
||||||
|
|||||||
@ -1,18 +1,74 @@
|
|||||||
package contract
|
package contract
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"apskel-pos-be/internal/models"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ProductRecipeContract interface {
|
// Request structures
|
||||||
Create(request *models.CreateProductRecipeRequest, organizationID uuid.UUID) (*models.ProductRecipeResponse, error)
|
type CreateProductRecipeRequest struct {
|
||||||
GetByID(id uuid.UUID, organizationID uuid.UUID) (*models.ProductRecipeResponse, error)
|
OutletID *uuid.UUID `json:"outlet_id"`
|
||||||
GetByProductID(productID uuid.UUID, organizationID uuid.UUID) ([]*models.ProductRecipeResponse, error)
|
ProductID uuid.UUID `json:"product_id" validate:"required"`
|
||||||
GetByProductAndVariantID(productID uuid.UUID, variantID *uuid.UUID, organizationID uuid.UUID) ([]*models.ProductRecipeResponse, error)
|
VariantID *uuid.UUID `json:"variant_id"`
|
||||||
GetByIngredientID(ingredientID uuid.UUID, organizationID uuid.UUID) ([]*models.ProductRecipeResponse, error)
|
IngredientID uuid.UUID `json:"ingredient_id" validate:"required"`
|
||||||
Update(id uuid.UUID, request *models.UpdateProductRecipeRequest, organizationID uuid.UUID) (*models.ProductRecipeResponse, error)
|
Quantity float64 `json:"quantity" validate:"required,gt=0"`
|
||||||
Delete(id uuid.UUID, organizationID uuid.UUID) error
|
WastePercentage float64 `json:"waste_percentage" validate:"min=0,max=100"`
|
||||||
BulkCreate(recipes []models.CreateProductRecipeRequest, organizationID uuid.UUID) ([]*models.ProductRecipeResponse, error)
|
}
|
||||||
|
|
||||||
|
type UpdateProductRecipeRequest struct {
|
||||||
|
OutletID *uuid.UUID `json:"outlet_id"`
|
||||||
|
VariantID *uuid.UUID `json:"variant_id"`
|
||||||
|
Quantity float64 `json:"quantity" validate:"required,gt=0"`
|
||||||
|
WastePercentage float64 `json:"waste_percentage" validate:"min=0,max=100"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetProductRecipeByProductIDRequest struct {
|
||||||
|
ProductID uuid.UUID `json:"-"`
|
||||||
|
VariantID *uuid.UUID `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BulkCreateProductRecipeRequest struct {
|
||||||
|
Recipes []CreateProductRecipeRequest `json:"recipes" validate:"required,min=1"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response structures
|
||||||
|
type ProductRecipeResponse struct {
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
OrganizationID uuid.UUID `json:"organization_id"`
|
||||||
|
OutletID *uuid.UUID `json:"outlet_id"`
|
||||||
|
ProductID uuid.UUID `json:"product_id"`
|
||||||
|
VariantID *uuid.UUID `json:"variant_id"`
|
||||||
|
IngredientID uuid.UUID `json:"ingredient_id"`
|
||||||
|
Quantity float64 `json:"quantity"`
|
||||||
|
WastePercentage float64 `json:"waste_percentage"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
Product *ProductResponse `json:"product,omitempty"`
|
||||||
|
ProductVariant *ProductVariantResponse `json:"product_variant,omitempty"`
|
||||||
|
Ingredient *ProductRecipeIngredientResponse `json:"ingredient,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProductRecipeIngredientResponse struct {
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
OrganizationID uuid.UUID `json:"organization_id"`
|
||||||
|
OutletID *uuid.UUID `json:"outlet_id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
UnitID uuid.UUID `json:"unit_id"`
|
||||||
|
Cost float64 `json:"cost"`
|
||||||
|
Stock float64 `json:"stock"`
|
||||||
|
IsSemiFinished bool `json:"is_semi_finished"`
|
||||||
|
IsActive bool `json:"is_active"`
|
||||||
|
Metadata map[string]interface{} `json:"metadata"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
Unit *ProductRecipeUnitResponse `json:"unit,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProductRecipeUnitResponse struct {
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Symbol string `json:"symbol"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
}
|
}
|
||||||
@ -9,8 +9,8 @@ import (
|
|||||||
type CreatePurchaseOrderRequest struct {
|
type CreatePurchaseOrderRequest struct {
|
||||||
VendorID uuid.UUID `json:"vendor_id" validate:"required"`
|
VendorID uuid.UUID `json:"vendor_id" validate:"required"`
|
||||||
PONumber string `json:"po_number" validate:"required,min=1,max=50"`
|
PONumber string `json:"po_number" validate:"required,min=1,max=50"`
|
||||||
TransactionDate time.Time `json:"transaction_date" validate:"required"`
|
TransactionDate string `json:"transaction_date" validate:"required"` // Format: YYYY-MM-DD
|
||||||
DueDate time.Time `json:"due_date" validate:"required"`
|
DueDate string `json:"due_date" validate:"required"` // Format: YYYY-MM-DD
|
||||||
Reference *string `json:"reference,omitempty" validate:"omitempty,max=100"`
|
Reference *string `json:"reference,omitempty" validate:"omitempty,max=100"`
|
||||||
Status *string `json:"status,omitempty" validate:"omitempty,oneof=draft sent approved received cancelled"`
|
Status *string `json:"status,omitempty" validate:"omitempty,oneof=draft sent approved received cancelled"`
|
||||||
Message *string `json:"message,omitempty" validate:"omitempty"`
|
Message *string `json:"message,omitempty" validate:"omitempty"`
|
||||||
@ -29,8 +29,8 @@ type CreatePurchaseOrderItemRequest struct {
|
|||||||
type UpdatePurchaseOrderRequest struct {
|
type UpdatePurchaseOrderRequest struct {
|
||||||
VendorID *uuid.UUID `json:"vendor_id,omitempty" validate:"omitempty"`
|
VendorID *uuid.UUID `json:"vendor_id,omitempty" validate:"omitempty"`
|
||||||
PONumber *string `json:"po_number,omitempty" validate:"omitempty,min=1,max=50"`
|
PONumber *string `json:"po_number,omitempty" validate:"omitempty,min=1,max=50"`
|
||||||
TransactionDate *time.Time `json:"transaction_date,omitempty" validate:"omitempty"`
|
TransactionDate *string `json:"transaction_date,omitempty" validate:"omitempty"` // Format: YYYY-MM-DD
|
||||||
DueDate *time.Time `json:"due_date,omitempty" validate:"omitempty"`
|
DueDate *string `json:"due_date,omitempty" validate:"omitempty"` // Format: YYYY-MM-DD
|
||||||
Reference *string `json:"reference,omitempty" validate:"omitempty,max=100"`
|
Reference *string `json:"reference,omitempty" validate:"omitempty,max=100"`
|
||||||
Status *string `json:"status,omitempty" validate:"omitempty,oneof=draft sent approved received cancelled"`
|
Status *string `json:"status,omitempty" validate:"omitempty,oneof=draft sent approved received cancelled"`
|
||||||
Message *string `json:"message,omitempty" validate:"omitempty"`
|
Message *string `json:"message,omitempty" validate:"omitempty"`
|
||||||
|
|||||||
@ -30,7 +30,7 @@ type Product struct {
|
|||||||
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"`
|
||||||
ProductIngredients []ProductIngredient `gorm:"foreignKey:ProductID" json:"product_ingredients,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"`
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,15 +8,16 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type ProductRecipe struct {
|
type ProductRecipe 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"`
|
||||||
OrganizationID uuid.UUID `gorm:"type:uuid;not null;index" json:"organization_id"`
|
OrganizationID uuid.UUID `gorm:"type:uuid;not null;index" json:"organization_id"`
|
||||||
OutletID *uuid.UUID `gorm:"type:uuid;index" json:"outlet_id"`
|
OutletID *uuid.UUID `gorm:"type:uuid;index" json:"outlet_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"`
|
||||||
VariantID *uuid.UUID `gorm:"type:uuid;index" json:"variant_id"`
|
VariantID *uuid.UUID `gorm:"type:uuid;index" json:"variant_id"`
|
||||||
IngredientID uuid.UUID `gorm:"type:uuid;not null;index" json:"ingredient_id"`
|
IngredientID uuid.UUID `gorm:"type:uuid;not null;index" json:"ingredient_id"`
|
||||||
Quantity float64 `gorm:"type:decimal(12,3);not null" json:"quantity"`
|
Quantity float64 `gorm:"type:decimal(12,3);not null" json:"quantity"`
|
||||||
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
WastePercentage float64 `gorm:"type:decimal(5,2);default:0" json:"waste_percentage"`
|
||||||
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||||
|
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
||||||
|
|
||||||
// Relations
|
// Relations
|
||||||
Product *Product `gorm:"foreignKey:ProductID" json:"product,omitempty"`
|
Product *Product `gorm:"foreignKey:ProductID" json:"product,omitempty"`
|
||||||
|
|||||||
@ -5,7 +5,6 @@ import (
|
|||||||
"apskel-pos-be/internal/constants"
|
"apskel-pos-be/internal/constants"
|
||||||
"apskel-pos-be/internal/contract"
|
"apskel-pos-be/internal/contract"
|
||||||
"apskel-pos-be/internal/logger"
|
"apskel-pos-be/internal/logger"
|
||||||
"apskel-pos-be/internal/models"
|
|
||||||
"apskel-pos-be/internal/service"
|
"apskel-pos-be/internal/service"
|
||||||
"apskel-pos-be/internal/util"
|
"apskel-pos-be/internal/util"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -28,7 +27,7 @@ func (h *ProductRecipeHandler) Create(c *gin.Context) {
|
|||||||
ctx := c.Request.Context()
|
ctx := c.Request.Context()
|
||||||
contextInfo := appcontext.FromGinContext(ctx)
|
contextInfo := appcontext.FromGinContext(ctx)
|
||||||
|
|
||||||
var request models.CreateProductRecipeRequest
|
var request contract.CreateProductRecipeRequest
|
||||||
if err := c.ShouldBindJSON(&request); err != nil {
|
if err := c.ShouldBindJSON(&request); err != nil {
|
||||||
logger.FromContext(ctx).WithError(err).Error("ProductRecipeHandler::Create -> request binding failed")
|
logger.FromContext(ctx).WithError(err).Error("ProductRecipeHandler::Create -> request binding failed")
|
||||||
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, err.Error())
|
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, err.Error())
|
||||||
@ -75,6 +74,7 @@ func (h *ProductRecipeHandler) GetByProductID(c *gin.Context) {
|
|||||||
ctx := c.Request.Context()
|
ctx := c.Request.Context()
|
||||||
contextInfo := appcontext.FromGinContext(ctx)
|
contextInfo := appcontext.FromGinContext(ctx)
|
||||||
|
|
||||||
|
// Parse product ID from URL parameter
|
||||||
productIDStr := c.Param("product_id")
|
productIDStr := c.Param("product_id")
|
||||||
productID, err := uuid.Parse(productIDStr)
|
productID, err := uuid.Parse(productIDStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -84,10 +84,9 @@ func (h *ProductRecipeHandler) GetByProductID(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if variant_id is provided
|
// Parse optional variant ID from query parameter
|
||||||
variantIDStr := c.Query("variant_id")
|
|
||||||
var variantID *uuid.UUID
|
var variantID *uuid.UUID
|
||||||
if variantIDStr != "" {
|
if variantIDStr := c.Query("variant_id"); variantIDStr != "" {
|
||||||
parsed, err := uuid.Parse(variantIDStr)
|
parsed, err := uuid.Parse(variantIDStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.FromContext(ctx).WithError(err).Error("ProductRecipeHandler::GetByProductID -> Invalid variant ID")
|
logger.FromContext(ctx).WithError(err).Error("ProductRecipeHandler::GetByProductID -> Invalid variant ID")
|
||||||
@ -98,15 +97,14 @@ func (h *ProductRecipeHandler) GetByProductID(c *gin.Context) {
|
|||||||
variantID = &parsed
|
variantID = &parsed
|
||||||
}
|
}
|
||||||
|
|
||||||
var recipes []*models.ProductRecipeResponse
|
// Create request object
|
||||||
if variantIDStr != "" {
|
request := &contract.GetProductRecipeByProductIDRequest{
|
||||||
// Get by product and variant ID
|
ProductID: productID,
|
||||||
recipes, err = h.productRecipeService.GetByProductAndVariantID(ctx, productID, variantID, contextInfo.OrganizationID)
|
VariantID: variantID,
|
||||||
} else {
|
|
||||||
// Get by product ID only
|
|
||||||
recipes, err = h.productRecipeService.GetByProductID(ctx, productID, contextInfo.OrganizationID)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Call service
|
||||||
|
recipes, err := h.productRecipeService.GetByProductID(ctx, request, contextInfo.OrganizationID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.FromContext(ctx).WithError(err).Error("ProductRecipeHandler::GetByProductID -> Failed to get product recipes")
|
logger.FromContext(ctx).WithError(err).Error("ProductRecipeHandler::GetByProductID -> Failed to get product recipes")
|
||||||
validationResponseError := contract.NewResponseError(constants.InternalServerErrorCode, constants.RequestEntity, err.Error())
|
validationResponseError := contract.NewResponseError(constants.InternalServerErrorCode, constants.RequestEntity, err.Error())
|
||||||
@ -154,7 +152,7 @@ func (h *ProductRecipeHandler) Update(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var request models.UpdateProductRecipeRequest
|
var request contract.UpdateProductRecipeRequest
|
||||||
if err := c.ShouldBindJSON(&request); err != nil {
|
if err := c.ShouldBindJSON(&request); err != nil {
|
||||||
logger.FromContext(ctx).WithError(err).Error("ProductRecipeHandler::Update -> request binding failed")
|
logger.FromContext(ctx).WithError(err).Error("ProductRecipeHandler::Update -> request binding failed")
|
||||||
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, err.Error())
|
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, err.Error())
|
||||||
@ -204,10 +202,7 @@ func (h *ProductRecipeHandler) BulkCreate(c *gin.Context) {
|
|||||||
ctx := c.Request.Context()
|
ctx := c.Request.Context()
|
||||||
contextInfo := appcontext.FromGinContext(ctx)
|
contextInfo := appcontext.FromGinContext(ctx)
|
||||||
|
|
||||||
var request struct {
|
var request contract.BulkCreateProductRecipeRequest
|
||||||
Recipes []models.CreateProductRecipeRequest `json:"recipes" validate:"required,min=1"`
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := c.ShouldBindJSON(&request); err != nil {
|
if err := c.ShouldBindJSON(&request); err != nil {
|
||||||
logger.FromContext(ctx).WithError(err).Error("ProductRecipeHandler::BulkCreate -> request binding failed")
|
logger.FromContext(ctx).WithError(err).Error("ProductRecipeHandler::BulkCreate -> request binding failed")
|
||||||
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, err.Error())
|
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, err.Error())
|
||||||
@ -215,7 +210,7 @@ func (h *ProductRecipeHandler) BulkCreate(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
recipes, err := h.productRecipeService.BulkCreate(ctx, contextInfo.OrganizationID, request.Recipes)
|
recipes, err := h.productRecipeService.BulkCreate(ctx, contextInfo.OrganizationID, &request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.FromContext(ctx).WithError(err).Error("ProductRecipeHandler::BulkCreate -> Failed to bulk create product recipes")
|
logger.FromContext(ctx).WithError(err).Error("ProductRecipeHandler::BulkCreate -> Failed to bulk create product recipes")
|
||||||
validationResponseError := contract.NewResponseError(constants.InternalServerErrorCode, constants.RequestEntity, err.Error())
|
validationResponseError := contract.NewResponseError(constants.InternalServerErrorCode, constants.RequestEntity, err.Error())
|
||||||
|
|||||||
@ -7,15 +7,16 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type ProductRecipe struct {
|
type ProductRecipe struct {
|
||||||
ID uuid.UUID `json:"id"`
|
ID uuid.UUID `json:"id"`
|
||||||
OrganizationID uuid.UUID `json:"organization_id"`
|
OrganizationID uuid.UUID `json:"organization_id"`
|
||||||
OutletID *uuid.UUID `json:"outlet_id"`
|
OutletID *uuid.UUID `json:"outlet_id"`
|
||||||
ProductID uuid.UUID `json:"product_id"`
|
ProductID uuid.UUID `json:"product_id"`
|
||||||
VariantID *uuid.UUID `json:"variant_id"`
|
VariantID *uuid.UUID `json:"variant_id"`
|
||||||
IngredientID uuid.UUID `json:"ingredient_id"`
|
IngredientID uuid.UUID `json:"ingredient_id"`
|
||||||
Quantity float64 `json:"quantity"`
|
Quantity float64 `json:"quantity"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
WastePercentage float64 `json:"waste_percentage"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
|
||||||
// Relations
|
// Relations
|
||||||
Product *Product `json:"product,omitempty"`
|
Product *Product `json:"product,omitempty"`
|
||||||
@ -24,29 +25,32 @@ type ProductRecipe struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type CreateProductRecipeRequest struct {
|
type CreateProductRecipeRequest struct {
|
||||||
OutletID *uuid.UUID `json:"outlet_id"`
|
OutletID *uuid.UUID `json:"outlet_id"`
|
||||||
ProductID uuid.UUID `json:"product_id" validate:"required"`
|
ProductID uuid.UUID `json:"product_id" validate:"required"`
|
||||||
VariantID *uuid.UUID `json:"variant_id"`
|
VariantID *uuid.UUID `json:"variant_id"`
|
||||||
IngredientID uuid.UUID `json:"ingredient_id" validate:"required"`
|
IngredientID uuid.UUID `json:"ingredient_id" validate:"required"`
|
||||||
Quantity float64 `json:"quantity" validate:"required,gt=0"`
|
Quantity float64 `json:"quantity" validate:"required,gt=0"`
|
||||||
|
WastePercentage float64 `json:"waste_percentage" validate:"min=0,max=100"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpdateProductRecipeRequest struct {
|
type UpdateProductRecipeRequest struct {
|
||||||
OutletID *uuid.UUID `json:"outlet_id"`
|
OutletID *uuid.UUID `json:"outlet_id"`
|
||||||
VariantID *uuid.UUID `json:"variant_id"`
|
VariantID *uuid.UUID `json:"variant_id"`
|
||||||
Quantity float64 `json:"quantity" validate:"required,gt=0"`
|
Quantity float64 `json:"quantity" validate:"required,gt=0"`
|
||||||
|
WastePercentage float64 `json:"waste_percentage" validate:"min=0,max=100"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProductRecipeResponse struct {
|
type ProductRecipeResponse struct {
|
||||||
ID uuid.UUID `json:"id"`
|
ID uuid.UUID `json:"id"`
|
||||||
OrganizationID uuid.UUID `json:"organization_id"`
|
OrganizationID uuid.UUID `json:"organization_id"`
|
||||||
OutletID *uuid.UUID `json:"outlet_id"`
|
OutletID *uuid.UUID `json:"outlet_id"`
|
||||||
ProductID uuid.UUID `json:"product_id"`
|
ProductID uuid.UUID `json:"product_id"`
|
||||||
VariantID *uuid.UUID `json:"variant_id"`
|
VariantID *uuid.UUID `json:"variant_id"`
|
||||||
IngredientID uuid.UUID `json:"ingredient_id"`
|
IngredientID uuid.UUID `json:"ingredient_id"`
|
||||||
Quantity float64 `json:"quantity"`
|
Quantity float64 `json:"quantity"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
WastePercentage float64 `json:"waste_percentage"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
|
||||||
// Relations
|
// Relations
|
||||||
Product *Product `json:"product,omitempty"`
|
Product *Product `json:"product,omitempty"`
|
||||||
|
|||||||
@ -28,20 +28,20 @@ type OrderIngredientTransactionProcessor interface {
|
|||||||
|
|
||||||
type OrderIngredientTransactionProcessorImpl struct {
|
type OrderIngredientTransactionProcessorImpl struct {
|
||||||
orderIngredientTransactionRepo OrderIngredientTransactionRepository
|
orderIngredientTransactionRepo OrderIngredientTransactionRepository
|
||||||
productIngredientRepo ProductIngredientRepository
|
productRecipeRepo ProductRecipeRepository
|
||||||
ingredientRepo IngredientRepository
|
ingredientRepo IngredientRepository
|
||||||
unitRepo UnitRepository
|
unitRepo UnitRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewOrderIngredientTransactionProcessorImpl(
|
func NewOrderIngredientTransactionProcessorImpl(
|
||||||
orderIngredientTransactionRepo OrderIngredientTransactionRepository,
|
orderIngredientTransactionRepo OrderIngredientTransactionRepository,
|
||||||
productIngredientRepo ProductIngredientRepository,
|
productRecipeRepo ProductRecipeRepository,
|
||||||
ingredientRepo IngredientRepository,
|
ingredientRepo IngredientRepository,
|
||||||
unitRepo UnitRepository,
|
unitRepo UnitRepository,
|
||||||
) OrderIngredientTransactionProcessor {
|
) OrderIngredientTransactionProcessor {
|
||||||
return &OrderIngredientTransactionProcessorImpl{
|
return &OrderIngredientTransactionProcessorImpl{
|
||||||
orderIngredientTransactionRepo: orderIngredientTransactionRepo,
|
orderIngredientTransactionRepo: orderIngredientTransactionRepo,
|
||||||
productIngredientRepo: productIngredientRepo,
|
productRecipeRepo: productRecipeRepo,
|
||||||
ingredientRepo: ingredientRepo,
|
ingredientRepo: ingredientRepo,
|
||||||
unitRepo: unitRepo,
|
unitRepo: unitRepo,
|
||||||
}
|
}
|
||||||
@ -334,36 +334,36 @@ func (p *OrderIngredientTransactionProcessorImpl) BulkCreateOrderIngredientTrans
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *OrderIngredientTransactionProcessorImpl) CalculateWasteQuantities(ctx context.Context, productID uuid.UUID, quantity float64, organizationID uuid.UUID) ([]*models.CreateOrderIngredientTransactionRequest, error) {
|
func (p *OrderIngredientTransactionProcessorImpl) CalculateWasteQuantities(ctx context.Context, productID uuid.UUID, quantity float64, organizationID uuid.UUID) ([]*models.CreateOrderIngredientTransactionRequest, error) {
|
||||||
// Get product ingredients
|
// Get product recipes
|
||||||
productIngredients, err := p.productIngredientRepo.GetByProductID(ctx, productID, organizationID)
|
productRecipes, err := p.productRecipeRepo.GetByProductID(ctx, productID, organizationID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get product ingredients: %w", err)
|
return nil, fmt.Errorf("failed to get product recipes: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(productIngredients) == 0 {
|
if len(productRecipes) == 0 {
|
||||||
return []*models.CreateOrderIngredientTransactionRequest{}, nil
|
return []*models.CreateOrderIngredientTransactionRequest{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get ingredient details for unit information
|
// Get ingredient details for unit information
|
||||||
ingredientMap := make(map[uuid.UUID]*entities.Ingredient)
|
ingredientMap := make(map[uuid.UUID]*entities.Ingredient)
|
||||||
for _, pi := range productIngredients {
|
for _, pr := range productRecipes {
|
||||||
ingredient, err := p.ingredientRepo.GetByID(ctx, pi.IngredientID, organizationID)
|
ingredient, err := p.ingredientRepo.GetByID(ctx, pr.IngredientID, organizationID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get ingredient %s: %w", pi.IngredientID, err)
|
return nil, fmt.Errorf("failed to get ingredient %s: %w", pr.IngredientID, err)
|
||||||
}
|
}
|
||||||
ingredientMap[pi.IngredientID] = ingredient
|
ingredientMap[pr.IngredientID] = ingredient
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate quantities for each ingredient
|
// Calculate quantities for each ingredient
|
||||||
transactions := make([]*models.CreateOrderIngredientTransactionRequest, 0, len(productIngredients))
|
transactions := make([]*models.CreateOrderIngredientTransactionRequest, 0, len(productRecipes))
|
||||||
for _, pi := range productIngredients {
|
for _, pr := range productRecipes {
|
||||||
ingredient := ingredientMap[pi.IngredientID]
|
ingredient := ingredientMap[pr.IngredientID]
|
||||||
|
|
||||||
// Calculate net quantity (actual quantity needed for the product)
|
// Calculate net quantity (actual quantity needed for the product)
|
||||||
netQty := pi.Quantity * quantity
|
netQty := pr.Quantity * quantity
|
||||||
|
|
||||||
// Calculate gross quantity (including waste)
|
// Calculate gross quantity (including waste)
|
||||||
wasteMultiplier := 1 + (pi.WastePercentage / 100)
|
wasteMultiplier := 1 + (pr.WastePercentage / 100)
|
||||||
grossQty := netQty * wasteMultiplier
|
grossQty := netQty * wasteMultiplier
|
||||||
|
|
||||||
// Calculate waste quantity
|
// Calculate waste quantity
|
||||||
@ -379,7 +379,7 @@ func (p *OrderIngredientTransactionProcessorImpl) CalculateWasteQuantities(ctx c
|
|||||||
}
|
}
|
||||||
|
|
||||||
transaction := &models.CreateOrderIngredientTransactionRequest{
|
transaction := &models.CreateOrderIngredientTransactionRequest{
|
||||||
IngredientID: pi.IngredientID,
|
IngredientID: pr.IngredientID,
|
||||||
GrossQty: util.RoundToDecimalPlaces(grossQty, 3),
|
GrossQty: util.RoundToDecimalPlaces(grossQty, 3),
|
||||||
NetQty: util.RoundToDecimalPlaces(netQty, 3),
|
NetQty: util.RoundToDecimalPlaces(netQty, 3),
|
||||||
WasteQty: util.RoundToDecimalPlaces(wasteQty, 3),
|
WasteQty: util.RoundToDecimalPlaces(wasteQty, 3),
|
||||||
|
|||||||
@ -1,27 +1,26 @@
|
|||||||
package processor
|
package processor
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"apskel-pos-be/internal/constants"
|
"apskel-pos-be/internal/contract"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"apskel-pos-be/internal/entities"
|
"apskel-pos-be/internal/entities"
|
||||||
"apskel-pos-be/internal/models"
|
|
||||||
"apskel-pos-be/internal/repository"
|
"apskel-pos-be/internal/repository"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ProductRecipeProcessor interface {
|
type ProductRecipeProcessor interface {
|
||||||
Create(ctx context.Context, req *models.CreateProductRecipeRequest, organizationID uuid.UUID) (*models.ProductRecipeResponse, error)
|
Create(ctx context.Context, req *contract.CreateProductRecipeRequest, organizationID uuid.UUID) (*contract.ProductRecipeResponse, error)
|
||||||
GetByID(ctx context.Context, id uuid.UUID, organizationID uuid.UUID) (*models.ProductRecipeResponse, error)
|
GetByID(ctx context.Context, id uuid.UUID, organizationID uuid.UUID) (*contract.ProductRecipeResponse, error)
|
||||||
GetByProductID(ctx context.Context, productID uuid.UUID, organizationID uuid.UUID) ([]*models.ProductRecipeResponse, error)
|
GetByProductID(ctx context.Context, productID uuid.UUID, organizationID uuid.UUID) ([]*contract.ProductRecipeResponse, error)
|
||||||
GetByProductAndVariantID(ctx context.Context, productID uuid.UUID, variantID *uuid.UUID, organizationID uuid.UUID) ([]*models.ProductRecipeResponse, error)
|
GetByProductAndVariantID(ctx context.Context, productID uuid.UUID, variantID *uuid.UUID, organizationID uuid.UUID) ([]*contract.ProductRecipeResponse, error)
|
||||||
GetByIngredientID(ctx context.Context, ingredientID uuid.UUID, organizationID uuid.UUID) ([]*models.ProductRecipeResponse, error)
|
GetByIngredientID(ctx context.Context, ingredientID uuid.UUID, organizationID uuid.UUID) ([]*contract.ProductRecipeResponse, error)
|
||||||
Update(ctx context.Context, id uuid.UUID, req *models.UpdateProductRecipeRequest, organizationID uuid.UUID) (*models.ProductRecipeResponse, error)
|
Update(ctx context.Context, id uuid.UUID, req *contract.UpdateProductRecipeRequest, organizationID uuid.UUID) (*contract.ProductRecipeResponse, error)
|
||||||
Delete(ctx context.Context, id uuid.UUID, organizationID uuid.UUID) error
|
Delete(ctx context.Context, id uuid.UUID, organizationID uuid.UUID) error
|
||||||
BulkCreate(ctx context.Context, recipes []models.CreateProductRecipeRequest, organizationID uuid.UUID) ([]*models.ProductRecipeResponse, error)
|
BulkCreate(ctx context.Context, recipes []contract.CreateProductRecipeRequest, organizationID uuid.UUID) ([]*contract.ProductRecipeResponse, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProductRecipeProcessorImpl struct {
|
type ProductRecipeProcessorImpl struct {
|
||||||
@ -38,7 +37,7 @@ func NewProductRecipeProcessor(productRecipeRepo *repository.ProductRecipeReposi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ProductRecipeProcessorImpl) Create(ctx context.Context, req *models.CreateProductRecipeRequest, organizationID uuid.UUID) (*models.ProductRecipeResponse, error) {
|
func (p *ProductRecipeProcessorImpl) Create(ctx context.Context, req *contract.CreateProductRecipeRequest, organizationID uuid.UUID) (*contract.ProductRecipeResponse, error) {
|
||||||
_, err := p.productRepo.GetByID(ctx, req.ProductID)
|
_, err := p.productRepo.GetByID(ctx, req.ProductID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("invalid product: %w", err)
|
return nil, fmt.Errorf("invalid product: %w", err)
|
||||||
@ -50,15 +49,16 @@ func (p *ProductRecipeProcessorImpl) Create(ctx context.Context, req *models.Cre
|
|||||||
}
|
}
|
||||||
|
|
||||||
entity := &entities.ProductRecipe{
|
entity := &entities.ProductRecipe{
|
||||||
ID: uuid.New(),
|
ID: uuid.New(),
|
||||||
OrganizationID: organizationID,
|
OrganizationID: organizationID,
|
||||||
OutletID: req.OutletID,
|
OutletID: req.OutletID,
|
||||||
ProductID: req.ProductID,
|
ProductID: req.ProductID,
|
||||||
VariantID: req.VariantID,
|
VariantID: req.VariantID,
|
||||||
IngredientID: req.IngredientID,
|
IngredientID: req.IngredientID,
|
||||||
Quantity: req.Quantity,
|
Quantity: req.Quantity,
|
||||||
CreatedAt: time.Now().UTC(),
|
WastePercentage: req.WastePercentage,
|
||||||
UpdatedAt: time.Now().UTC(),
|
CreatedAt: time.Now().UTC(),
|
||||||
|
UpdatedAt: time.Now().UTC(),
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := p.productRecipeRepo.Create(ctx, entity); err != nil {
|
if err := p.productRecipeRepo.Create(ctx, entity); err != nil {
|
||||||
@ -73,7 +73,7 @@ func (p *ProductRecipeProcessorImpl) Create(ctx context.Context, req *models.Cre
|
|||||||
return p.entityToResponse(createdEntity), nil
|
return p.entityToResponse(createdEntity), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ProductRecipeProcessorImpl) GetByID(ctx context.Context, id uuid.UUID, organizationID uuid.UUID) (*models.ProductRecipeResponse, error) {
|
func (p *ProductRecipeProcessorImpl) GetByID(ctx context.Context, id uuid.UUID, organizationID uuid.UUID) (*contract.ProductRecipeResponse, error) {
|
||||||
entity, err := p.productRecipeRepo.GetByID(ctx, id, organizationID)
|
entity, err := p.productRecipeRepo.GetByID(ctx, id, organizationID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get product recipe: %w", err)
|
return nil, fmt.Errorf("failed to get product recipe: %w", err)
|
||||||
@ -82,13 +82,13 @@ func (p *ProductRecipeProcessorImpl) GetByID(ctx context.Context, id uuid.UUID,
|
|||||||
return p.entityToResponse(entity), nil
|
return p.entityToResponse(entity), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ProductRecipeProcessorImpl) GetByProductID(ctx context.Context, productID uuid.UUID, organizationID uuid.UUID) ([]*models.ProductRecipeResponse, error) {
|
func (p *ProductRecipeProcessorImpl) GetByProductID(ctx context.Context, productID uuid.UUID, organizationID uuid.UUID) ([]*contract.ProductRecipeResponse, error) {
|
||||||
entities, err := p.productRecipeRepo.GetByProductID(ctx, productID, organizationID)
|
entities, err := p.productRecipeRepo.GetByProductID(ctx, productID, organizationID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get product recipes by product ID: %w", err)
|
return nil, fmt.Errorf("failed to get product recipes by product ID: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
responses := make([]*models.ProductRecipeResponse, len(entities))
|
responses := make([]*contract.ProductRecipeResponse, len(entities))
|
||||||
for i, entity := range entities {
|
for i, entity := range entities {
|
||||||
responses[i] = p.entityToResponse(entity)
|
responses[i] = p.entityToResponse(entity)
|
||||||
}
|
}
|
||||||
@ -96,13 +96,13 @@ func (p *ProductRecipeProcessorImpl) GetByProductID(ctx context.Context, product
|
|||||||
return responses, nil
|
return responses, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ProductRecipeProcessorImpl) GetByProductAndVariantID(ctx context.Context, productID uuid.UUID, variantID *uuid.UUID, organizationID uuid.UUID) ([]*models.ProductRecipeResponse, error) {
|
func (p *ProductRecipeProcessorImpl) GetByProductAndVariantID(ctx context.Context, productID uuid.UUID, variantID *uuid.UUID, organizationID uuid.UUID) ([]*contract.ProductRecipeResponse, error) {
|
||||||
entities, err := p.productRecipeRepo.GetByProductAndVariantID(ctx, productID, variantID, organizationID)
|
entities, err := p.productRecipeRepo.GetByProductAndVariantID(ctx, productID, variantID, organizationID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get product recipes by product and variant ID: %w", err)
|
return nil, fmt.Errorf("failed to get product recipes by product and variant ID: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
responses := make([]*models.ProductRecipeResponse, len(entities))
|
responses := make([]*contract.ProductRecipeResponse, len(entities))
|
||||||
for i, entity := range entities {
|
for i, entity := range entities {
|
||||||
responses[i] = p.entityToResponse(entity)
|
responses[i] = p.entityToResponse(entity)
|
||||||
}
|
}
|
||||||
@ -110,13 +110,13 @@ func (p *ProductRecipeProcessorImpl) GetByProductAndVariantID(ctx context.Contex
|
|||||||
return responses, nil
|
return responses, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ProductRecipeProcessorImpl) GetByIngredientID(ctx context.Context, ingredientID uuid.UUID, organizationID uuid.UUID) ([]*models.ProductRecipeResponse, error) {
|
func (p *ProductRecipeProcessorImpl) GetByIngredientID(ctx context.Context, ingredientID uuid.UUID, organizationID uuid.UUID) ([]*contract.ProductRecipeResponse, error) {
|
||||||
entities, err := p.productRecipeRepo.GetByIngredientID(ctx, ingredientID, organizationID)
|
entities, err := p.productRecipeRepo.GetByIngredientID(ctx, ingredientID, organizationID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get product recipes by ingredient ID: %w", err)
|
return nil, fmt.Errorf("failed to get product recipes by ingredient ID: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
responses := make([]*models.ProductRecipeResponse, len(entities))
|
responses := make([]*contract.ProductRecipeResponse, len(entities))
|
||||||
for i, entity := range entities {
|
for i, entity := range entities {
|
||||||
responses[i] = p.entityToResponse(entity)
|
responses[i] = p.entityToResponse(entity)
|
||||||
}
|
}
|
||||||
@ -124,7 +124,7 @@ func (p *ProductRecipeProcessorImpl) GetByIngredientID(ctx context.Context, ingr
|
|||||||
return responses, nil
|
return responses, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ProductRecipeProcessorImpl) Update(ctx context.Context, id uuid.UUID, req *models.UpdateProductRecipeRequest, organizationID uuid.UUID) (*models.ProductRecipeResponse, error) {
|
func (p *ProductRecipeProcessorImpl) Update(ctx context.Context, id uuid.UUID, req *contract.UpdateProductRecipeRequest, organizationID uuid.UUID) (*contract.ProductRecipeResponse, error) {
|
||||||
// Get existing entity
|
// Get existing entity
|
||||||
existingEntity, err := p.productRecipeRepo.GetByID(ctx, id, organizationID)
|
existingEntity, err := p.productRecipeRepo.GetByID(ctx, id, organizationID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -135,6 +135,7 @@ func (p *ProductRecipeProcessorImpl) Update(ctx context.Context, id uuid.UUID, r
|
|||||||
existingEntity.OutletID = req.OutletID
|
existingEntity.OutletID = req.OutletID
|
||||||
existingEntity.VariantID = req.VariantID
|
existingEntity.VariantID = req.VariantID
|
||||||
existingEntity.Quantity = req.Quantity
|
existingEntity.Quantity = req.Quantity
|
||||||
|
existingEntity.WastePercentage = req.WastePercentage
|
||||||
existingEntity.UpdatedAt = time.Now().UTC()
|
existingEntity.UpdatedAt = time.Now().UTC()
|
||||||
|
|
||||||
if err := p.productRecipeRepo.Update(ctx, existingEntity); err != nil {
|
if err := p.productRecipeRepo.Update(ctx, existingEntity); err != nil {
|
||||||
@ -158,8 +159,8 @@ func (p *ProductRecipeProcessorImpl) Delete(ctx context.Context, id uuid.UUID, o
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ProductRecipeProcessorImpl) BulkCreate(ctx context.Context, recipes []models.CreateProductRecipeRequest, organizationID uuid.UUID) ([]*models.ProductRecipeResponse, error) {
|
func (p *ProductRecipeProcessorImpl) BulkCreate(ctx context.Context, recipes []contract.CreateProductRecipeRequest, organizationID uuid.UUID) ([]*contract.ProductRecipeResponse, error) {
|
||||||
responses := make([]*models.ProductRecipeResponse, 0, len(recipes))
|
responses := make([]*contract.ProductRecipeResponse, 0, len(recipes))
|
||||||
|
|
||||||
for _, recipe := range recipes {
|
for _, recipe := range recipes {
|
||||||
response, err := p.Create(ctx, &recipe, organizationID)
|
response, err := p.Create(ctx, &recipe, organizationID)
|
||||||
@ -172,21 +173,22 @@ func (p *ProductRecipeProcessorImpl) BulkCreate(ctx context.Context, recipes []m
|
|||||||
return responses, nil
|
return responses, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ProductRecipeProcessorImpl) entityToResponse(entity *entities.ProductRecipe) *models.ProductRecipeResponse {
|
func (p *ProductRecipeProcessorImpl) entityToResponse(entity *entities.ProductRecipe) *contract.ProductRecipeResponse {
|
||||||
response := &models.ProductRecipeResponse{
|
response := &contract.ProductRecipeResponse{
|
||||||
ID: entity.ID,
|
ID: entity.ID,
|
||||||
OrganizationID: entity.OrganizationID,
|
OrganizationID: entity.OrganizationID,
|
||||||
OutletID: entity.OutletID,
|
OutletID: entity.OutletID,
|
||||||
ProductID: entity.ProductID,
|
ProductID: entity.ProductID,
|
||||||
VariantID: entity.VariantID,
|
VariantID: entity.VariantID,
|
||||||
IngredientID: entity.IngredientID,
|
IngredientID: entity.IngredientID,
|
||||||
Quantity: entity.Quantity,
|
Quantity: entity.Quantity,
|
||||||
CreatedAt: entity.CreatedAt,
|
WastePercentage: entity.WastePercentage,
|
||||||
UpdatedAt: entity.UpdatedAt,
|
CreatedAt: entity.CreatedAt,
|
||||||
|
UpdatedAt: entity.UpdatedAt,
|
||||||
}
|
}
|
||||||
|
|
||||||
if entity.Product != nil {
|
if entity.Product != nil {
|
||||||
response.Product = &models.Product{
|
response.Product = &contract.ProductResponse{
|
||||||
ID: entity.Product.ID,
|
ID: entity.Product.ID,
|
||||||
OrganizationID: entity.Product.OrganizationID,
|
OrganizationID: entity.Product.OrganizationID,
|
||||||
CategoryID: entity.Product.CategoryID,
|
CategoryID: entity.Product.CategoryID,
|
||||||
@ -195,11 +197,9 @@ func (p *ProductRecipeProcessorImpl) entityToResponse(entity *entities.ProductRe
|
|||||||
Description: entity.Product.Description,
|
Description: entity.Product.Description,
|
||||||
Price: entity.Product.Price,
|
Price: entity.Product.Price,
|
||||||
Cost: entity.Product.Cost,
|
Cost: entity.Product.Cost,
|
||||||
BusinessType: constants.BusinessType(entity.Product.BusinessType),
|
BusinessType: string(entity.Product.BusinessType),
|
||||||
ImageURL: entity.Product.ImageURL,
|
ImageURL: entity.Product.ImageURL,
|
||||||
PrinterType: entity.Product.PrinterType,
|
PrinterType: entity.Product.PrinterType,
|
||||||
UnitID: entity.Product.UnitID,
|
|
||||||
HasIngredients: entity.Product.HasIngredients,
|
|
||||||
Metadata: entity.Product.Metadata,
|
Metadata: entity.Product.Metadata,
|
||||||
IsActive: entity.Product.IsActive,
|
IsActive: entity.Product.IsActive,
|
||||||
CreatedAt: entity.Product.CreatedAt,
|
CreatedAt: entity.Product.CreatedAt,
|
||||||
@ -208,7 +208,7 @@ func (p *ProductRecipeProcessorImpl) entityToResponse(entity *entities.ProductRe
|
|||||||
}
|
}
|
||||||
|
|
||||||
if entity.ProductVariant != nil {
|
if entity.ProductVariant != nil {
|
||||||
response.ProductVariant = &models.ProductVariant{
|
response.ProductVariant = &contract.ProductVariantResponse{
|
||||||
ID: entity.ProductVariant.ID,
|
ID: entity.ProductVariant.ID,
|
||||||
ProductID: entity.ProductVariant.ProductID,
|
ProductID: entity.ProductVariant.ProductID,
|
||||||
Name: entity.ProductVariant.Name,
|
Name: entity.ProductVariant.Name,
|
||||||
@ -221,7 +221,7 @@ func (p *ProductRecipeProcessorImpl) entityToResponse(entity *entities.ProductRe
|
|||||||
}
|
}
|
||||||
|
|
||||||
if entity.Ingredient != nil {
|
if entity.Ingredient != nil {
|
||||||
response.Ingredient = &models.Ingredient{
|
response.Ingredient = &contract.ProductRecipeIngredientResponse{
|
||||||
ID: entity.Ingredient.ID,
|
ID: entity.Ingredient.ID,
|
||||||
OrganizationID: entity.Ingredient.OrganizationID,
|
OrganizationID: entity.Ingredient.OrganizationID,
|
||||||
OutletID: entity.Ingredient.OutletID,
|
OutletID: entity.Ingredient.OutletID,
|
||||||
@ -235,7 +235,22 @@ func (p *ProductRecipeProcessorImpl) entityToResponse(entity *entities.ProductRe
|
|||||||
CreatedAt: entity.Ingredient.CreatedAt,
|
CreatedAt: entity.Ingredient.CreatedAt,
|
||||||
UpdatedAt: entity.Ingredient.UpdatedAt,
|
UpdatedAt: entity.Ingredient.UpdatedAt,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add unit if available
|
||||||
|
if entity.Ingredient.Unit != nil {
|
||||||
|
symbol := ""
|
||||||
|
if entity.Ingredient.Unit.Abbreviation != nil {
|
||||||
|
symbol = *entity.Ingredient.Unit.Abbreviation
|
||||||
|
}
|
||||||
|
response.Ingredient.Unit = &contract.ProductRecipeUnitResponse{
|
||||||
|
ID: entity.Ingredient.Unit.ID,
|
||||||
|
Name: entity.Ingredient.Unit.Name,
|
||||||
|
Symbol: symbol,
|
||||||
|
CreatedAt: entity.Ingredient.Unit.CreatedAt,
|
||||||
|
UpdatedAt: entity.Ingredient.Unit.UpdatedAt,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
@ -56,12 +56,12 @@ type OrderIngredientTransactionRepository interface {
|
|||||||
BulkCreate(ctx context.Context, transactions []*entities.OrderIngredientTransaction) error
|
BulkCreate(ctx context.Context, transactions []*entities.OrderIngredientTransaction) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProductIngredientRepository interface {
|
type ProductRecipeRepository interface {
|
||||||
Create(ctx context.Context, productIngredient *entities.ProductIngredient) error
|
Create(ctx context.Context, productRecipe *entities.ProductRecipe) error
|
||||||
GetByID(ctx context.Context, id, organizationID uuid.UUID) (*entities.ProductIngredient, error)
|
GetByID(ctx context.Context, id, organizationID uuid.UUID) (*entities.ProductRecipe, error)
|
||||||
GetByProductID(ctx context.Context, productID, organizationID uuid.UUID) ([]*entities.ProductIngredient, error)
|
GetByProductID(ctx context.Context, productID, organizationID uuid.UUID) ([]*entities.ProductRecipe, error)
|
||||||
GetByIngredientID(ctx context.Context, ingredientID, organizationID uuid.UUID) ([]*entities.ProductIngredient, error)
|
GetByIngredientID(ctx context.Context, ingredientID, organizationID uuid.UUID) ([]*entities.ProductRecipe, error)
|
||||||
Update(ctx context.Context, productIngredient *entities.ProductIngredient) error
|
Update(ctx context.Context, productRecipe *entities.ProductRecipe) error
|
||||||
Delete(ctx context.Context, id, organizationID uuid.UUID) error
|
Delete(ctx context.Context, id, organizationID uuid.UUID) error
|
||||||
DeleteByProductID(ctx context.Context, productID, organizationID uuid.UUID) error
|
DeleteByProductID(ctx context.Context, productID, organizationID uuid.UUID) error
|
||||||
}
|
}
|
||||||
|
|||||||
@ -35,17 +35,17 @@ type OrderServiceImpl struct {
|
|||||||
tableRepo repository.TableRepositoryInterface
|
tableRepo repository.TableRepositoryInterface
|
||||||
orderIngredientTransactionService *OrderIngredientTransactionService
|
orderIngredientTransactionService *OrderIngredientTransactionService
|
||||||
orderIngredientTransactionProcessor processor.OrderIngredientTransactionProcessor
|
orderIngredientTransactionProcessor processor.OrderIngredientTransactionProcessor
|
||||||
productIngredientRepo repository.ProductIngredientRepository
|
productRecipeRepo repository.ProductRecipeRepository
|
||||||
txManager *repository.TxManager
|
txManager *repository.TxManager
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewOrderServiceImpl(orderProcessor processor.OrderProcessor, tableRepo repository.TableRepositoryInterface, orderIngredientTransactionService *OrderIngredientTransactionService, orderIngredientTransactionProcessor processor.OrderIngredientTransactionProcessor, productIngredientRepo repository.ProductIngredientRepository, txManager *repository.TxManager) *OrderServiceImpl {
|
func NewOrderServiceImpl(orderProcessor processor.OrderProcessor, tableRepo repository.TableRepositoryInterface, orderIngredientTransactionService *OrderIngredientTransactionService, orderIngredientTransactionProcessor processor.OrderIngredientTransactionProcessor, productRecipeRepo repository.ProductRecipeRepository, txManager *repository.TxManager) *OrderServiceImpl {
|
||||||
return &OrderServiceImpl{
|
return &OrderServiceImpl{
|
||||||
orderProcessor: orderProcessor,
|
orderProcessor: orderProcessor,
|
||||||
tableRepo: tableRepo,
|
tableRepo: tableRepo,
|
||||||
orderIngredientTransactionService: orderIngredientTransactionService,
|
orderIngredientTransactionService: orderIngredientTransactionService,
|
||||||
orderIngredientTransactionProcessor: orderIngredientTransactionProcessor,
|
orderIngredientTransactionProcessor: orderIngredientTransactionProcessor,
|
||||||
productIngredientRepo: productIngredientRepo,
|
productRecipeRepo: productRecipeRepo,
|
||||||
txManager: txManager,
|
txManager: txManager,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -112,18 +112,18 @@ func (s *OrderServiceImpl) createIngredientTransactions(ctx context.Context, ord
|
|||||||
var allTransactions []*contract.CreateOrderIngredientTransactionRequest
|
var allTransactions []*contract.CreateOrderIngredientTransactionRequest
|
||||||
|
|
||||||
for _, orderItem := range orderItems {
|
for _, orderItem := range orderItems {
|
||||||
// Get product ingredients for this product
|
// Get product recipes for this product
|
||||||
productIngredients, err := s.productIngredientRepo.GetByProductID(ctx, orderItem.ProductID, organizationID)
|
productRecipes, err := s.productRecipeRepo.GetByProductID(ctx, orderItem.ProductID, organizationID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get product ingredients for product %s: %w", orderItem.ProductID, err)
|
return nil, fmt.Errorf("failed to get product recipes for product %s: %w", orderItem.ProductID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(productIngredients) == 0 {
|
if len(productRecipes) == 0 {
|
||||||
continue // Skip if no ingredients
|
continue // Skip if no recipes
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate waste quantities
|
// Calculate waste quantities
|
||||||
transactions, err := s.calculateWasteQuantities(productIngredients, float64(orderItem.Quantity))
|
transactions, err := s.calculateWasteQuantities(productRecipes, float64(orderItem.Quantity))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to calculate waste quantities for product %s: %w", err)
|
return nil, fmt.Errorf("failed to calculate waste quantities for product %s: %w", err)
|
||||||
}
|
}
|
||||||
@ -646,17 +646,17 @@ func (s *OrderServiceImpl) handleTableReleaseOnVoid(ctx context.Context, orderID
|
|||||||
|
|
||||||
func (s *OrderServiceImpl) createOrderIngredientTransactions(ctx context.Context, order *models.Order, orderItems []*models.OrderItem) error {
|
func (s *OrderServiceImpl) createOrderIngredientTransactions(ctx context.Context, order *models.Order, orderItems []*models.OrderItem) error {
|
||||||
for _, orderItem := range orderItems {
|
for _, orderItem := range orderItems {
|
||||||
productIngredients, err := s.productIngredientRepo.GetByProductID(ctx, orderItem.ProductID, order.OrganizationID)
|
productRecipes, err := s.productRecipeRepo.GetByProductID(ctx, orderItem.ProductID, order.OrganizationID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get product ingredients for product %s: %w", orderItem.ProductID, err)
|
return fmt.Errorf("failed to get product recipes for product %s: %w", orderItem.ProductID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(productIngredients) == 0 {
|
if len(productRecipes) == 0 {
|
||||||
continue // Skip if no ingredients
|
continue // Skip if no recipes
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate waste quantities using the utility function
|
// Calculate waste quantities using the utility function
|
||||||
transactions, err := s.calculateWasteQuantities(productIngredients, float64(orderItem.Quantity))
|
transactions, err := s.calculateWasteQuantities(productRecipes, float64(orderItem.Quantity))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to calculate waste quantities for product %s: %w", orderItem.ProductID, err)
|
return fmt.Errorf("failed to calculate waste quantities for product %s: %w", orderItem.ProductID, err)
|
||||||
}
|
}
|
||||||
@ -681,20 +681,20 @@ func (s *OrderServiceImpl) createOrderIngredientTransactions(ctx context.Context
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// calculateWasteQuantities calculates gross, net, and waste quantities for product ingredients
|
// calculateWasteQuantities calculates gross, net, and waste quantities for product recipes
|
||||||
func (s *OrderServiceImpl) calculateWasteQuantities(productIngredients []*entities.ProductIngredient, quantity float64) ([]*contract.CreateOrderIngredientTransactionRequest, error) {
|
func (s *OrderServiceImpl) calculateWasteQuantities(productRecipes []*entities.ProductRecipe, quantity float64) ([]*contract.CreateOrderIngredientTransactionRequest, error) {
|
||||||
if len(productIngredients) == 0 {
|
if len(productRecipes) == 0 {
|
||||||
return []*contract.CreateOrderIngredientTransactionRequest{}, nil
|
return []*contract.CreateOrderIngredientTransactionRequest{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
transactions := make([]*contract.CreateOrderIngredientTransactionRequest, 0, len(productIngredients))
|
transactions := make([]*contract.CreateOrderIngredientTransactionRequest, 0, len(productRecipes))
|
||||||
|
|
||||||
for _, pi := range productIngredients {
|
for _, pr := range productRecipes {
|
||||||
// Calculate net quantity (actual quantity needed for the product)
|
// Calculate net quantity (actual quantity needed for the product)
|
||||||
netQty := pi.Quantity * quantity
|
netQty := pr.Quantity * quantity
|
||||||
|
|
||||||
// Calculate gross quantity (including waste)
|
// Calculate gross quantity (including waste)
|
||||||
wasteMultiplier := 1 + (pi.WastePercentage / 100)
|
wasteMultiplier := 1 + (pr.WastePercentage / 100)
|
||||||
grossQty := netQty * wasteMultiplier
|
grossQty := netQty * wasteMultiplier
|
||||||
|
|
||||||
// Calculate waste quantity
|
// Calculate waste quantity
|
||||||
@ -702,12 +702,12 @@ func (s *OrderServiceImpl) calculateWasteQuantities(productIngredients []*entiti
|
|||||||
|
|
||||||
// Get unit name from ingredient
|
// Get unit name from ingredient
|
||||||
unitName := "unit" // default
|
unitName := "unit" // default
|
||||||
if pi.Ingredient != nil && pi.Ingredient.Unit != nil {
|
if pr.Ingredient != nil && pr.Ingredient.Unit != nil {
|
||||||
unitName = pi.Ingredient.Unit.Name
|
unitName = pr.Ingredient.Unit.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
transaction := &contract.CreateOrderIngredientTransactionRequest{
|
transaction := &contract.CreateOrderIngredientTransactionRequest{
|
||||||
IngredientID: pi.IngredientID,
|
IngredientID: pr.IngredientID,
|
||||||
GrossQty: util.RoundToDecimalPlaces(grossQty, 3),
|
GrossQty: util.RoundToDecimalPlaces(grossQty, 3),
|
||||||
NetQty: util.RoundToDecimalPlaces(netQty, 3),
|
NetQty: util.RoundToDecimalPlaces(netQty, 3),
|
||||||
WasteQty: util.RoundToDecimalPlaces(wasteQty, 3),
|
WasteQty: util.RoundToDecimalPlaces(wasteQty, 3),
|
||||||
|
|||||||
@ -1,22 +1,22 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"apskel-pos-be/internal/models"
|
"apskel-pos-be/internal/contract"
|
||||||
"apskel-pos-be/internal/processor"
|
"apskel-pos-be/internal/processor"
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ProductRecipeService interface {
|
type ProductRecipeService interface {
|
||||||
Create(ctx context.Context, organizationID uuid.UUID, req *models.CreateProductRecipeRequest) (*models.ProductRecipeResponse, error)
|
Create(ctx context.Context, organizationID uuid.UUID, req *contract.CreateProductRecipeRequest) (*contract.ProductRecipeResponse, error)
|
||||||
GetByID(ctx context.Context, id uuid.UUID, organizationID uuid.UUID) (*models.ProductRecipeResponse, error)
|
GetByID(ctx context.Context, id uuid.UUID, organizationID uuid.UUID) (*contract.ProductRecipeResponse, error)
|
||||||
GetByProductID(ctx context.Context, productID uuid.UUID, organizationID uuid.UUID) ([]*models.ProductRecipeResponse, error)
|
GetByProductID(ctx context.Context, req *contract.GetProductRecipeByProductIDRequest, organizationID uuid.UUID) ([]*contract.ProductRecipeResponse, error)
|
||||||
GetByProductAndVariantID(ctx context.Context, productID uuid.UUID, variantID *uuid.UUID, organizationID uuid.UUID) ([]*models.ProductRecipeResponse, error)
|
GetByIngredientID(ctx context.Context, ingredientID uuid.UUID, organizationID uuid.UUID) ([]*contract.ProductRecipeResponse, error)
|
||||||
GetByIngredientID(ctx context.Context, ingredientID uuid.UUID, organizationID uuid.UUID) ([]*models.ProductRecipeResponse, error)
|
Update(ctx context.Context, id uuid.UUID, organizationID uuid.UUID, req *contract.UpdateProductRecipeRequest) (*contract.ProductRecipeResponse, error)
|
||||||
Update(ctx context.Context, id uuid.UUID, organizationID uuid.UUID, req *models.UpdateProductRecipeRequest) (*models.ProductRecipeResponse, error)
|
|
||||||
Delete(ctx context.Context, id uuid.UUID, organizationID uuid.UUID) error
|
Delete(ctx context.Context, id uuid.UUID, organizationID uuid.UUID) error
|
||||||
BulkCreate(ctx context.Context, organizationID uuid.UUID, recipes []models.CreateProductRecipeRequest) ([]*models.ProductRecipeResponse, error)
|
BulkCreate(ctx context.Context, organizationID uuid.UUID, req *contract.BulkCreateProductRecipeRequest) ([]*contract.ProductRecipeResponse, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProductRecipeServiceImpl struct {
|
type ProductRecipeServiceImpl struct {
|
||||||
@ -29,34 +29,86 @@ func NewProductRecipeService(processor processor.ProductRecipeProcessor) *Produc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ProductRecipeServiceImpl) Create(ctx context.Context, organizationID uuid.UUID, req *models.CreateProductRecipeRequest) (*models.ProductRecipeResponse, error) {
|
func (s *ProductRecipeServiceImpl) Create(ctx context.Context, organizationID uuid.UUID, req *contract.CreateProductRecipeRequest) (*contract.ProductRecipeResponse, error) {
|
||||||
|
// Validate request
|
||||||
|
if req == nil {
|
||||||
|
return nil, fmt.Errorf("request cannot be nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call processor to handle business logic
|
||||||
return s.processor.Create(ctx, req, organizationID)
|
return s.processor.Create(ctx, req, organizationID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ProductRecipeServiceImpl) GetByID(ctx context.Context, id uuid.UUID, organizationID uuid.UUID) (*models.ProductRecipeResponse, error) {
|
func (s *ProductRecipeServiceImpl) GetByID(ctx context.Context, id uuid.UUID, organizationID uuid.UUID) (*contract.ProductRecipeResponse, error) {
|
||||||
|
// Validate ID
|
||||||
|
if id == uuid.Nil {
|
||||||
|
return nil, fmt.Errorf("invalid recipe ID")
|
||||||
|
}
|
||||||
|
|
||||||
return s.processor.GetByID(ctx, id, organizationID)
|
return s.processor.GetByID(ctx, id, organizationID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ProductRecipeServiceImpl) GetByProductID(ctx context.Context, productID uuid.UUID, organizationID uuid.UUID) ([]*models.ProductRecipeResponse, error) {
|
func (s *ProductRecipeServiceImpl) GetByProductID(ctx context.Context, req *contract.GetProductRecipeByProductIDRequest, organizationID uuid.UUID) ([]*contract.ProductRecipeResponse, error) {
|
||||||
return s.processor.GetByProductID(ctx, productID, organizationID)
|
// Validate request
|
||||||
|
if req == nil {
|
||||||
|
return nil, fmt.Errorf("request cannot be nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate product ID
|
||||||
|
if req.ProductID == uuid.Nil {
|
||||||
|
return nil, fmt.Errorf("invalid product ID")
|
||||||
|
}
|
||||||
|
|
||||||
|
// If variant ID is provided, get by product and variant
|
||||||
|
if req.VariantID != nil {
|
||||||
|
return s.processor.GetByProductAndVariantID(ctx, req.ProductID, req.VariantID, organizationID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise get by product ID only
|
||||||
|
return s.processor.GetByProductID(ctx, req.ProductID, organizationID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ProductRecipeServiceImpl) GetByProductAndVariantID(ctx context.Context, productID uuid.UUID, variantID *uuid.UUID, organizationID uuid.UUID) ([]*models.ProductRecipeResponse, error) {
|
func (s *ProductRecipeServiceImpl) GetByIngredientID(ctx context.Context, ingredientID uuid.UUID, organizationID uuid.UUID) ([]*contract.ProductRecipeResponse, error) {
|
||||||
return s.processor.GetByProductAndVariantID(ctx, productID, variantID, organizationID)
|
// Validate ingredient ID
|
||||||
}
|
if ingredientID == uuid.Nil {
|
||||||
|
return nil, fmt.Errorf("invalid ingredient ID")
|
||||||
|
}
|
||||||
|
|
||||||
func (s *ProductRecipeServiceImpl) GetByIngredientID(ctx context.Context, ingredientID uuid.UUID, organizationID uuid.UUID) ([]*models.ProductRecipeResponse, error) {
|
|
||||||
return s.processor.GetByIngredientID(ctx, ingredientID, organizationID)
|
return s.processor.GetByIngredientID(ctx, ingredientID, organizationID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ProductRecipeServiceImpl) Update(ctx context.Context, id uuid.UUID, organizationID uuid.UUID, req *models.UpdateProductRecipeRequest) (*models.ProductRecipeResponse, error) {
|
func (s *ProductRecipeServiceImpl) Update(ctx context.Context, id uuid.UUID, organizationID uuid.UUID, req *contract.UpdateProductRecipeRequest) (*contract.ProductRecipeResponse, error) {
|
||||||
|
// Validate ID
|
||||||
|
if id == uuid.Nil {
|
||||||
|
return nil, fmt.Errorf("invalid recipe ID")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate request
|
||||||
|
if req == nil {
|
||||||
|
return nil, fmt.Errorf("request cannot be nil")
|
||||||
|
}
|
||||||
|
|
||||||
return s.processor.Update(ctx, id, req, organizationID)
|
return s.processor.Update(ctx, id, req, organizationID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ProductRecipeServiceImpl) Delete(ctx context.Context, id uuid.UUID, organizationID uuid.UUID) error {
|
func (s *ProductRecipeServiceImpl) Delete(ctx context.Context, id uuid.UUID, organizationID uuid.UUID) error {
|
||||||
|
// Validate ID
|
||||||
|
if id == uuid.Nil {
|
||||||
|
return fmt.Errorf("invalid recipe ID")
|
||||||
|
}
|
||||||
|
|
||||||
return s.processor.Delete(ctx, id, organizationID)
|
return s.processor.Delete(ctx, id, organizationID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ProductRecipeServiceImpl) BulkCreate(ctx context.Context, organizationID uuid.UUID, recipes []models.CreateProductRecipeRequest) ([]*models.ProductRecipeResponse, error) {
|
func (s *ProductRecipeServiceImpl) BulkCreate(ctx context.Context, organizationID uuid.UUID, req *contract.BulkCreateProductRecipeRequest) ([]*contract.ProductRecipeResponse, error) {
|
||||||
return s.processor.BulkCreate(ctx, recipes, organizationID)
|
// Validate request
|
||||||
}
|
if req == nil {
|
||||||
|
return nil, fmt.Errorf("request cannot be nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(req.Recipes) == 0 {
|
||||||
|
return nil, fmt.Errorf("at least one recipe is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.processor.BulkCreate(ctx, req.Recipes, organizationID)
|
||||||
|
}
|
||||||
@ -34,7 +34,11 @@ func NewPurchaseOrderService(purchaseOrderProcessor processor.PurchaseOrderProce
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *PurchaseOrderServiceImpl) CreatePurchaseOrder(ctx context.Context, apctx *appcontext.ContextInfo, req *contract.CreatePurchaseOrderRequest) *contract.Response {
|
func (s *PurchaseOrderServiceImpl) CreatePurchaseOrder(ctx context.Context, apctx *appcontext.ContextInfo, req *contract.CreatePurchaseOrderRequest) *contract.Response {
|
||||||
modelReq := transformer.CreatePurchaseOrderRequestToModel(req)
|
modelReq, err := transformer.CreatePurchaseOrderRequestToModel(req)
|
||||||
|
if err != nil {
|
||||||
|
errorResp := contract.NewResponseError(constants.MalformedFieldErrorCode, constants.PurchaseOrderServiceEntity, "Invalid date format. Use YYYY-MM-DD format")
|
||||||
|
return contract.BuildErrorResponse([]*contract.ResponseError{errorResp})
|
||||||
|
}
|
||||||
|
|
||||||
poResponse, err := s.purchaseOrderProcessor.CreatePurchaseOrder(ctx, apctx.OrganizationID, modelReq)
|
poResponse, err := s.purchaseOrderProcessor.CreatePurchaseOrder(ctx, apctx.OrganizationID, modelReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -47,7 +51,11 @@ func (s *PurchaseOrderServiceImpl) CreatePurchaseOrder(ctx context.Context, apct
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *PurchaseOrderServiceImpl) UpdatePurchaseOrder(ctx context.Context, apctx *appcontext.ContextInfo, id uuid.UUID, req *contract.UpdatePurchaseOrderRequest) *contract.Response {
|
func (s *PurchaseOrderServiceImpl) UpdatePurchaseOrder(ctx context.Context, apctx *appcontext.ContextInfo, id uuid.UUID, req *contract.UpdatePurchaseOrderRequest) *contract.Response {
|
||||||
modelReq := transformer.UpdatePurchaseOrderRequestToModel(req)
|
modelReq, err := transformer.UpdatePurchaseOrderRequestToModel(req)
|
||||||
|
if err != nil {
|
||||||
|
errorResp := contract.NewResponseError(constants.MalformedFieldErrorCode, constants.PurchaseOrderServiceEntity, "Invalid date format. Use YYYY-MM-DD format")
|
||||||
|
return contract.BuildErrorResponse([]*contract.ResponseError{errorResp})
|
||||||
|
}
|
||||||
|
|
||||||
poResponse, err := s.purchaseOrderProcessor.UpdatePurchaseOrder(ctx, id, apctx.OrganizationID, modelReq)
|
poResponse, err := s.purchaseOrderProcessor.UpdatePurchaseOrder(ctx, id, apctx.OrganizationID, modelReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@ -3,10 +3,11 @@ package transformer
|
|||||||
import (
|
import (
|
||||||
"apskel-pos-be/internal/contract"
|
"apskel-pos-be/internal/contract"
|
||||||
"apskel-pos-be/internal/models"
|
"apskel-pos-be/internal/models"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Contract to Model conversions
|
// Contract to Model conversions
|
||||||
func CreatePurchaseOrderRequestToModel(req *contract.CreatePurchaseOrderRequest) *models.CreatePurchaseOrderRequest {
|
func CreatePurchaseOrderRequestToModel(req *contract.CreatePurchaseOrderRequest) (*models.CreatePurchaseOrderRequest, error) {
|
||||||
items := make([]models.CreatePurchaseOrderItemRequest, len(req.Items))
|
items := make([]models.CreatePurchaseOrderItemRequest, len(req.Items))
|
||||||
for i, item := range req.Items {
|
for i, item := range req.Items {
|
||||||
items[i] = models.CreatePurchaseOrderItemRequest{
|
items[i] = models.CreatePurchaseOrderItemRequest{
|
||||||
@ -18,20 +19,32 @@ func CreatePurchaseOrderRequestToModel(req *contract.CreatePurchaseOrderRequest)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parse transaction date
|
||||||
|
transactionDate, err := time.Parse("2006-01-02", req.TransactionDate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse due date
|
||||||
|
dueDate, err := time.Parse("2006-01-02", req.DueDate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return &models.CreatePurchaseOrderRequest{
|
return &models.CreatePurchaseOrderRequest{
|
||||||
VendorID: req.VendorID,
|
VendorID: req.VendorID,
|
||||||
PONumber: req.PONumber,
|
PONumber: req.PONumber,
|
||||||
TransactionDate: req.TransactionDate,
|
TransactionDate: transactionDate,
|
||||||
DueDate: req.DueDate,
|
DueDate: dueDate,
|
||||||
Reference: req.Reference,
|
Reference: req.Reference,
|
||||||
Status: req.Status,
|
Status: req.Status,
|
||||||
Message: req.Message,
|
Message: req.Message,
|
||||||
Items: items,
|
Items: items,
|
||||||
AttachmentFileIDs: req.AttachmentFileIDs,
|
AttachmentFileIDs: req.AttachmentFileIDs,
|
||||||
}
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func UpdatePurchaseOrderRequestToModel(req *contract.UpdatePurchaseOrderRequest) *models.UpdatePurchaseOrderRequest {
|
func UpdatePurchaseOrderRequestToModel(req *contract.UpdatePurchaseOrderRequest) (*models.UpdatePurchaseOrderRequest, error) {
|
||||||
var items []models.UpdatePurchaseOrderItemRequest
|
var items []models.UpdatePurchaseOrderItemRequest
|
||||||
if req.Items != nil {
|
if req.Items != nil {
|
||||||
items = make([]models.UpdatePurchaseOrderItemRequest, len(req.Items))
|
items = make([]models.UpdatePurchaseOrderItemRequest, len(req.Items))
|
||||||
@ -47,17 +60,37 @@ func UpdatePurchaseOrderRequestToModel(req *contract.UpdatePurchaseOrderRequest)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parse transaction date if provided
|
||||||
|
var transactionDate *time.Time
|
||||||
|
if req.TransactionDate != nil && *req.TransactionDate != "" {
|
||||||
|
parsedDate, err := time.Parse("2006-01-02", *req.TransactionDate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
transactionDate = &parsedDate
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse due date if provided
|
||||||
|
var dueDate *time.Time
|
||||||
|
if req.DueDate != nil && *req.DueDate != "" {
|
||||||
|
parsedDate, err := time.Parse("2006-01-02", *req.DueDate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
dueDate = &parsedDate
|
||||||
|
}
|
||||||
|
|
||||||
return &models.UpdatePurchaseOrderRequest{
|
return &models.UpdatePurchaseOrderRequest{
|
||||||
VendorID: req.VendorID,
|
VendorID: req.VendorID,
|
||||||
PONumber: req.PONumber,
|
PONumber: req.PONumber,
|
||||||
TransactionDate: req.TransactionDate,
|
TransactionDate: transactionDate,
|
||||||
DueDate: req.DueDate,
|
DueDate: dueDate,
|
||||||
Reference: req.Reference,
|
Reference: req.Reference,
|
||||||
Status: req.Status,
|
Status: req.Status,
|
||||||
Message: req.Message,
|
Message: req.Message,
|
||||||
Items: items,
|
Items: items,
|
||||||
AttachmentFileIDs: req.AttachmentFileIDs,
|
AttachmentFileIDs: req.AttachmentFileIDs,
|
||||||
}
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ListPurchaseOrdersRequestToModel(req *contract.ListPurchaseOrdersRequest) *models.ListPurchaseOrdersRequest {
|
func ListPurchaseOrdersRequestToModel(req *contract.ListPurchaseOrdersRequest) *models.ListPurchaseOrdersRequest {
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package validator
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"apskel-pos-be/internal/constants"
|
"apskel-pos-be/internal/constants"
|
||||||
"apskel-pos-be/internal/contract"
|
"apskel-pos-be/internal/contract"
|
||||||
@ -37,15 +38,26 @@ func (v *PurchaseOrderValidatorImpl) ValidateCreatePurchaseOrderRequest(req *con
|
|||||||
return errors.New("po_number must be between 1 and 50 characters"), constants.MalformedFieldErrorCode
|
return errors.New("po_number must be between 1 and 50 characters"), constants.MalformedFieldErrorCode
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.TransactionDate.IsZero() {
|
// Validate transaction date
|
||||||
|
if strings.TrimSpace(req.TransactionDate) == "" {
|
||||||
return errors.New("transaction_date is required"), constants.MissingFieldErrorCode
|
return errors.New("transaction_date is required"), constants.MissingFieldErrorCode
|
||||||
}
|
}
|
||||||
|
transactionDate, err := time.Parse("2006-01-02", req.TransactionDate)
|
||||||
if req.DueDate.IsZero() {
|
if err != nil {
|
||||||
return errors.New("due_date is required"), constants.MissingFieldErrorCode
|
return errors.New("transaction_date must be in YYYY-MM-DD format"), constants.MalformedFieldErrorCode
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.DueDate.Before(req.TransactionDate) {
|
// Validate due date
|
||||||
|
if strings.TrimSpace(req.DueDate) == "" {
|
||||||
|
return errors.New("due_date is required"), constants.MissingFieldErrorCode
|
||||||
|
}
|
||||||
|
dueDate, err := time.Parse("2006-01-02", req.DueDate)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("due_date must be in YYYY-MM-DD format"), constants.MalformedFieldErrorCode
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if due date is after transaction date
|
||||||
|
if dueDate.Before(transactionDate) {
|
||||||
return errors.New("due_date must be after transaction_date"), constants.MalformedFieldErrorCode
|
return errors.New("due_date must be after transaction_date"), constants.MalformedFieldErrorCode
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,9 +100,22 @@ func (v *PurchaseOrderValidatorImpl) ValidateUpdatePurchaseOrderRequest(req *con
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate dates if both are provided
|
||||||
if req.TransactionDate != nil && req.DueDate != nil {
|
if req.TransactionDate != nil && req.DueDate != nil {
|
||||||
if req.DueDate.Before(*req.TransactionDate) {
|
if *req.TransactionDate != "" && *req.DueDate != "" {
|
||||||
return errors.New("due_date must be after transaction_date"), constants.MalformedFieldErrorCode
|
transactionDate, err := time.Parse("2006-01-02", *req.TransactionDate)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("transaction_date must be in YYYY-MM-DD format"), constants.MalformedFieldErrorCode
|
||||||
|
}
|
||||||
|
|
||||||
|
dueDate, err := time.Parse("2006-01-02", *req.DueDate)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("due_date must be in YYYY-MM-DD format"), constants.MalformedFieldErrorCode
|
||||||
|
}
|
||||||
|
|
||||||
|
if dueDate.Before(transactionDate) {
|
||||||
|
return errors.New("due_date must be after transaction_date"), constants.MalformedFieldErrorCode
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,2 @@
|
|||||||
|
-- Remove waste_percentage column from product_recipes table
|
||||||
|
ALTER TABLE product_recipes DROP COLUMN waste_percentage;
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
-- Add waste_percentage column to product_recipes table
|
||||||
|
ALTER TABLE product_recipes
|
||||||
|
ADD COLUMN waste_percentage DECIMAL(5,2) DEFAULT 0.00 CHECK (waste_percentage >= 0 AND waste_percentage <= 100);
|
||||||
|
|
||||||
|
COMMENT ON COLUMN product_recipes.waste_percentage IS 'Waste percentage for this ingredient (0-100). Used to calculate gross quantity needed including waste.';
|
||||||
Loading…
x
Reference in New Issue
Block a user