add product ingredients
This commit is contained in:
parent
efe09c21e4
commit
3a04990ec8
@ -92,6 +92,8 @@ func (a *App) Initialize(cfg *config.Config) error {
|
|||||||
validators.chartOfAccountValidator,
|
validators.chartOfAccountValidator,
|
||||||
services.accountService,
|
services.accountService,
|
||||||
validators.accountValidator,
|
validators.accountValidator,
|
||||||
|
*services.orderIngredientTransactionService,
|
||||||
|
validators.orderIngredientTransactionValidator,
|
||||||
)
|
)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -164,6 +166,8 @@ type repositories struct {
|
|||||||
chartOfAccountTypeRepo *repository.ChartOfAccountTypeRepositoryImpl
|
chartOfAccountTypeRepo *repository.ChartOfAccountTypeRepositoryImpl
|
||||||
chartOfAccountRepo *repository.ChartOfAccountRepositoryImpl
|
chartOfAccountRepo *repository.ChartOfAccountRepositoryImpl
|
||||||
accountRepo *repository.AccountRepositoryImpl
|
accountRepo *repository.AccountRepositoryImpl
|
||||||
|
orderIngredientTransactionRepo *repository.OrderIngredientTransactionRepositoryImpl
|
||||||
|
productIngredientRepo *repository.ProductIngredientRepository
|
||||||
txManager *repository.TxManager
|
txManager *repository.TxManager
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -196,6 +200,11 @@ func (a *App) initRepositories() *repositories {
|
|||||||
chartOfAccountTypeRepo: repository.NewChartOfAccountTypeRepositoryImpl(a.db),
|
chartOfAccountTypeRepo: repository.NewChartOfAccountTypeRepositoryImpl(a.db),
|
||||||
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),
|
||||||
|
productIngredientRepo: func() *repository.ProductIngredientRepository {
|
||||||
|
db, _ := a.db.DB()
|
||||||
|
return repository.NewProductIngredientRepository(db)
|
||||||
|
}(),
|
||||||
txManager: repository.NewTxManager(a.db),
|
txManager: repository.NewTxManager(a.db),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -224,6 +233,7 @@ type processors struct {
|
|||||||
chartOfAccountTypeProcessor *processor.ChartOfAccountTypeProcessorImpl
|
chartOfAccountTypeProcessor *processor.ChartOfAccountTypeProcessorImpl
|
||||||
chartOfAccountProcessor *processor.ChartOfAccountProcessorImpl
|
chartOfAccountProcessor *processor.ChartOfAccountProcessorImpl
|
||||||
accountProcessor *processor.AccountProcessorImpl
|
accountProcessor *processor.AccountProcessorImpl
|
||||||
|
orderIngredientTransactionProcessor *processor.OrderIngredientTransactionProcessorImpl
|
||||||
fileClient processor.FileClient
|
fileClient processor.FileClient
|
||||||
inventoryMovementService service.InventoryMovementService
|
inventoryMovementService service.InventoryMovementService
|
||||||
}
|
}
|
||||||
@ -256,6 +266,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),
|
||||||
fileClient: fileClient,
|
fileClient: fileClient,
|
||||||
inventoryMovementService: inventoryMovementService,
|
inventoryMovementService: inventoryMovementService,
|
||||||
}
|
}
|
||||||
@ -287,6 +298,7 @@ type services struct {
|
|||||||
chartOfAccountTypeService service.ChartOfAccountTypeService
|
chartOfAccountTypeService service.ChartOfAccountTypeService
|
||||||
chartOfAccountService service.ChartOfAccountService
|
chartOfAccountService service.ChartOfAccountService
|
||||||
accountService service.AccountService
|
accountService service.AccountService
|
||||||
|
orderIngredientTransactionService *service.OrderIngredientTransactionService
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) initServices(processors *processors, repos *repositories, cfg *config.Config) *services {
|
func (a *App) initServices(processors *processors, repos *repositories, cfg *config.Config) *services {
|
||||||
@ -300,7 +312,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)
|
orderService := service.NewOrderServiceImpl(processors.orderProcessor, repos.tableRepo, nil, processors.orderIngredientTransactionProcessor, *repos.productIngredientRepo, 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)
|
||||||
@ -316,6 +328,10 @@ func (a *App) initServices(processors *processors, repos *repositories, cfg *con
|
|||||||
chartOfAccountTypeService := service.NewChartOfAccountTypeService(processors.chartOfAccountTypeProcessor)
|
chartOfAccountTypeService := service.NewChartOfAccountTypeService(processors.chartOfAccountTypeProcessor)
|
||||||
chartOfAccountService := service.NewChartOfAccountService(processors.chartOfAccountProcessor)
|
chartOfAccountService := service.NewChartOfAccountService(processors.chartOfAccountProcessor)
|
||||||
accountService := service.NewAccountService(processors.accountProcessor)
|
accountService := service.NewAccountService(processors.accountProcessor)
|
||||||
|
orderIngredientTransactionService := service.NewOrderIngredientTransactionService(processors.orderIngredientTransactionProcessor, repos.txManager)
|
||||||
|
|
||||||
|
// Update order service with order ingredient transaction service
|
||||||
|
orderService = service.NewOrderServiceImpl(processors.orderProcessor, repos.tableRepo, orderIngredientTransactionService, processors.orderIngredientTransactionProcessor, *repos.productIngredientRepo, repos.txManager)
|
||||||
|
|
||||||
return &services{
|
return &services{
|
||||||
userService: service.NewUserService(processors.userProcessor),
|
userService: service.NewUserService(processors.userProcessor),
|
||||||
@ -343,6 +359,7 @@ func (a *App) initServices(processors *processors, repos *repositories, cfg *con
|
|||||||
chartOfAccountTypeService: chartOfAccountTypeService,
|
chartOfAccountTypeService: chartOfAccountTypeService,
|
||||||
chartOfAccountService: chartOfAccountService,
|
chartOfAccountService: chartOfAccountService,
|
||||||
accountService: accountService,
|
accountService: accountService,
|
||||||
|
orderIngredientTransactionService: orderIngredientTransactionService,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -375,6 +392,7 @@ type validators struct {
|
|||||||
chartOfAccountTypeValidator *validator.ChartOfAccountTypeValidatorImpl
|
chartOfAccountTypeValidator *validator.ChartOfAccountTypeValidatorImpl
|
||||||
chartOfAccountValidator *validator.ChartOfAccountValidatorImpl
|
chartOfAccountValidator *validator.ChartOfAccountValidatorImpl
|
||||||
accountValidator *validator.AccountValidatorImpl
|
accountValidator *validator.AccountValidatorImpl
|
||||||
|
orderIngredientTransactionValidator *validator.OrderIngredientTransactionValidatorImpl
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) initValidators() *validators {
|
func (a *App) initValidators() *validators {
|
||||||
@ -397,5 +415,6 @@ func (a *App) initValidators() *validators {
|
|||||||
chartOfAccountTypeValidator: validator.NewChartOfAccountTypeValidator().(*validator.ChartOfAccountTypeValidatorImpl),
|
chartOfAccountTypeValidator: validator.NewChartOfAccountTypeValidator().(*validator.ChartOfAccountTypeValidatorImpl),
|
||||||
chartOfAccountValidator: validator.NewChartOfAccountValidator().(*validator.ChartOfAccountValidatorImpl),
|
chartOfAccountValidator: validator.NewChartOfAccountValidator().(*validator.ChartOfAccountValidatorImpl),
|
||||||
accountValidator: validator.NewAccountValidator().(*validator.AccountValidatorImpl),
|
accountValidator: validator.NewAccountValidator().(*validator.AccountValidatorImpl),
|
||||||
|
orderIngredientTransactionValidator: validator.NewOrderIngredientTransactionValidator().(*validator.OrderIngredientTransactionValidatorImpl),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -74,3 +74,11 @@ type ListIngredientUnitConvertersResponse struct {
|
|||||||
TotalPages int `json:"total_pages"`
|
TotalPages int `json:"total_pages"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type IngredientUnitsResponse struct {
|
||||||
|
IngredientID uuid.UUID `json:"ingredient_id"`
|
||||||
|
IngredientName string `json:"ingredient_name"`
|
||||||
|
BaseUnitID uuid.UUID `json:"base_unit_id"`
|
||||||
|
BaseUnitName string `json:"base_unit_name"`
|
||||||
|
Units []*UnitResponse `json:"units"`
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
20
internal/contract/order_ingredient_transaction_contract.go
Normal file
20
internal/contract/order_ingredient_transaction_contract.go
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package contract
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OrderIngredientTransactionContract interface {
|
||||||
|
CreateOrderIngredientTransaction(ctx context.Context, req *CreateOrderIngredientTransactionRequest) (*OrderIngredientTransactionResponse, error)
|
||||||
|
GetOrderIngredientTransactionByID(ctx context.Context, id uuid.UUID) (*OrderIngredientTransactionResponse, error)
|
||||||
|
UpdateOrderIngredientTransaction(ctx context.Context, id uuid.UUID, req *UpdateOrderIngredientTransactionRequest) (*OrderIngredientTransactionResponse, error)
|
||||||
|
DeleteOrderIngredientTransaction(ctx context.Context, id uuid.UUID) error
|
||||||
|
ListOrderIngredientTransactions(ctx context.Context, req *ListOrderIngredientTransactionsRequest) ([]*OrderIngredientTransactionResponse, int64, error)
|
||||||
|
GetOrderIngredientTransactionsByOrder(ctx context.Context, orderID uuid.UUID) ([]*OrderIngredientTransactionResponse, error)
|
||||||
|
GetOrderIngredientTransactionsByOrderItem(ctx context.Context, orderItemID uuid.UUID) ([]*OrderIngredientTransactionResponse, error)
|
||||||
|
GetOrderIngredientTransactionsByIngredient(ctx context.Context, ingredientID uuid.UUID) ([]*OrderIngredientTransactionResponse, error)
|
||||||
|
GetOrderIngredientTransactionSummary(ctx context.Context, req *ListOrderIngredientTransactionsRequest) ([]*OrderIngredientTransactionSummary, error)
|
||||||
|
BulkCreateOrderIngredientTransactions(ctx context.Context, transactions []*CreateOrderIngredientTransactionRequest) ([]*OrderIngredientTransactionResponse, error)
|
||||||
|
}
|
||||||
79
internal/contract/order_ingredient_transaction_request.go
Normal file
79
internal/contract/order_ingredient_transaction_request.go
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
package contract
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CreateOrderIngredientTransactionRequest struct {
|
||||||
|
OrderID uuid.UUID `json:"order_id" validate:"required"`
|
||||||
|
OrderItemID *uuid.UUID `json:"order_item_id,omitempty"`
|
||||||
|
ProductID uuid.UUID `json:"product_id" validate:"required"`
|
||||||
|
ProductVariantID *uuid.UUID `json:"product_variant_id,omitempty"`
|
||||||
|
IngredientID uuid.UUID `json:"ingredient_id" validate:"required"`
|
||||||
|
GrossQty float64 `json:"gross_qty" validate:"required,gt=0"`
|
||||||
|
NetQty float64 `json:"net_qty" validate:"required,gt=0"`
|
||||||
|
WasteQty float64 `json:"waste_qty" validate:"min=0"`
|
||||||
|
Unit string `json:"unit" validate:"required,max=50"`
|
||||||
|
TransactionDate *time.Time `json:"transaction_date,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateOrderIngredientTransactionRequest struct {
|
||||||
|
GrossQty *float64 `json:"gross_qty,omitempty" validate:"omitempty,gt=0"`
|
||||||
|
NetQty *float64 `json:"net_qty,omitempty" validate:"omitempty,gt=0"`
|
||||||
|
WasteQty *float64 `json:"waste_qty,omitempty" validate:"min=0"`
|
||||||
|
Unit *string `json:"unit,omitempty" validate:"omitempty,max=50"`
|
||||||
|
TransactionDate *time.Time `json:"transaction_date,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type OrderIngredientTransactionResponse struct {
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
OrganizationID uuid.UUID `json:"organization_id"`
|
||||||
|
OutletID *uuid.UUID `json:"outlet_id"`
|
||||||
|
OrderID uuid.UUID `json:"order_id"`
|
||||||
|
OrderItemID *uuid.UUID `json:"order_item_id"`
|
||||||
|
ProductID uuid.UUID `json:"product_id"`
|
||||||
|
ProductVariantID *uuid.UUID `json:"product_variant_id"`
|
||||||
|
IngredientID uuid.UUID `json:"ingredient_id"`
|
||||||
|
GrossQty float64 `json:"gross_qty"`
|
||||||
|
NetQty float64 `json:"net_qty"`
|
||||||
|
WasteQty float64 `json:"waste_qty"`
|
||||||
|
Unit string `json:"unit"`
|
||||||
|
TransactionDate time.Time `json:"transaction_date"`
|
||||||
|
CreatedBy uuid.UUID `json:"created_by"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
|
||||||
|
// Relations - these would be populated by the service layer
|
||||||
|
Organization interface{} `json:"organization,omitempty"`
|
||||||
|
Outlet interface{} `json:"outlet,omitempty"`
|
||||||
|
Order interface{} `json:"order,omitempty"`
|
||||||
|
OrderItem interface{} `json:"order_item,omitempty"`
|
||||||
|
Product interface{} `json:"product,omitempty"`
|
||||||
|
ProductVariant interface{} `json:"product_variant,omitempty"`
|
||||||
|
Ingredient interface{} `json:"ingredient,omitempty"`
|
||||||
|
CreatedByUser interface{} `json:"created_by_user,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListOrderIngredientTransactionsRequest struct {
|
||||||
|
OrderID *uuid.UUID `json:"order_id,omitempty"`
|
||||||
|
OrderItemID *uuid.UUID `json:"order_item_id,omitempty"`
|
||||||
|
ProductID *uuid.UUID `json:"product_id,omitempty"`
|
||||||
|
ProductVariantID *uuid.UUID `json:"product_variant_id,omitempty"`
|
||||||
|
IngredientID *uuid.UUID `json:"ingredient_id,omitempty"`
|
||||||
|
StartDate *time.Time `json:"start_date,omitempty"`
|
||||||
|
EndDate *time.Time `json:"end_date,omitempty"`
|
||||||
|
Page int `json:"page" validate:"min=1"`
|
||||||
|
Limit int `json:"limit" validate:"min=1,max=100"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type OrderIngredientTransactionSummary struct {
|
||||||
|
IngredientID uuid.UUID `json:"ingredient_id"`
|
||||||
|
IngredientName string `json:"ingredient_name"`
|
||||||
|
TotalGrossQty float64 `json:"total_gross_qty"`
|
||||||
|
TotalNetQty float64 `json:"total_net_qty"`
|
||||||
|
TotalWasteQty float64 `json:"total_waste_qty"`
|
||||||
|
WastePercentage float64 `json:"waste_percentage"`
|
||||||
|
Unit string `json:"unit"`
|
||||||
|
}
|
||||||
48
internal/entities/order_ingredient_transaction.go
Normal file
48
internal/entities/order_ingredient_transaction.go
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
package entities
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OrderIngredientTransaction struct {
|
||||||
|
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" validate:"required"`
|
||||||
|
OutletID *uuid.UUID `gorm:"type:uuid;index" json:"outlet_id"`
|
||||||
|
OrderID uuid.UUID `gorm:"type:uuid;not null;index" json:"order_id" validate:"required"`
|
||||||
|
OrderItemID *uuid.UUID `gorm:"type:uuid;index" json:"order_item_id"`
|
||||||
|
ProductID uuid.UUID `gorm:"type:uuid;not null;index" json:"product_id" validate:"required"`
|
||||||
|
ProductVariantID *uuid.UUID `gorm:"type:uuid;index" json:"product_variant_id"`
|
||||||
|
IngredientID uuid.UUID `gorm:"type:uuid;not null;index" json:"ingredient_id" validate:"required"`
|
||||||
|
GrossQty float64 `gorm:"type:decimal(12,3);not null" json:"gross_qty" validate:"required,gt=0"`
|
||||||
|
NetQty float64 `gorm:"type:decimal(12,3);not null" json:"net_qty" validate:"required,gt=0"`
|
||||||
|
WasteQty float64 `gorm:"type:decimal(12,3);not null" json:"waste_qty" validate:"min=0"`
|
||||||
|
Unit string `gorm:"size:50;not null" json:"unit" validate:"required,max=50"`
|
||||||
|
TransactionDate time.Time `gorm:"not null;index" json:"transaction_date"`
|
||||||
|
CreatedBy uuid.UUID `gorm:"type:uuid;not null;index" json:"created_by" validate:"required"`
|
||||||
|
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||||
|
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
||||||
|
|
||||||
|
// Relations
|
||||||
|
Organization Organization `gorm:"foreignKey:OrganizationID" json:"organization,omitempty"`
|
||||||
|
Outlet *Outlet `gorm:"foreignKey:OutletID" json:"outlet,omitempty"`
|
||||||
|
Order Order `gorm:"foreignKey:OrderID" json:"order,omitempty"`
|
||||||
|
OrderItem *OrderItem `gorm:"foreignKey:OrderItemID" json:"order_item,omitempty"`
|
||||||
|
Product Product `gorm:"foreignKey:ProductID" json:"product,omitempty"`
|
||||||
|
ProductVariant *ProductVariant `gorm:"foreignKey:ProductVariantID" json:"product_variant,omitempty"`
|
||||||
|
Ingredient Ingredient `gorm:"foreignKey:IngredientID" json:"ingredient,omitempty"`
|
||||||
|
CreatedByUser User `gorm:"foreignKey:CreatedBy" json:"created_by_user,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (oit *OrderIngredientTransaction) BeforeCreate(tx *gorm.DB) error {
|
||||||
|
if oit.ID == uuid.Nil {
|
||||||
|
oit.ID = uuid.New()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (OrderIngredientTransaction) TableName() string {
|
||||||
|
return "order_ingredients_transactions"
|
||||||
|
}
|
||||||
@ -13,6 +13,7 @@ type ProductIngredient struct {
|
|||||||
ProductID uuid.UUID `json:"product_id" db:"product_id"`
|
ProductID uuid.UUID `json:"product_id" db:"product_id"`
|
||||||
IngredientID uuid.UUID `json:"ingredient_id" db:"ingredient_id"`
|
IngredientID uuid.UUID `json:"ingredient_id" db:"ingredient_id"`
|
||||||
Quantity float64 `json:"quantity" db:"quantity"`
|
Quantity float64 `json:"quantity" db:"quantity"`
|
||||||
|
WastePercentage float64 `json:"waste_percentage" db:"waste_percentage"`
|
||||||
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
|
||||||
|
|
||||||
|
|||||||
@ -254,3 +254,25 @@ func (h *IngredientUnitConverterHandler) ConvertUnit(c *gin.Context) {
|
|||||||
util.HandleResponse(c.Writer, c.Request, converterResponse, "IngredientUnitConverterHandler::ConvertUnit")
|
util.HandleResponse(c.Writer, c.Request, converterResponse, "IngredientUnitConverterHandler::ConvertUnit")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *IngredientUnitConverterHandler) GetUnitsByIngredientID(c *gin.Context) {
|
||||||
|
ctx := c.Request.Context()
|
||||||
|
contextInfo := appcontext.FromGinContext(ctx)
|
||||||
|
|
||||||
|
ingredientIDStr := c.Param("ingredient_id")
|
||||||
|
ingredientID, err := uuid.Parse(ingredientIDStr)
|
||||||
|
if err != nil {
|
||||||
|
logger.FromContext(ctx).WithError(err).Error("IngredientUnitConverterHandler::GetUnitsByIngredientID -> Invalid ingredient ID")
|
||||||
|
validationResponseError := contract.NewResponseError(constants.MalformedFieldErrorCode, constants.RequestEntity, "Invalid ingredient ID")
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "IngredientUnitConverterHandler::GetUnitsByIngredientID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
unitsResponse := h.converterService.GetUnitsByIngredientID(ctx, contextInfo, ingredientID)
|
||||||
|
if unitsResponse.HasErrors() {
|
||||||
|
errorResp := unitsResponse.GetErrors()[0]
|
||||||
|
logger.FromContext(ctx).WithError(errorResp).Error("IngredientUnitConverterHandler::GetUnitsByIngredientID -> Failed to get units for ingredient from service")
|
||||||
|
}
|
||||||
|
|
||||||
|
util.HandleResponse(c.Writer, c.Request, unitsResponse, "IngredientUnitConverterHandler::GetUnitsByIngredientID")
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
201
internal/handler/order_ingredient_transaction_handler.go
Normal file
201
internal/handler/order_ingredient_transaction_handler.go
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"apskel-pos-be/internal/contract"
|
||||||
|
"apskel-pos-be/internal/util"
|
||||||
|
"apskel-pos-be/internal/validator"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OrderIngredientTransactionHandler struct {
|
||||||
|
service OrderIngredientTransactionService
|
||||||
|
validator validator.OrderIngredientTransactionValidator
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewOrderIngredientTransactionHandler(service OrderIngredientTransactionService, validator validator.OrderIngredientTransactionValidator) *OrderIngredientTransactionHandler {
|
||||||
|
return &OrderIngredientTransactionHandler{
|
||||||
|
service: service,
|
||||||
|
validator: validator,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *OrderIngredientTransactionHandler) CreateOrderIngredientTransaction(c *gin.Context) {
|
||||||
|
var req contract.CreateOrderIngredientTransactionRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "OrderIngredientTransactionHandler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := h.service.CreateOrderIngredientTransaction(c, &req)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "OrderIngredientTransactionHandler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "OrderIngredientTransactionHandler")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *OrderIngredientTransactionHandler) GetOrderIngredientTransactionByID(c *gin.Context) {
|
||||||
|
idStr := c.Param("id")
|
||||||
|
id, err := uuid.Parse(idStr)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: "Invalid ID format"}}), "OrderIngredientTransactionHandler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := h.service.GetOrderIngredientTransactionByID(c, id)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "OrderIngredientTransactionHandler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "OrderIngredientTransactionHandler")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *OrderIngredientTransactionHandler) UpdateOrderIngredientTransaction(c *gin.Context) {
|
||||||
|
idStr := c.Param("id")
|
||||||
|
id, err := uuid.Parse(idStr)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: "Invalid ID format"}}), "OrderIngredientTransactionHandler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req contract.UpdateOrderIngredientTransactionRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "OrderIngredientTransactionHandler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := h.service.UpdateOrderIngredientTransaction(c, id, &req)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "OrderIngredientTransactionHandler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "OrderIngredientTransactionHandler")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *OrderIngredientTransactionHandler) DeleteOrderIngredientTransaction(c *gin.Context) {
|
||||||
|
idStr := c.Param("id")
|
||||||
|
id, err := uuid.Parse(idStr)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: "Invalid ID format"}}), "OrderIngredientTransactionHandler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.service.DeleteOrderIngredientTransaction(c, id)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "OrderIngredientTransactionHandler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(gin.H{"message": "Order ingredient transaction deleted successfully"}), "OrderIngredientTransactionHandler")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *OrderIngredientTransactionHandler) ListOrderIngredientTransactions(c *gin.Context) {
|
||||||
|
var req contract.ListOrderIngredientTransactionsRequest
|
||||||
|
if err := c.ShouldBindQuery(&req); err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "OrderIngredientTransactionHandler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response, total, err := h.service.ListOrderIngredientTransactions(c, &req)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "OrderIngredientTransactionHandler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(gin.H{
|
||||||
|
"data": response,
|
||||||
|
"total": total,
|
||||||
|
"page": req.Page,
|
||||||
|
"limit": req.Limit,
|
||||||
|
}), "OrderIngredientTransactionHandler")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *OrderIngredientTransactionHandler) GetOrderIngredientTransactionsByOrder(c *gin.Context) {
|
||||||
|
orderIDStr := c.Param("order_id")
|
||||||
|
orderID, err := uuid.Parse(orderIDStr)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: "Invalid order ID format"}}), "OrderIngredientTransactionHandler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := h.service.GetOrderIngredientTransactionsByOrder(c, orderID)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "OrderIngredientTransactionHandler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "OrderIngredientTransactionHandler")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *OrderIngredientTransactionHandler) GetOrderIngredientTransactionsByOrderItem(c *gin.Context) {
|
||||||
|
orderItemIDStr := c.Param("order_item_id")
|
||||||
|
orderItemID, err := uuid.Parse(orderItemIDStr)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: "Invalid order item ID format"}}), "OrderIngredientTransactionHandler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := h.service.GetOrderIngredientTransactionsByOrderItem(c, orderItemID)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "OrderIngredientTransactionHandler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "OrderIngredientTransactionHandler")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *OrderIngredientTransactionHandler) GetOrderIngredientTransactionsByIngredient(c *gin.Context) {
|
||||||
|
ingredientIDStr := c.Param("ingredient_id")
|
||||||
|
ingredientID, err := uuid.Parse(ingredientIDStr)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: "Invalid ingredient ID format"}}), "OrderIngredientTransactionHandler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := h.service.GetOrderIngredientTransactionsByIngredient(c, ingredientID)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "OrderIngredientTransactionHandler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "OrderIngredientTransactionHandler")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *OrderIngredientTransactionHandler) GetOrderIngredientTransactionSummary(c *gin.Context) {
|
||||||
|
var req contract.ListOrderIngredientTransactionsRequest
|
||||||
|
if err := c.ShouldBindQuery(&req); err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "OrderIngredientTransactionHandler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := h.service.GetOrderIngredientTransactionSummary(c, &req)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "OrderIngredientTransactionHandler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "OrderIngredientTransactionHandler")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *OrderIngredientTransactionHandler) BulkCreateOrderIngredientTransactions(c *gin.Context) {
|
||||||
|
var req struct {
|
||||||
|
Transactions []*contract.CreateOrderIngredientTransactionRequest `json:"transactions" validate:"required,min=1"`
|
||||||
|
}
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "OrderIngredientTransactionHandler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := h.service.BulkCreateOrderIngredientTransactions(c, req.Transactions)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "OrderIngredientTransactionHandler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "OrderIngredientTransactionHandler")
|
||||||
|
}
|
||||||
21
internal/handler/order_ingredient_transaction_interface.go
Normal file
21
internal/handler/order_ingredient_transaction_interface.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"apskel-pos-be/internal/contract"
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OrderIngredientTransactionService interface {
|
||||||
|
CreateOrderIngredientTransaction(ctx context.Context, req *contract.CreateOrderIngredientTransactionRequest) (*contract.OrderIngredientTransactionResponse, error)
|
||||||
|
GetOrderIngredientTransactionByID(ctx context.Context, id uuid.UUID) (*contract.OrderIngredientTransactionResponse, error)
|
||||||
|
UpdateOrderIngredientTransaction(ctx context.Context, id uuid.UUID, req *contract.UpdateOrderIngredientTransactionRequest) (*contract.OrderIngredientTransactionResponse, error)
|
||||||
|
DeleteOrderIngredientTransaction(ctx context.Context, id uuid.UUID) error
|
||||||
|
ListOrderIngredientTransactions(ctx context.Context, req *contract.ListOrderIngredientTransactionsRequest) ([]*contract.OrderIngredientTransactionResponse, int64, error)
|
||||||
|
GetOrderIngredientTransactionsByOrder(ctx context.Context, orderID uuid.UUID) ([]*contract.OrderIngredientTransactionResponse, error)
|
||||||
|
GetOrderIngredientTransactionsByOrderItem(ctx context.Context, orderItemID uuid.UUID) ([]*contract.OrderIngredientTransactionResponse, error)
|
||||||
|
GetOrderIngredientTransactionsByIngredient(ctx context.Context, ingredientID uuid.UUID) ([]*contract.OrderIngredientTransactionResponse, error)
|
||||||
|
GetOrderIngredientTransactionSummary(ctx context.Context, req *contract.ListOrderIngredientTransactionsRequest) ([]*contract.OrderIngredientTransactionSummary, error)
|
||||||
|
BulkCreateOrderIngredientTransactions(ctx context.Context, transactions []*contract.CreateOrderIngredientTransactionRequest) ([]*contract.OrderIngredientTransactionResponse, error)
|
||||||
|
}
|
||||||
@ -166,3 +166,110 @@ func ModelToContractAccountResponse(resp *models.AccountResponse) *contract.Acco
|
|||||||
|
|
||||||
return contractResp
|
return contractResp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Order Ingredient Transaction mappers
|
||||||
|
func ContractToModelCreateOrderIngredientTransactionRequest(req *contract.CreateOrderIngredientTransactionRequest) *models.CreateOrderIngredientTransactionRequest {
|
||||||
|
return &models.CreateOrderIngredientTransactionRequest{
|
||||||
|
OrderID: req.OrderID,
|
||||||
|
OrderItemID: req.OrderItemID,
|
||||||
|
ProductID: req.ProductID,
|
||||||
|
ProductVariantID: req.ProductVariantID,
|
||||||
|
IngredientID: req.IngredientID,
|
||||||
|
GrossQty: req.GrossQty,
|
||||||
|
NetQty: req.NetQty,
|
||||||
|
WasteQty: req.WasteQty,
|
||||||
|
Unit: req.Unit,
|
||||||
|
TransactionDate: req.TransactionDate,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ContractToModelUpdateOrderIngredientTransactionRequest(req *contract.UpdateOrderIngredientTransactionRequest) *models.UpdateOrderIngredientTransactionRequest {
|
||||||
|
return &models.UpdateOrderIngredientTransactionRequest{
|
||||||
|
GrossQty: req.GrossQty,
|
||||||
|
NetQty: req.NetQty,
|
||||||
|
WasteQty: req.WasteQty,
|
||||||
|
Unit: req.Unit,
|
||||||
|
TransactionDate: req.TransactionDate,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ModelToContractOrderIngredientTransactionResponse(resp *models.OrderIngredientTransactionResponse) *contract.OrderIngredientTransactionResponse {
|
||||||
|
return &contract.OrderIngredientTransactionResponse{
|
||||||
|
ID: resp.ID,
|
||||||
|
OrganizationID: resp.OrganizationID,
|
||||||
|
OutletID: resp.OutletID,
|
||||||
|
OrderID: resp.OrderID,
|
||||||
|
OrderItemID: resp.OrderItemID,
|
||||||
|
ProductID: resp.ProductID,
|
||||||
|
ProductVariantID: resp.ProductVariantID,
|
||||||
|
IngredientID: resp.IngredientID,
|
||||||
|
GrossQty: resp.GrossQty,
|
||||||
|
NetQty: resp.NetQty,
|
||||||
|
WasteQty: resp.WasteQty,
|
||||||
|
Unit: resp.Unit,
|
||||||
|
TransactionDate: resp.TransactionDate,
|
||||||
|
CreatedBy: resp.CreatedBy,
|
||||||
|
CreatedAt: resp.CreatedAt,
|
||||||
|
UpdatedAt: resp.UpdatedAt,
|
||||||
|
Organization: resp.Organization,
|
||||||
|
Outlet: resp.Outlet,
|
||||||
|
Order: resp.Order,
|
||||||
|
OrderItem: resp.OrderItem,
|
||||||
|
Product: resp.Product,
|
||||||
|
ProductVariant: resp.ProductVariant,
|
||||||
|
Ingredient: resp.Ingredient,
|
||||||
|
CreatedByUser: resp.CreatedByUser,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ModelToContractOrderIngredientTransactionResponses(responses []*models.OrderIngredientTransactionResponse) []*contract.OrderIngredientTransactionResponse {
|
||||||
|
if responses == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
contractResponses := make([]*contract.OrderIngredientTransactionResponse, len(responses))
|
||||||
|
for i, resp := range responses {
|
||||||
|
contractResponses[i] = ModelToContractOrderIngredientTransactionResponse(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
return contractResponses
|
||||||
|
}
|
||||||
|
|
||||||
|
func ContractToModelListOrderIngredientTransactionsRequest(req *contract.ListOrderIngredientTransactionsRequest) *models.ListOrderIngredientTransactionsRequest {
|
||||||
|
return &models.ListOrderIngredientTransactionsRequest{
|
||||||
|
OrderID: req.OrderID,
|
||||||
|
OrderItemID: req.OrderItemID,
|
||||||
|
ProductID: req.ProductID,
|
||||||
|
ProductVariantID: req.ProductVariantID,
|
||||||
|
IngredientID: req.IngredientID,
|
||||||
|
StartDate: req.StartDate,
|
||||||
|
EndDate: req.EndDate,
|
||||||
|
Page: req.Page,
|
||||||
|
Limit: req.Limit,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ModelToContractOrderIngredientTransactionSummary(resp *models.OrderIngredientTransactionSummary) *contract.OrderIngredientTransactionSummary {
|
||||||
|
return &contract.OrderIngredientTransactionSummary{
|
||||||
|
IngredientID: resp.IngredientID,
|
||||||
|
IngredientName: resp.IngredientName,
|
||||||
|
TotalGrossQty: resp.TotalGrossQty,
|
||||||
|
TotalNetQty: resp.TotalNetQty,
|
||||||
|
TotalWasteQty: resp.TotalWasteQty,
|
||||||
|
WastePercentage: resp.WastePercentage,
|
||||||
|
Unit: resp.Unit,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ModelToContractOrderIngredientTransactionSummaries(responses []*models.OrderIngredientTransactionSummary) []*contract.OrderIngredientTransactionSummary {
|
||||||
|
if responses == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
contractResponses := make([]*contract.OrderIngredientTransactionSummary, len(responses))
|
||||||
|
for i, resp := range responses {
|
||||||
|
contractResponses[i] = ModelToContractOrderIngredientTransactionSummary(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
return contractResponses
|
||||||
|
}
|
||||||
|
|||||||
185
internal/mappers/order_ingredient_transaction_mapper.go
Normal file
185
internal/mappers/order_ingredient_transaction_mapper.go
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
package mappers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"apskel-pos-be/internal/entities"
|
||||||
|
"apskel-pos-be/internal/models"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
func MapOrderIngredientTransactionEntityToModel(entity *entities.OrderIngredientTransaction) *models.OrderIngredientTransaction {
|
||||||
|
if entity == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &models.OrderIngredientTransaction{
|
||||||
|
ID: entity.ID,
|
||||||
|
OrganizationID: entity.OrganizationID,
|
||||||
|
OutletID: entity.OutletID,
|
||||||
|
OrderID: entity.OrderID,
|
||||||
|
OrderItemID: entity.OrderItemID,
|
||||||
|
ProductID: entity.ProductID,
|
||||||
|
ProductVariantID: entity.ProductVariantID,
|
||||||
|
IngredientID: entity.IngredientID,
|
||||||
|
GrossQty: entity.GrossQty,
|
||||||
|
NetQty: entity.NetQty,
|
||||||
|
WasteQty: entity.WasteQty,
|
||||||
|
Unit: entity.Unit,
|
||||||
|
TransactionDate: entity.TransactionDate,
|
||||||
|
CreatedBy: entity.CreatedBy,
|
||||||
|
CreatedAt: entity.CreatedAt,
|
||||||
|
UpdatedAt: entity.UpdatedAt,
|
||||||
|
Organization: nil,
|
||||||
|
Outlet: nil,
|
||||||
|
Order: nil,
|
||||||
|
OrderItem: nil,
|
||||||
|
Product: nil,
|
||||||
|
ProductVariant: nil,
|
||||||
|
Ingredient: nil,
|
||||||
|
CreatedByUser: nil,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func MapOrderIngredientTransactionModelToEntity(model *models.OrderIngredientTransaction) *entities.OrderIngredientTransaction {
|
||||||
|
if model == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &entities.OrderIngredientTransaction{
|
||||||
|
ID: model.ID,
|
||||||
|
OrganizationID: model.OrganizationID,
|
||||||
|
OutletID: model.OutletID,
|
||||||
|
OrderID: model.OrderID,
|
||||||
|
OrderItemID: model.OrderItemID,
|
||||||
|
ProductID: model.ProductID,
|
||||||
|
ProductVariantID: model.ProductVariantID,
|
||||||
|
IngredientID: model.IngredientID,
|
||||||
|
GrossQty: model.GrossQty,
|
||||||
|
NetQty: model.NetQty,
|
||||||
|
WasteQty: model.WasteQty,
|
||||||
|
Unit: model.Unit,
|
||||||
|
TransactionDate: model.TransactionDate,
|
||||||
|
CreatedBy: model.CreatedBy,
|
||||||
|
CreatedAt: model.CreatedAt,
|
||||||
|
UpdatedAt: model.UpdatedAt,
|
||||||
|
Organization: entities.Organization{},
|
||||||
|
Outlet: nil,
|
||||||
|
Order: entities.Order{},
|
||||||
|
OrderItem: nil,
|
||||||
|
Product: entities.Product{},
|
||||||
|
ProductVariant: nil,
|
||||||
|
Ingredient: entities.Ingredient{},
|
||||||
|
CreatedByUser: entities.User{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func MapOrderIngredientTransactionEntitiesToModels(entities []*entities.OrderIngredientTransaction) []*models.OrderIngredientTransaction {
|
||||||
|
if entities == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
models := make([]*models.OrderIngredientTransaction, len(entities))
|
||||||
|
for i, entity := range entities {
|
||||||
|
models[i] = MapOrderIngredientTransactionEntityToModel(entity)
|
||||||
|
}
|
||||||
|
|
||||||
|
return models
|
||||||
|
}
|
||||||
|
|
||||||
|
func MapOrderIngredientTransactionModelsToEntities(models []*models.OrderIngredientTransaction) []*entities.OrderIngredientTransaction {
|
||||||
|
if models == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
entities := make([]*entities.OrderIngredientTransaction, len(models))
|
||||||
|
for i, model := range models {
|
||||||
|
entities[i] = MapOrderIngredientTransactionModelToEntity(model)
|
||||||
|
}
|
||||||
|
|
||||||
|
return entities
|
||||||
|
}
|
||||||
|
|
||||||
|
func MapOrderIngredientTransactionEntityToResponse(entity *entities.OrderIngredientTransaction) *models.OrderIngredientTransactionResponse {
|
||||||
|
if entity == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &models.OrderIngredientTransactionResponse{
|
||||||
|
ID: entity.ID,
|
||||||
|
OrganizationID: entity.OrganizationID,
|
||||||
|
OutletID: entity.OutletID,
|
||||||
|
OrderID: entity.OrderID,
|
||||||
|
OrderItemID: entity.OrderItemID,
|
||||||
|
ProductID: entity.ProductID,
|
||||||
|
ProductVariantID: entity.ProductVariantID,
|
||||||
|
IngredientID: entity.IngredientID,
|
||||||
|
GrossQty: entity.GrossQty,
|
||||||
|
NetQty: entity.NetQty,
|
||||||
|
WasteQty: entity.WasteQty,
|
||||||
|
Unit: entity.Unit,
|
||||||
|
TransactionDate: entity.TransactionDate,
|
||||||
|
CreatedBy: entity.CreatedBy,
|
||||||
|
CreatedAt: entity.CreatedAt,
|
||||||
|
UpdatedAt: entity.UpdatedAt,
|
||||||
|
Organization: nil,
|
||||||
|
Outlet: nil,
|
||||||
|
Order: nil,
|
||||||
|
OrderItem: nil,
|
||||||
|
Product: nil,
|
||||||
|
ProductVariant: nil,
|
||||||
|
Ingredient: nil,
|
||||||
|
CreatedByUser: nil,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func MapOrderIngredientTransactionEntitiesToResponses(entities []*entities.OrderIngredientTransaction) []*models.OrderIngredientTransactionResponse {
|
||||||
|
if entities == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
responses := make([]*models.OrderIngredientTransactionResponse, len(entities))
|
||||||
|
for i, entity := range entities {
|
||||||
|
responses[i] = MapOrderIngredientTransactionEntityToResponse(entity)
|
||||||
|
}
|
||||||
|
|
||||||
|
return responses
|
||||||
|
}
|
||||||
|
|
||||||
|
func MapOrderIngredientTransactionSummary(transactions []*entities.OrderIngredientTransaction) []*models.OrderIngredientTransactionSummary {
|
||||||
|
if transactions == nil || len(transactions) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Group by ingredient ID
|
||||||
|
ingredientMap := make(map[uuid.UUID]*models.OrderIngredientTransactionSummary)
|
||||||
|
|
||||||
|
for _, transaction := range transactions {
|
||||||
|
ingredientID := transaction.IngredientID
|
||||||
|
|
||||||
|
if summary, exists := ingredientMap[ingredientID]; exists {
|
||||||
|
summary.TotalGrossQty += transaction.GrossQty
|
||||||
|
summary.TotalNetQty += transaction.NetQty
|
||||||
|
summary.TotalWasteQty += transaction.WasteQty
|
||||||
|
} else {
|
||||||
|
ingredientMap[ingredientID] = &models.OrderIngredientTransactionSummary{
|
||||||
|
IngredientID: ingredientID,
|
||||||
|
IngredientName: transaction.Ingredient.Name,
|
||||||
|
TotalGrossQty: transaction.GrossQty,
|
||||||
|
TotalNetQty: transaction.NetQty,
|
||||||
|
TotalWasteQty: transaction.WasteQty,
|
||||||
|
Unit: transaction.Unit,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert map to slice and calculate waste percentage
|
||||||
|
var summaries []*models.OrderIngredientTransactionSummary
|
||||||
|
for _, summary := range ingredientMap {
|
||||||
|
if summary.TotalGrossQty > 0 {
|
||||||
|
summary.WastePercentage = (summary.TotalWasteQty / summary.TotalGrossQty) * 100
|
||||||
|
}
|
||||||
|
summaries = append(summaries, summary)
|
||||||
|
}
|
||||||
|
|
||||||
|
return summaries
|
||||||
|
}
|
||||||
@ -17,6 +17,7 @@ func MapProductIngredientEntityToModel(entity *entities.ProductIngredient) *mode
|
|||||||
ProductID: entity.ProductID,
|
ProductID: entity.ProductID,
|
||||||
IngredientID: entity.IngredientID,
|
IngredientID: entity.IngredientID,
|
||||||
Quantity: entity.Quantity,
|
Quantity: entity.Quantity,
|
||||||
|
WastePercentage: entity.WastePercentage,
|
||||||
CreatedAt: entity.CreatedAt,
|
CreatedAt: entity.CreatedAt,
|
||||||
UpdatedAt: entity.UpdatedAt,
|
UpdatedAt: entity.UpdatedAt,
|
||||||
Product: ProductEntityToModel(entity.Product),
|
Product: ProductEntityToModel(entity.Product),
|
||||||
@ -36,6 +37,7 @@ func MapProductIngredientModelToEntity(model *models.ProductIngredient) *entitie
|
|||||||
ProductID: model.ProductID,
|
ProductID: model.ProductID,
|
||||||
IngredientID: model.IngredientID,
|
IngredientID: model.IngredientID,
|
||||||
Quantity: model.Quantity,
|
Quantity: model.Quantity,
|
||||||
|
WastePercentage: model.WastePercentage,
|
||||||
CreatedAt: model.CreatedAt,
|
CreatedAt: model.CreatedAt,
|
||||||
UpdatedAt: model.UpdatedAt,
|
UpdatedAt: model.UpdatedAt,
|
||||||
Product: ProductModelToEntity(model.Product),
|
Product: ProductModelToEntity(model.Product),
|
||||||
|
|||||||
@ -94,3 +94,11 @@ type ListIngredientUnitConvertersResponse struct {
|
|||||||
TotalPages int `json:"total_pages"`
|
TotalPages int `json:"total_pages"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type IngredientUnitsResponse struct {
|
||||||
|
IngredientID uuid.UUID `json:"ingredient_id"`
|
||||||
|
IngredientName string `json:"ingredient_name"`
|
||||||
|
BaseUnitID uuid.UUID `json:"base_unit_id"`
|
||||||
|
BaseUnitName string `json:"base_unit_name"`
|
||||||
|
Units []*UnitResponse `json:"units"`
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
108
internal/models/order_ingredient_transaction.go
Normal file
108
internal/models/order_ingredient_transaction.go
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OrderIngredientTransaction struct {
|
||||||
|
ID uuid.UUID
|
||||||
|
OrganizationID uuid.UUID
|
||||||
|
OutletID *uuid.UUID
|
||||||
|
OrderID uuid.UUID
|
||||||
|
OrderItemID *uuid.UUID
|
||||||
|
ProductID uuid.UUID
|
||||||
|
ProductVariantID *uuid.UUID
|
||||||
|
IngredientID uuid.UUID
|
||||||
|
GrossQty float64
|
||||||
|
NetQty float64
|
||||||
|
WasteQty float64
|
||||||
|
Unit string
|
||||||
|
TransactionDate time.Time
|
||||||
|
CreatedBy uuid.UUID
|
||||||
|
CreatedAt time.Time
|
||||||
|
UpdatedAt time.Time
|
||||||
|
|
||||||
|
// Relations
|
||||||
|
Organization *Organization `json:"organization,omitempty"`
|
||||||
|
Outlet *Outlet `json:"outlet,omitempty"`
|
||||||
|
Order *Order `json:"order,omitempty"`
|
||||||
|
OrderItem *OrderItem `json:"order_item,omitempty"`
|
||||||
|
Product *Product `json:"product,omitempty"`
|
||||||
|
ProductVariant *ProductVariant `json:"product_variant,omitempty"`
|
||||||
|
Ingredient *Ingredient `json:"ingredient,omitempty"`
|
||||||
|
CreatedByUser *User `json:"created_by_user,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateOrderIngredientTransactionRequest struct {
|
||||||
|
OrderID uuid.UUID `json:"order_id" validate:"required"`
|
||||||
|
OrderItemID *uuid.UUID `json:"order_item_id,omitempty"`
|
||||||
|
ProductID uuid.UUID `json:"product_id" validate:"required"`
|
||||||
|
ProductVariantID *uuid.UUID `json:"product_variant_id,omitempty"`
|
||||||
|
IngredientID uuid.UUID `json:"ingredient_id" validate:"required"`
|
||||||
|
GrossQty float64 `json:"gross_qty" validate:"required,gt=0"`
|
||||||
|
NetQty float64 `json:"net_qty" validate:"required,gt=0"`
|
||||||
|
WasteQty float64 `json:"waste_qty" validate:"min=0"`
|
||||||
|
Unit string `json:"unit" validate:"required,max=50"`
|
||||||
|
TransactionDate *time.Time `json:"transaction_date,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateOrderIngredientTransactionRequest struct {
|
||||||
|
GrossQty *float64 `json:"gross_qty,omitempty" validate:"omitempty,gt=0"`
|
||||||
|
NetQty *float64 `json:"net_qty,omitempty" validate:"omitempty,gt=0"`
|
||||||
|
WasteQty *float64 `json:"waste_qty,omitempty" validate:"min=0"`
|
||||||
|
Unit *string `json:"unit,omitempty" validate:"omitempty,max=50"`
|
||||||
|
TransactionDate *time.Time `json:"transaction_date,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type OrderIngredientTransactionResponse struct {
|
||||||
|
ID uuid.UUID
|
||||||
|
OrganizationID uuid.UUID
|
||||||
|
OutletID *uuid.UUID
|
||||||
|
OrderID uuid.UUID
|
||||||
|
OrderItemID *uuid.UUID
|
||||||
|
ProductID uuid.UUID
|
||||||
|
ProductVariantID *uuid.UUID
|
||||||
|
IngredientID uuid.UUID
|
||||||
|
GrossQty float64
|
||||||
|
NetQty float64
|
||||||
|
WasteQty float64
|
||||||
|
Unit string
|
||||||
|
TransactionDate time.Time
|
||||||
|
CreatedBy uuid.UUID
|
||||||
|
CreatedAt time.Time
|
||||||
|
UpdatedAt time.Time
|
||||||
|
|
||||||
|
// Relations
|
||||||
|
Organization *Organization `json:"organization,omitempty"`
|
||||||
|
Outlet *Outlet `json:"outlet,omitempty"`
|
||||||
|
Order *Order `json:"order,omitempty"`
|
||||||
|
OrderItem *OrderItem `json:"order_item,omitempty"`
|
||||||
|
Product *Product `json:"product,omitempty"`
|
||||||
|
ProductVariant *ProductVariant `json:"product_variant,omitempty"`
|
||||||
|
Ingredient *Ingredient `json:"ingredient,omitempty"`
|
||||||
|
CreatedByUser *User `json:"created_by_user,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListOrderIngredientTransactionsRequest struct {
|
||||||
|
OrderID *uuid.UUID `json:"order_id,omitempty"`
|
||||||
|
OrderItemID *uuid.UUID `json:"order_item_id,omitempty"`
|
||||||
|
ProductID *uuid.UUID `json:"product_id,omitempty"`
|
||||||
|
ProductVariantID *uuid.UUID `json:"product_variant_id,omitempty"`
|
||||||
|
IngredientID *uuid.UUID `json:"ingredient_id,omitempty"`
|
||||||
|
StartDate *time.Time `json:"start_date,omitempty"`
|
||||||
|
EndDate *time.Time `json:"end_date,omitempty"`
|
||||||
|
Page int `json:"page" validate:"min=1"`
|
||||||
|
Limit int `json:"limit" validate:"min=1,max=100"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type OrderIngredientTransactionSummary struct {
|
||||||
|
IngredientID uuid.UUID `json:"ingredient_id"`
|
||||||
|
IngredientName string `json:"ingredient_name"`
|
||||||
|
TotalGrossQty float64 `json:"total_gross_qty"`
|
||||||
|
TotalNetQty float64 `json:"total_net_qty"`
|
||||||
|
TotalWasteQty float64 `json:"total_waste_qty"`
|
||||||
|
WastePercentage float64 `json:"waste_percentage"`
|
||||||
|
Unit string `json:"unit"`
|
||||||
|
}
|
||||||
@ -13,6 +13,7 @@ type ProductIngredient struct {
|
|||||||
ProductID uuid.UUID `json:"product_id"`
|
ProductID uuid.UUID `json:"product_id"`
|
||||||
IngredientID uuid.UUID `json:"ingredient_id"`
|
IngredientID uuid.UUID `json:"ingredient_id"`
|
||||||
Quantity float64 `json:"quantity"`
|
Quantity float64 `json:"quantity"`
|
||||||
|
WastePercentage float64 `json:"waste_percentage"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
|
||||||
@ -26,11 +27,13 @@ type CreateProductIngredientRequest struct {
|
|||||||
ProductID uuid.UUID `json:"product_id" validate:"required"`
|
ProductID uuid.UUID `json:"product_id" validate:"required"`
|
||||||
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 UpdateProductIngredientRequest struct {
|
type UpdateProductIngredientRequest struct {
|
||||||
OutletID *uuid.UUID `json:"outlet_id"`
|
OutletID *uuid.UUID `json:"outlet_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 ProductIngredientResponse struct {
|
type ProductIngredientResponse struct {
|
||||||
@ -40,6 +43,7 @@ type ProductIngredientResponse struct {
|
|||||||
ProductID uuid.UUID `json:"product_id"`
|
ProductID uuid.UUID `json:"product_id"`
|
||||||
IngredientID uuid.UUID `json:"ingredient_id"`
|
IngredientID uuid.UUID `json:"ingredient_id"`
|
||||||
Quantity float64 `json:"quantity"`
|
Quantity float64 `json:"quantity"`
|
||||||
|
WastePercentage float64 `json:"waste_percentage"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
|
||||||
|
|||||||
@ -12,7 +12,6 @@ import (
|
|||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
type AccountProcessor interface {
|
type AccountProcessor interface {
|
||||||
CreateAccount(ctx context.Context, req *models.CreateAccountRequest) (*models.AccountResponse, error)
|
CreateAccount(ctx context.Context, req *models.CreateAccountRequest) (*models.AccountResponse, error)
|
||||||
GetAccountByID(ctx context.Context, id uuid.UUID) (*models.AccountResponse, error)
|
GetAccountByID(ctx context.Context, id uuid.UUID) (*models.AccountResponse, error)
|
||||||
|
|||||||
@ -10,7 +10,6 @@ import (
|
|||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
type ChartOfAccountTypeProcessor interface {
|
type ChartOfAccountTypeProcessor interface {
|
||||||
CreateChartOfAccountType(ctx context.Context, req *models.CreateChartOfAccountTypeRequest) (*models.ChartOfAccountTypeResponse, error)
|
CreateChartOfAccountType(ctx context.Context, req *models.CreateChartOfAccountTypeRequest) (*models.ChartOfAccountTypeResponse, error)
|
||||||
GetChartOfAccountTypeByID(ctx context.Context, id uuid.UUID) (*models.ChartOfAccountTypeResponse, error)
|
GetChartOfAccountTypeByID(ctx context.Context, id uuid.UUID) (*models.ChartOfAccountTypeResponse, error)
|
||||||
|
|||||||
@ -1,17 +1 @@
|
|||||||
package processor
|
package processor
|
||||||
|
|
||||||
import (
|
|
||||||
"apskel-pos-be/internal/entities"
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
type IngredientRepository interface {
|
|
||||||
Create(ctx context.Context, ingredient *entities.Ingredient) error
|
|
||||||
GetByID(ctx context.Context, id, organizationID uuid.UUID) (*entities.Ingredient, error)
|
|
||||||
GetAll(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID, page, limit int, search string, isSemiFinished *bool) ([]*entities.Ingredient, int, error)
|
|
||||||
Update(ctx context.Context, ingredient *entities.Ingredient) error
|
|
||||||
Delete(ctx context.Context, id, organizationID uuid.UUID) error
|
|
||||||
UpdateStock(ctx context.Context, id uuid.UUID, newStock float64, organizationID uuid.UUID) error
|
|
||||||
}
|
|
||||||
|
|||||||
@ -32,6 +32,7 @@ type IngredientUnitConverterProcessor interface {
|
|||||||
ListIngredientUnitConverters(ctx context.Context, organizationID uuid.UUID, filters map[string]interface{}, page, limit int) ([]*models.IngredientUnitConverterResponse, int, error)
|
ListIngredientUnitConverters(ctx context.Context, organizationID uuid.UUID, filters map[string]interface{}, page, limit int) ([]*models.IngredientUnitConverterResponse, int, error)
|
||||||
GetConvertersForIngredient(ctx context.Context, ingredientID, organizationID uuid.UUID) ([]*models.IngredientUnitConverterResponse, error)
|
GetConvertersForIngredient(ctx context.Context, ingredientID, organizationID uuid.UUID) ([]*models.IngredientUnitConverterResponse, error)
|
||||||
ConvertUnit(ctx context.Context, organizationID uuid.UUID, req *models.ConvertUnitRequest) (*models.ConvertUnitResponse, error)
|
ConvertUnit(ctx context.Context, organizationID uuid.UUID, req *models.ConvertUnitRequest) (*models.ConvertUnitResponse, error)
|
||||||
|
GetUnitsByIngredientID(ctx context.Context, organizationID, ingredientID uuid.UUID) (*models.IngredientUnitsResponse, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type IngredientUnitConverterProcessorImpl struct {
|
type IngredientUnitConverterProcessorImpl struct {
|
||||||
@ -257,3 +258,64 @@ func (p *IngredientUnitConverterProcessorImpl) ConvertUnit(ctx context.Context,
|
|||||||
|
|
||||||
return response, nil
|
return response, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *IngredientUnitConverterProcessorImpl) GetUnitsByIngredientID(ctx context.Context, organizationID, ingredientID uuid.UUID) (*models.IngredientUnitsResponse, error) {
|
||||||
|
// Get the ingredient with its base unit
|
||||||
|
ingredient, err := p.ingredientRepo.GetByID(ctx, ingredientID, organizationID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get ingredient: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the base unit details
|
||||||
|
baseUnit, err := p.unitRepo.GetByID(ctx, ingredient.UnitID, organizationID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get base unit: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start with the base unit
|
||||||
|
units := []*models.UnitResponse{
|
||||||
|
mappers.MapUnitEntityToResponse(baseUnit),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all converters for this ingredient
|
||||||
|
converters, err := p.converterRepo.GetConvertersForIngredient(ctx, ingredientID, organizationID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get converters: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add unique units from converters
|
||||||
|
unitMap := make(map[uuid.UUID]bool)
|
||||||
|
unitMap[baseUnit.ID] = true
|
||||||
|
|
||||||
|
for _, converter := range converters {
|
||||||
|
if converter.IsActive {
|
||||||
|
// Add FromUnit if not already added
|
||||||
|
if !unitMap[converter.FromUnitID] {
|
||||||
|
fromUnit, err := p.unitRepo.GetByID(ctx, converter.FromUnitID, organizationID)
|
||||||
|
if err == nil {
|
||||||
|
units = append(units, mappers.MapUnitEntityToResponse(fromUnit))
|
||||||
|
unitMap[converter.FromUnitID] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add ToUnit if not already added
|
||||||
|
if !unitMap[converter.ToUnitID] {
|
||||||
|
toUnit, err := p.unitRepo.GetByID(ctx, converter.ToUnitID, organizationID)
|
||||||
|
if err == nil {
|
||||||
|
units = append(units, mappers.MapUnitEntityToResponse(toUnit))
|
||||||
|
unitMap[converter.ToUnitID] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
response := &models.IngredientUnitsResponse{
|
||||||
|
IngredientID: ingredientID,
|
||||||
|
IngredientName: ingredient.Name,
|
||||||
|
BaseUnitID: baseUnit.ID,
|
||||||
|
BaseUnitName: baseUnit.Name,
|
||||||
|
Units: units,
|
||||||
|
}
|
||||||
|
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|||||||
393
internal/processor/order_ingredient_transaction_processor.go
Normal file
393
internal/processor/order_ingredient_transaction_processor.go
Normal file
@ -0,0 +1,393 @@
|
|||||||
|
package processor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"apskel-pos-be/internal/entities"
|
||||||
|
"apskel-pos-be/internal/mappers"
|
||||||
|
"apskel-pos-be/internal/models"
|
||||||
|
"apskel-pos-be/internal/util"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OrderIngredientTransactionProcessor interface {
|
||||||
|
CreateOrderIngredientTransaction(ctx context.Context, req *models.CreateOrderIngredientTransactionRequest, organizationID, outletID, createdBy uuid.UUID) (*models.OrderIngredientTransactionResponse, error)
|
||||||
|
GetOrderIngredientTransactionByID(ctx context.Context, id, organizationID uuid.UUID) (*models.OrderIngredientTransactionResponse, error)
|
||||||
|
UpdateOrderIngredientTransaction(ctx context.Context, id uuid.UUID, req *models.UpdateOrderIngredientTransactionRequest, organizationID uuid.UUID) (*models.OrderIngredientTransactionResponse, error)
|
||||||
|
DeleteOrderIngredientTransaction(ctx context.Context, id, organizationID uuid.UUID) error
|
||||||
|
ListOrderIngredientTransactions(ctx context.Context, req *models.ListOrderIngredientTransactionsRequest, organizationID uuid.UUID) ([]*models.OrderIngredientTransactionResponse, int64, error)
|
||||||
|
GetOrderIngredientTransactionsByOrder(ctx context.Context, orderID, organizationID uuid.UUID) ([]*models.OrderIngredientTransactionResponse, error)
|
||||||
|
GetOrderIngredientTransactionsByOrderItem(ctx context.Context, orderItemID, organizationID uuid.UUID) ([]*models.OrderIngredientTransactionResponse, error)
|
||||||
|
GetOrderIngredientTransactionsByIngredient(ctx context.Context, ingredientID, organizationID uuid.UUID) ([]*models.OrderIngredientTransactionResponse, error)
|
||||||
|
GetOrderIngredientTransactionSummary(ctx context.Context, req *models.ListOrderIngredientTransactionsRequest, organizationID uuid.UUID) ([]*models.OrderIngredientTransactionSummary, error)
|
||||||
|
BulkCreateOrderIngredientTransactions(ctx context.Context, transactions []*models.CreateOrderIngredientTransactionRequest, organizationID, outletID, createdBy uuid.UUID) ([]*models.OrderIngredientTransactionResponse, error)
|
||||||
|
CalculateWasteQuantities(ctx context.Context, productID uuid.UUID, quantity float64, organizationID uuid.UUID) ([]*models.CreateOrderIngredientTransactionRequest, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type OrderIngredientTransactionProcessorImpl struct {
|
||||||
|
orderIngredientTransactionRepo OrderIngredientTransactionRepository
|
||||||
|
productIngredientRepo ProductIngredientRepository
|
||||||
|
ingredientRepo IngredientRepository
|
||||||
|
unitRepo UnitRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewOrderIngredientTransactionProcessorImpl(
|
||||||
|
orderIngredientTransactionRepo OrderIngredientTransactionRepository,
|
||||||
|
productIngredientRepo ProductIngredientRepository,
|
||||||
|
ingredientRepo IngredientRepository,
|
||||||
|
unitRepo UnitRepository,
|
||||||
|
) OrderIngredientTransactionProcessor {
|
||||||
|
return &OrderIngredientTransactionProcessorImpl{
|
||||||
|
orderIngredientTransactionRepo: orderIngredientTransactionRepo,
|
||||||
|
productIngredientRepo: productIngredientRepo,
|
||||||
|
ingredientRepo: ingredientRepo,
|
||||||
|
unitRepo: unitRepo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *OrderIngredientTransactionProcessorImpl) CreateOrderIngredientTransaction(ctx context.Context, req *models.CreateOrderIngredientTransactionRequest, organizationID, outletID, createdBy uuid.UUID) (*models.OrderIngredientTransactionResponse, error) {
|
||||||
|
// Validate that gross qty >= net qty
|
||||||
|
if req.GrossQty < req.NetQty {
|
||||||
|
return nil, fmt.Errorf("gross quantity must be greater than or equal to net quantity")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate that waste qty = gross qty - net qty
|
||||||
|
expectedWasteQty := req.GrossQty - req.NetQty
|
||||||
|
if req.WasteQty != expectedWasteQty {
|
||||||
|
return nil, fmt.Errorf("waste quantity must equal gross quantity minus net quantity")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set transaction date if not provided
|
||||||
|
transactionDate := time.Now()
|
||||||
|
if req.TransactionDate != nil {
|
||||||
|
transactionDate = *req.TransactionDate
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create entity
|
||||||
|
entity := &entities.OrderIngredientTransaction{
|
||||||
|
ID: uuid.New(),
|
||||||
|
OrganizationID: organizationID,
|
||||||
|
OutletID: &outletID,
|
||||||
|
OrderID: req.OrderID,
|
||||||
|
OrderItemID: req.OrderItemID,
|
||||||
|
ProductID: req.ProductID,
|
||||||
|
ProductVariantID: req.ProductVariantID,
|
||||||
|
IngredientID: req.IngredientID,
|
||||||
|
GrossQty: req.GrossQty,
|
||||||
|
NetQty: req.NetQty,
|
||||||
|
WasteQty: req.WasteQty,
|
||||||
|
Unit: req.Unit,
|
||||||
|
TransactionDate: transactionDate,
|
||||||
|
CreatedBy: createdBy,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create in database
|
||||||
|
if err := p.orderIngredientTransactionRepo.Create(ctx, entity); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create order ingredient transaction: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get created entity with relations
|
||||||
|
createdEntity, err := p.orderIngredientTransactionRepo.GetByID(ctx, entity.ID, organizationID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get created order ingredient transaction: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to response
|
||||||
|
response := mappers.MapOrderIngredientTransactionEntityToResponse(createdEntity)
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *OrderIngredientTransactionProcessorImpl) GetOrderIngredientTransactionByID(ctx context.Context, id, organizationID uuid.UUID) (*models.OrderIngredientTransactionResponse, error) {
|
||||||
|
entity, err := p.orderIngredientTransactionRepo.GetByID(ctx, id, organizationID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get order ingredient transaction: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
response := mappers.MapOrderIngredientTransactionEntityToResponse(entity)
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *OrderIngredientTransactionProcessorImpl) UpdateOrderIngredientTransaction(ctx context.Context, id uuid.UUID, req *models.UpdateOrderIngredientTransactionRequest, organizationID uuid.UUID) (*models.OrderIngredientTransactionResponse, error) {
|
||||||
|
// Get existing entity
|
||||||
|
entity, err := p.orderIngredientTransactionRepo.GetByID(ctx, id, organizationID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get order ingredient transaction: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update fields
|
||||||
|
if req.GrossQty != nil {
|
||||||
|
entity.GrossQty = *req.GrossQty
|
||||||
|
}
|
||||||
|
if req.NetQty != nil {
|
||||||
|
entity.NetQty = *req.NetQty
|
||||||
|
}
|
||||||
|
if req.WasteQty != nil {
|
||||||
|
entity.WasteQty = *req.WasteQty
|
||||||
|
}
|
||||||
|
if req.Unit != nil {
|
||||||
|
entity.Unit = *req.Unit
|
||||||
|
}
|
||||||
|
if req.TransactionDate != nil {
|
||||||
|
entity.TransactionDate = *req.TransactionDate
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate quantities
|
||||||
|
if entity.GrossQty < entity.NetQty {
|
||||||
|
return nil, fmt.Errorf("gross quantity must be greater than or equal to net quantity")
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedWasteQty := entity.GrossQty - entity.NetQty
|
||||||
|
if entity.WasteQty != expectedWasteQty {
|
||||||
|
return nil, fmt.Errorf("waste quantity must equal gross quantity minus net quantity")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update in database
|
||||||
|
if err := p.orderIngredientTransactionRepo.Update(ctx, entity); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to update order ingredient transaction: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get updated entity with relations
|
||||||
|
updatedEntity, err := p.orderIngredientTransactionRepo.GetByID(ctx, id, organizationID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get updated order ingredient transaction: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
response := mappers.MapOrderIngredientTransactionEntityToResponse(updatedEntity)
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *OrderIngredientTransactionProcessorImpl) DeleteOrderIngredientTransaction(ctx context.Context, id, organizationID uuid.UUID) error {
|
||||||
|
if err := p.orderIngredientTransactionRepo.Delete(ctx, id, organizationID); err != nil {
|
||||||
|
return fmt.Errorf("failed to delete order ingredient transaction: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *OrderIngredientTransactionProcessorImpl) ListOrderIngredientTransactions(ctx context.Context, req *models.ListOrderIngredientTransactionsRequest, organizationID uuid.UUID) ([]*models.OrderIngredientTransactionResponse, int64, error) {
|
||||||
|
// Convert filters
|
||||||
|
filters := make(map[string]interface{})
|
||||||
|
if req.OrderID != nil {
|
||||||
|
filters["order_id"] = *req.OrderID
|
||||||
|
}
|
||||||
|
if req.OrderItemID != nil {
|
||||||
|
filters["order_item_id"] = *req.OrderItemID
|
||||||
|
}
|
||||||
|
if req.ProductID != nil {
|
||||||
|
filters["product_id"] = *req.ProductID
|
||||||
|
}
|
||||||
|
if req.ProductVariantID != nil {
|
||||||
|
filters["product_variant_id"] = *req.ProductVariantID
|
||||||
|
}
|
||||||
|
if req.IngredientID != nil {
|
||||||
|
filters["ingredient_id"] = *req.IngredientID
|
||||||
|
}
|
||||||
|
if req.StartDate != nil {
|
||||||
|
filters["start_date"] = req.StartDate.Format(time.RFC3339)
|
||||||
|
}
|
||||||
|
if req.EndDate != nil {
|
||||||
|
filters["end_date"] = req.EndDate.Format(time.RFC3339)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set default pagination
|
||||||
|
page := req.Page
|
||||||
|
if page <= 0 {
|
||||||
|
page = 1
|
||||||
|
}
|
||||||
|
limit := req.Limit
|
||||||
|
if limit <= 0 {
|
||||||
|
limit = 10
|
||||||
|
}
|
||||||
|
|
||||||
|
entities, total, err := p.orderIngredientTransactionRepo.List(ctx, organizationID, filters, page, limit)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, fmt.Errorf("failed to list order ingredient transactions: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
responses := mappers.MapOrderIngredientTransactionEntitiesToResponses(entities)
|
||||||
|
return responses, total, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *OrderIngredientTransactionProcessorImpl) GetOrderIngredientTransactionsByOrder(ctx context.Context, orderID, organizationID uuid.UUID) ([]*models.OrderIngredientTransactionResponse, error) {
|
||||||
|
entities, err := p.orderIngredientTransactionRepo.GetByOrderID(ctx, orderID, organizationID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get order ingredient transactions by order: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
responses := mappers.MapOrderIngredientTransactionEntitiesToResponses(entities)
|
||||||
|
return responses, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *OrderIngredientTransactionProcessorImpl) GetOrderIngredientTransactionsByOrderItem(ctx context.Context, orderItemID, organizationID uuid.UUID) ([]*models.OrderIngredientTransactionResponse, error) {
|
||||||
|
entities, err := p.orderIngredientTransactionRepo.GetByOrderItemID(ctx, orderItemID, organizationID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get order ingredient transactions by order item: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
responses := mappers.MapOrderIngredientTransactionEntitiesToResponses(entities)
|
||||||
|
return responses, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *OrderIngredientTransactionProcessorImpl) GetOrderIngredientTransactionsByIngredient(ctx context.Context, ingredientID, organizationID uuid.UUID) ([]*models.OrderIngredientTransactionResponse, error) {
|
||||||
|
entities, err := p.orderIngredientTransactionRepo.GetByIngredientID(ctx, ingredientID, organizationID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get order ingredient transactions by ingredient: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
responses := mappers.MapOrderIngredientTransactionEntitiesToResponses(entities)
|
||||||
|
return responses, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *OrderIngredientTransactionProcessorImpl) GetOrderIngredientTransactionSummary(ctx context.Context, req *models.ListOrderIngredientTransactionsRequest, organizationID uuid.UUID) ([]*models.OrderIngredientTransactionSummary, error) {
|
||||||
|
// Convert filters
|
||||||
|
filters := make(map[string]interface{})
|
||||||
|
if req.OrderID != nil {
|
||||||
|
filters["order_id"] = *req.OrderID
|
||||||
|
}
|
||||||
|
if req.OrderItemID != nil {
|
||||||
|
filters["order_item_id"] = *req.OrderItemID
|
||||||
|
}
|
||||||
|
if req.ProductID != nil {
|
||||||
|
filters["product_id"] = *req.ProductID
|
||||||
|
}
|
||||||
|
if req.ProductVariantID != nil {
|
||||||
|
filters["product_variant_id"] = *req.ProductVariantID
|
||||||
|
}
|
||||||
|
if req.IngredientID != nil {
|
||||||
|
filters["ingredient_id"] = *req.IngredientID
|
||||||
|
}
|
||||||
|
if req.StartDate != nil {
|
||||||
|
filters["start_date"] = req.StartDate.Format(time.RFC3339)
|
||||||
|
}
|
||||||
|
if req.EndDate != nil {
|
||||||
|
filters["end_date"] = req.EndDate.Format(time.RFC3339)
|
||||||
|
}
|
||||||
|
|
||||||
|
entities, err := p.orderIngredientTransactionRepo.GetSummary(ctx, organizationID, filters)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get order ingredient transaction summary: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
summaries := mappers.MapOrderIngredientTransactionSummary(entities)
|
||||||
|
return summaries, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *OrderIngredientTransactionProcessorImpl) BulkCreateOrderIngredientTransactions(ctx context.Context, transactions []*models.CreateOrderIngredientTransactionRequest, organizationID, outletID, createdBy uuid.UUID) ([]*models.OrderIngredientTransactionResponse, error) {
|
||||||
|
if len(transactions) == 0 {
|
||||||
|
return []*models.OrderIngredientTransactionResponse{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to entities
|
||||||
|
transactionEntities := make([]*entities.OrderIngredientTransaction, len(transactions))
|
||||||
|
for i, req := range transactions {
|
||||||
|
// Validate quantities
|
||||||
|
if req.GrossQty < req.NetQty {
|
||||||
|
return nil, fmt.Errorf("gross quantity must be greater than or equal to net quantity for transaction %d", i)
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedWasteQty := req.GrossQty - req.NetQty
|
||||||
|
if req.WasteQty != expectedWasteQty {
|
||||||
|
return nil, fmt.Errorf("waste quantity must equal gross quantity minus net quantity for transaction %d", i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set transaction date if not provided
|
||||||
|
transactionDate := time.Now()
|
||||||
|
if req.TransactionDate != nil {
|
||||||
|
transactionDate = *req.TransactionDate
|
||||||
|
}
|
||||||
|
|
||||||
|
transactionEntities[i] = &entities.OrderIngredientTransaction{
|
||||||
|
ID: uuid.New(),
|
||||||
|
OrganizationID: organizationID,
|
||||||
|
OutletID: &outletID,
|
||||||
|
OrderID: req.OrderID,
|
||||||
|
OrderItemID: req.OrderItemID,
|
||||||
|
ProductID: req.ProductID,
|
||||||
|
ProductVariantID: req.ProductVariantID,
|
||||||
|
IngredientID: req.IngredientID,
|
||||||
|
GrossQty: req.GrossQty,
|
||||||
|
NetQty: req.NetQty,
|
||||||
|
WasteQty: req.WasteQty,
|
||||||
|
Unit: req.Unit,
|
||||||
|
TransactionDate: transactionDate,
|
||||||
|
CreatedBy: createdBy,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bulk create
|
||||||
|
if err := p.orderIngredientTransactionRepo.BulkCreate(ctx, transactionEntities); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to bulk create order ingredient transactions: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get created entities with relations
|
||||||
|
responses := make([]*models.OrderIngredientTransactionResponse, len(transactionEntities))
|
||||||
|
for i, entity := range transactionEntities {
|
||||||
|
createdEntity, err := p.orderIngredientTransactionRepo.GetByID(ctx, entity.ID, organizationID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get created order ingredient transaction %d: %w", i, err)
|
||||||
|
}
|
||||||
|
responses[i] = mappers.MapOrderIngredientTransactionEntityToResponse(createdEntity)
|
||||||
|
}
|
||||||
|
|
||||||
|
return responses, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *OrderIngredientTransactionProcessorImpl) CalculateWasteQuantities(ctx context.Context, productID uuid.UUID, quantity float64, organizationID uuid.UUID) ([]*models.CreateOrderIngredientTransactionRequest, error) {
|
||||||
|
// Get product ingredients
|
||||||
|
productIngredients, err := p.productIngredientRepo.GetByProductID(ctx, productID, organizationID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get product ingredients: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(productIngredients) == 0 {
|
||||||
|
return []*models.CreateOrderIngredientTransactionRequest{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get ingredient details for unit information
|
||||||
|
ingredientMap := make(map[uuid.UUID]*entities.Ingredient)
|
||||||
|
for _, pi := range productIngredients {
|
||||||
|
ingredient, err := p.ingredientRepo.GetByID(ctx, pi.IngredientID, organizationID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get ingredient %s: %w", pi.IngredientID, err)
|
||||||
|
}
|
||||||
|
ingredientMap[pi.IngredientID] = ingredient
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate quantities for each ingredient
|
||||||
|
transactions := make([]*models.CreateOrderIngredientTransactionRequest, 0, len(productIngredients))
|
||||||
|
for _, pi := range productIngredients {
|
||||||
|
ingredient := ingredientMap[pi.IngredientID]
|
||||||
|
|
||||||
|
// Calculate net quantity (actual quantity needed for the product)
|
||||||
|
netQty := pi.Quantity * quantity
|
||||||
|
|
||||||
|
// Calculate gross quantity (including waste)
|
||||||
|
wasteMultiplier := 1 + (pi.WastePercentage / 100)
|
||||||
|
grossQty := netQty * wasteMultiplier
|
||||||
|
|
||||||
|
// Calculate waste quantity
|
||||||
|
wasteQty := grossQty - netQty
|
||||||
|
|
||||||
|
// Get unit name
|
||||||
|
unitName := "unit" // default
|
||||||
|
if ingredient.UnitID != uuid.Nil {
|
||||||
|
unit, err := p.unitRepo.GetByID(ctx, ingredient.UnitID, organizationID)
|
||||||
|
if err == nil {
|
||||||
|
unitName = unit.Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
transaction := &models.CreateOrderIngredientTransactionRequest{
|
||||||
|
IngredientID: pi.IngredientID,
|
||||||
|
GrossQty: util.RoundToDecimalPlaces(grossQty, 3),
|
||||||
|
NetQty: util.RoundToDecimalPlaces(netQty, 3),
|
||||||
|
WasteQty: util.RoundToDecimalPlaces(wasteQty, 3),
|
||||||
|
Unit: unitName,
|
||||||
|
}
|
||||||
|
|
||||||
|
transactions = append(transactions, transaction)
|
||||||
|
}
|
||||||
|
|
||||||
|
return transactions, nil
|
||||||
|
}
|
||||||
@ -42,3 +42,43 @@ type AccountRepository interface {
|
|||||||
UpdateBalance(ctx context.Context, id uuid.UUID, amount float64) error
|
UpdateBalance(ctx context.Context, id uuid.UUID, amount float64) error
|
||||||
GetBalance(ctx context.Context, id uuid.UUID) (float64, error)
|
GetBalance(ctx context.Context, id uuid.UUID) (float64, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type OrderIngredientTransactionRepository interface {
|
||||||
|
Create(ctx context.Context, transaction *entities.OrderIngredientTransaction) error
|
||||||
|
GetByID(ctx context.Context, id, organizationID uuid.UUID) (*entities.OrderIngredientTransaction, error)
|
||||||
|
Update(ctx context.Context, transaction *entities.OrderIngredientTransaction) error
|
||||||
|
Delete(ctx context.Context, id, organizationID uuid.UUID) error
|
||||||
|
List(ctx context.Context, organizationID uuid.UUID, filters map[string]interface{}, page, limit int) ([]*entities.OrderIngredientTransaction, int64, error)
|
||||||
|
GetByOrderID(ctx context.Context, orderID, organizationID uuid.UUID) ([]*entities.OrderIngredientTransaction, error)
|
||||||
|
GetByOrderItemID(ctx context.Context, orderItemID, organizationID uuid.UUID) ([]*entities.OrderIngredientTransaction, error)
|
||||||
|
GetByIngredientID(ctx context.Context, ingredientID, organizationID uuid.UUID) ([]*entities.OrderIngredientTransaction, error)
|
||||||
|
GetSummary(ctx context.Context, organizationID uuid.UUID, filters map[string]interface{}) ([]*entities.OrderIngredientTransaction, error)
|
||||||
|
BulkCreate(ctx context.Context, transactions []*entities.OrderIngredientTransaction) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProductIngredientRepository interface {
|
||||||
|
Create(ctx context.Context, productIngredient *entities.ProductIngredient) error
|
||||||
|
GetByID(ctx context.Context, id, organizationID uuid.UUID) (*entities.ProductIngredient, error)
|
||||||
|
GetByProductID(ctx context.Context, productID, organizationID uuid.UUID) ([]*entities.ProductIngredient, error)
|
||||||
|
GetByIngredientID(ctx context.Context, ingredientID, organizationID uuid.UUID) ([]*entities.ProductIngredient, error)
|
||||||
|
Update(ctx context.Context, productIngredient *entities.ProductIngredient) error
|
||||||
|
Delete(ctx context.Context, id, organizationID uuid.UUID) error
|
||||||
|
DeleteByProductID(ctx context.Context, productID, organizationID uuid.UUID) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type IngredientRepository interface {
|
||||||
|
Create(ctx context.Context, ingredient *entities.Ingredient) error
|
||||||
|
GetByID(ctx context.Context, id, organizationID uuid.UUID) (*entities.Ingredient, error)
|
||||||
|
GetAll(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID, page, limit int, search string, isSemiFinished *bool) ([]*entities.Ingredient, int, error)
|
||||||
|
Update(ctx context.Context, ingredient *entities.Ingredient) error
|
||||||
|
Delete(ctx context.Context, id, organizationID uuid.UUID) error
|
||||||
|
UpdateStock(ctx context.Context, id uuid.UUID, newStock float64, organizationID uuid.UUID) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type UnitRepository interface {
|
||||||
|
Create(ctx context.Context, unit *entities.Unit) error
|
||||||
|
GetByID(ctx context.Context, id, organizationID uuid.UUID) (*entities.Unit, error)
|
||||||
|
GetAll(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID, page, limit int, search string) ([]*entities.Unit, int, error)
|
||||||
|
Update(ctx context.Context, unit *entities.Unit) error
|
||||||
|
Delete(ctx context.Context, id, organizationID uuid.UUID) error
|
||||||
|
}
|
||||||
|
|||||||
@ -1,16 +1 @@
|
|||||||
package processor
|
package processor
|
||||||
|
|
||||||
import (
|
|
||||||
"apskel-pos-be/internal/entities"
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
type UnitRepository interface {
|
|
||||||
Create(ctx context.Context, unit *entities.Unit) error
|
|
||||||
GetByID(ctx context.Context, id, organizationID uuid.UUID) (*entities.Unit, error)
|
|
||||||
GetAll(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID, page, limit int, search string) ([]*entities.Unit, int, error)
|
|
||||||
Update(ctx context.Context, unit *entities.Unit) error
|
|
||||||
Delete(ctx context.Context, id, organizationID uuid.UUID) error
|
|
||||||
}
|
|
||||||
|
|||||||
234
internal/repository/order_ingredient_transaction_repository.go
Normal file
234
internal/repository/order_ingredient_transaction_repository.go
Normal file
@ -0,0 +1,234 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"apskel-pos-be/internal/entities"
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OrderIngredientTransactionRepository interface {
|
||||||
|
Create(ctx context.Context, transaction *entities.OrderIngredientTransaction) error
|
||||||
|
GetByID(ctx context.Context, id, organizationID uuid.UUID) (*entities.OrderIngredientTransaction, error)
|
||||||
|
Update(ctx context.Context, transaction *entities.OrderIngredientTransaction) error
|
||||||
|
Delete(ctx context.Context, id, organizationID uuid.UUID) error
|
||||||
|
List(ctx context.Context, organizationID uuid.UUID, filters map[string]interface{}, page, limit int) ([]*entities.OrderIngredientTransaction, int64, error)
|
||||||
|
GetByOrderID(ctx context.Context, orderID, organizationID uuid.UUID) ([]*entities.OrderIngredientTransaction, error)
|
||||||
|
GetByOrderItemID(ctx context.Context, orderItemID, organizationID uuid.UUID) ([]*entities.OrderIngredientTransaction, error)
|
||||||
|
GetByIngredientID(ctx context.Context, ingredientID, organizationID uuid.UUID) ([]*entities.OrderIngredientTransaction, error)
|
||||||
|
GetSummary(ctx context.Context, organizationID uuid.UUID, filters map[string]interface{}) ([]*entities.OrderIngredientTransaction, error)
|
||||||
|
BulkCreate(ctx context.Context, transactions []*entities.OrderIngredientTransaction) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type OrderIngredientTransactionRepositoryImpl struct {
|
||||||
|
db *gorm.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewOrderIngredientTransactionRepositoryImpl(db *gorm.DB) OrderIngredientTransactionRepository {
|
||||||
|
return &OrderIngredientTransactionRepositoryImpl{db: db}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *OrderIngredientTransactionRepositoryImpl) Create(ctx context.Context, transaction *entities.OrderIngredientTransaction) error {
|
||||||
|
return r.db.WithContext(ctx).Create(transaction).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *OrderIngredientTransactionRepositoryImpl) GetByID(ctx context.Context, id, organizationID uuid.UUID) (*entities.OrderIngredientTransaction, error) {
|
||||||
|
var transaction entities.OrderIngredientTransaction
|
||||||
|
err := r.db.WithContext(ctx).
|
||||||
|
Where("id = ? AND organization_id = ?", id, organizationID).
|
||||||
|
Preload("Organization").
|
||||||
|
Preload("Outlet").
|
||||||
|
Preload("Order").
|
||||||
|
Preload("OrderItem").
|
||||||
|
Preload("Product").
|
||||||
|
Preload("ProductVariant").
|
||||||
|
Preload("Ingredient").
|
||||||
|
Preload("CreatedByUser").
|
||||||
|
First(&transaction).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &transaction, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *OrderIngredientTransactionRepositoryImpl) Update(ctx context.Context, transaction *entities.OrderIngredientTransaction) error {
|
||||||
|
return r.db.WithContext(ctx).Save(transaction).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *OrderIngredientTransactionRepositoryImpl) Delete(ctx context.Context, id, organizationID uuid.UUID) error {
|
||||||
|
return r.db.WithContext(ctx).
|
||||||
|
Where("id = ? AND organization_id = ?", id, organizationID).
|
||||||
|
Delete(&entities.OrderIngredientTransaction{}).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *OrderIngredientTransactionRepositoryImpl) List(ctx context.Context, organizationID uuid.UUID, filters map[string]interface{}, page, limit int) ([]*entities.OrderIngredientTransaction, int64, error) {
|
||||||
|
var transactions []*entities.OrderIngredientTransaction
|
||||||
|
var total int64
|
||||||
|
|
||||||
|
query := r.db.WithContext(ctx).Model(&entities.OrderIngredientTransaction{}).
|
||||||
|
Where("organization_id = ?", organizationID)
|
||||||
|
|
||||||
|
// Apply filters
|
||||||
|
for key, value := range filters {
|
||||||
|
switch key {
|
||||||
|
case "order_id":
|
||||||
|
if orderID, ok := value.(uuid.UUID); ok {
|
||||||
|
query = query.Where("order_id = ?", orderID)
|
||||||
|
}
|
||||||
|
case "order_item_id":
|
||||||
|
if orderItemID, ok := value.(uuid.UUID); ok {
|
||||||
|
query = query.Where("order_item_id = ?", orderItemID)
|
||||||
|
}
|
||||||
|
case "product_id":
|
||||||
|
if productID, ok := value.(uuid.UUID); ok {
|
||||||
|
query = query.Where("product_id = ?", productID)
|
||||||
|
}
|
||||||
|
case "product_variant_id":
|
||||||
|
if productVariantID, ok := value.(uuid.UUID); ok {
|
||||||
|
query = query.Where("product_variant_id = ?", productVariantID)
|
||||||
|
}
|
||||||
|
case "ingredient_id":
|
||||||
|
if ingredientID, ok := value.(uuid.UUID); ok {
|
||||||
|
query = query.Where("ingredient_id = ?", ingredientID)
|
||||||
|
}
|
||||||
|
case "start_date":
|
||||||
|
if startDate, ok := value.(string); ok {
|
||||||
|
query = query.Where("transaction_date >= ?", startDate)
|
||||||
|
}
|
||||||
|
case "end_date":
|
||||||
|
if endDate, ok := value.(string); ok {
|
||||||
|
query = query.Where("transaction_date <= ?", endDate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get total count
|
||||||
|
if err := query.Count(&total).Error; err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply pagination and get results
|
||||||
|
offset := (page - 1) * limit
|
||||||
|
err := query.
|
||||||
|
Preload("Organization").
|
||||||
|
Preload("Outlet").
|
||||||
|
Preload("Order").
|
||||||
|
Preload("OrderItem").
|
||||||
|
Preload("Product").
|
||||||
|
Preload("ProductVariant").
|
||||||
|
Preload("Ingredient").
|
||||||
|
Preload("CreatedByUser").
|
||||||
|
Order("created_at DESC").
|
||||||
|
Offset(offset).
|
||||||
|
Limit(limit).
|
||||||
|
Find(&transactions).Error
|
||||||
|
|
||||||
|
return transactions, total, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *OrderIngredientTransactionRepositoryImpl) GetByOrderID(ctx context.Context, orderID, organizationID uuid.UUID) ([]*entities.OrderIngredientTransaction, error) {
|
||||||
|
var transactions []*entities.OrderIngredientTransaction
|
||||||
|
err := r.db.WithContext(ctx).
|
||||||
|
Where("order_id = ? AND organization_id = ?", orderID, organizationID).
|
||||||
|
Preload("Organization").
|
||||||
|
Preload("Outlet").
|
||||||
|
Preload("Order").
|
||||||
|
Preload("OrderItem").
|
||||||
|
Preload("Product").
|
||||||
|
Preload("ProductVariant").
|
||||||
|
Preload("Ingredient").
|
||||||
|
Preload("CreatedByUser").
|
||||||
|
Order("created_at ASC").
|
||||||
|
Find(&transactions).Error
|
||||||
|
return transactions, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *OrderIngredientTransactionRepositoryImpl) GetByOrderItemID(ctx context.Context, orderItemID, organizationID uuid.UUID) ([]*entities.OrderIngredientTransaction, error) {
|
||||||
|
var transactions []*entities.OrderIngredientTransaction
|
||||||
|
err := r.db.WithContext(ctx).
|
||||||
|
Where("order_item_id = ? AND organization_id = ?", orderItemID, organizationID).
|
||||||
|
Preload("Organization").
|
||||||
|
Preload("Outlet").
|
||||||
|
Preload("Order").
|
||||||
|
Preload("OrderItem").
|
||||||
|
Preload("Product").
|
||||||
|
Preload("ProductVariant").
|
||||||
|
Preload("Ingredient").
|
||||||
|
Preload("CreatedByUser").
|
||||||
|
Order("created_at ASC").
|
||||||
|
Find(&transactions).Error
|
||||||
|
return transactions, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *OrderIngredientTransactionRepositoryImpl) GetByIngredientID(ctx context.Context, ingredientID, organizationID uuid.UUID) ([]*entities.OrderIngredientTransaction, error) {
|
||||||
|
var transactions []*entities.OrderIngredientTransaction
|
||||||
|
err := r.db.WithContext(ctx).
|
||||||
|
Where("ingredient_id = ? AND organization_id = ?", ingredientID, organizationID).
|
||||||
|
Preload("Organization").
|
||||||
|
Preload("Outlet").
|
||||||
|
Preload("Order").
|
||||||
|
Preload("OrderItem").
|
||||||
|
Preload("Product").
|
||||||
|
Preload("ProductVariant").
|
||||||
|
Preload("Ingredient").
|
||||||
|
Preload("CreatedByUser").
|
||||||
|
Order("created_at DESC").
|
||||||
|
Find(&transactions).Error
|
||||||
|
return transactions, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *OrderIngredientTransactionRepositoryImpl) GetSummary(ctx context.Context, organizationID uuid.UUID, filters map[string]interface{}) ([]*entities.OrderIngredientTransaction, error) {
|
||||||
|
var transactions []*entities.OrderIngredientTransaction
|
||||||
|
|
||||||
|
query := r.db.WithContext(ctx).Model(&entities.OrderIngredientTransaction{}).
|
||||||
|
Where("organization_id = ?", organizationID)
|
||||||
|
|
||||||
|
// Apply filters
|
||||||
|
for key, value := range filters {
|
||||||
|
switch key {
|
||||||
|
case "order_id":
|
||||||
|
if orderID, ok := value.(uuid.UUID); ok {
|
||||||
|
query = query.Where("order_id = ?", orderID)
|
||||||
|
}
|
||||||
|
case "order_item_id":
|
||||||
|
if orderItemID, ok := value.(uuid.UUID); ok {
|
||||||
|
query = query.Where("order_item_id = ?", orderItemID)
|
||||||
|
}
|
||||||
|
case "product_id":
|
||||||
|
if productID, ok := value.(uuid.UUID); ok {
|
||||||
|
query = query.Where("product_id = ?", productID)
|
||||||
|
}
|
||||||
|
case "product_variant_id":
|
||||||
|
if productVariantID, ok := value.(uuid.UUID); ok {
|
||||||
|
query = query.Where("product_variant_id = ?", productVariantID)
|
||||||
|
}
|
||||||
|
case "ingredient_id":
|
||||||
|
if ingredientID, ok := value.(uuid.UUID); ok {
|
||||||
|
query = query.Where("ingredient_id = ?", ingredientID)
|
||||||
|
}
|
||||||
|
case "start_date":
|
||||||
|
if startDate, ok := value.(string); ok {
|
||||||
|
query = query.Where("transaction_date >= ?", startDate)
|
||||||
|
}
|
||||||
|
case "end_date":
|
||||||
|
if endDate, ok := value.(string); ok {
|
||||||
|
query = query.Where("transaction_date <= ?", endDate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err := query.
|
||||||
|
Preload("Ingredient").
|
||||||
|
Order("ingredient_id, created_at ASC").
|
||||||
|
Find(&transactions).Error
|
||||||
|
|
||||||
|
return transactions, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *OrderIngredientTransactionRepositoryImpl) BulkCreate(ctx context.Context, transactions []*entities.OrderIngredientTransaction) error {
|
||||||
|
if len(transactions) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return r.db.WithContext(ctx).CreateInBatches(transactions, 100).Error
|
||||||
|
}
|
||||||
@ -39,6 +39,7 @@ type Router struct {
|
|||||||
chartOfAccountTypeHandler *handler.ChartOfAccountTypeHandler
|
chartOfAccountTypeHandler *handler.ChartOfAccountTypeHandler
|
||||||
chartOfAccountHandler *handler.ChartOfAccountHandler
|
chartOfAccountHandler *handler.ChartOfAccountHandler
|
||||||
accountHandler *handler.AccountHandler
|
accountHandler *handler.AccountHandler
|
||||||
|
orderIngredientTransactionHandler *handler.OrderIngredientTransactionHandler
|
||||||
authMiddleware *middleware.AuthMiddleware
|
authMiddleware *middleware.AuthMiddleware
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,7 +88,9 @@ func NewRouter(cfg *config.Config,
|
|||||||
chartOfAccountService service.ChartOfAccountService,
|
chartOfAccountService service.ChartOfAccountService,
|
||||||
chartOfAccountValidator validator.ChartOfAccountValidator,
|
chartOfAccountValidator validator.ChartOfAccountValidator,
|
||||||
accountService service.AccountService,
|
accountService service.AccountService,
|
||||||
accountValidator validator.AccountValidator) *Router {
|
accountValidator validator.AccountValidator,
|
||||||
|
orderIngredientTransactionService service.OrderIngredientTransactionService,
|
||||||
|
orderIngredientTransactionValidator validator.OrderIngredientTransactionValidator) *Router {
|
||||||
|
|
||||||
return &Router{
|
return &Router{
|
||||||
config: cfg,
|
config: cfg,
|
||||||
@ -116,6 +119,7 @@ func NewRouter(cfg *config.Config,
|
|||||||
chartOfAccountTypeHandler: handler.NewChartOfAccountTypeHandler(chartOfAccountTypeService, chartOfAccountTypeValidator),
|
chartOfAccountTypeHandler: handler.NewChartOfAccountTypeHandler(chartOfAccountTypeService, chartOfAccountTypeValidator),
|
||||||
chartOfAccountHandler: handler.NewChartOfAccountHandler(chartOfAccountService, chartOfAccountValidator),
|
chartOfAccountHandler: handler.NewChartOfAccountHandler(chartOfAccountService, chartOfAccountValidator),
|
||||||
accountHandler: handler.NewAccountHandler(accountService, accountValidator),
|
accountHandler: handler.NewAccountHandler(accountService, accountValidator),
|
||||||
|
orderIngredientTransactionHandler: handler.NewOrderIngredientTransactionHandler(&orderIngredientTransactionService, orderIngredientTransactionValidator),
|
||||||
authMiddleware: authMiddleware,
|
authMiddleware: authMiddleware,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -351,6 +355,7 @@ func (r *Router) addAppRoutes(rg *gin.Engine) {
|
|||||||
unitConverters.POST("", r.unitConverterHandler.CreateIngredientUnitConverter)
|
unitConverters.POST("", r.unitConverterHandler.CreateIngredientUnitConverter)
|
||||||
unitConverters.GET("", r.unitConverterHandler.ListIngredientUnitConverters)
|
unitConverters.GET("", r.unitConverterHandler.ListIngredientUnitConverters)
|
||||||
unitConverters.GET("/ingredient/:ingredient_id", r.unitConverterHandler.GetConvertersForIngredient)
|
unitConverters.GET("/ingredient/:ingredient_id", r.unitConverterHandler.GetConvertersForIngredient)
|
||||||
|
unitConverters.GET("/ingredient/:ingredient_id/units", r.unitConverterHandler.GetUnitsByIngredientID)
|
||||||
unitConverters.POST("/convert", r.unitConverterHandler.ConvertUnit)
|
unitConverters.POST("/convert", r.unitConverterHandler.ConvertUnit)
|
||||||
unitConverters.GET("/:id", r.unitConverterHandler.GetIngredientUnitConverter)
|
unitConverters.GET("/:id", r.unitConverterHandler.GetIngredientUnitConverter)
|
||||||
unitConverters.PUT("/:id", r.unitConverterHandler.UpdateIngredientUnitConverter)
|
unitConverters.PUT("/:id", r.unitConverterHandler.UpdateIngredientUnitConverter)
|
||||||
@ -406,6 +411,21 @@ func (r *Router) addAppRoutes(rg *gin.Engine) {
|
|||||||
accounts.GET("/:id/balance", r.accountHandler.GetAccountBalance)
|
accounts.GET("/:id/balance", r.accountHandler.GetAccountBalance)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
orderIngredientTransactions := protected.Group("/order-ingredient-transactions")
|
||||||
|
orderIngredientTransactions.Use(r.authMiddleware.RequireAdminOrManager())
|
||||||
|
{
|
||||||
|
orderIngredientTransactions.POST("", r.orderIngredientTransactionHandler.CreateOrderIngredientTransaction)
|
||||||
|
orderIngredientTransactions.GET("", r.orderIngredientTransactionHandler.ListOrderIngredientTransactions)
|
||||||
|
orderIngredientTransactions.GET("/:id", r.orderIngredientTransactionHandler.GetOrderIngredientTransactionByID)
|
||||||
|
orderIngredientTransactions.PUT("/:id", r.orderIngredientTransactionHandler.UpdateOrderIngredientTransaction)
|
||||||
|
orderIngredientTransactions.DELETE("/:id", r.orderIngredientTransactionHandler.DeleteOrderIngredientTransaction)
|
||||||
|
orderIngredientTransactions.GET("/order/:order_id", r.orderIngredientTransactionHandler.GetOrderIngredientTransactionsByOrder)
|
||||||
|
orderIngredientTransactions.GET("/order-item/:order_item_id", r.orderIngredientTransactionHandler.GetOrderIngredientTransactionsByOrderItem)
|
||||||
|
orderIngredientTransactions.GET("/ingredient/:ingredient_id", r.orderIngredientTransactionHandler.GetOrderIngredientTransactionsByIngredient)
|
||||||
|
orderIngredientTransactions.GET("/summary", r.orderIngredientTransactionHandler.GetOrderIngredientTransactionSummary)
|
||||||
|
orderIngredientTransactions.POST("/bulk", r.orderIngredientTransactionHandler.BulkCreateOrderIngredientTransactions)
|
||||||
|
}
|
||||||
|
|
||||||
outlets := protected.Group("/outlets")
|
outlets := protected.Group("/outlets")
|
||||||
outlets.Use(r.authMiddleware.RequireAdminOrManager())
|
outlets.Use(r.authMiddleware.RequireAdminOrManager())
|
||||||
{
|
{
|
||||||
|
|||||||
@ -19,6 +19,7 @@ type IngredientUnitConverterService interface {
|
|||||||
ListIngredientUnitConverters(ctx context.Context, apctx *appcontext.ContextInfo, req *contract.ListIngredientUnitConvertersRequest) *contract.Response
|
ListIngredientUnitConverters(ctx context.Context, apctx *appcontext.ContextInfo, req *contract.ListIngredientUnitConvertersRequest) *contract.Response
|
||||||
GetConvertersForIngredient(ctx context.Context, apctx *appcontext.ContextInfo, ingredientID uuid.UUID) *contract.Response
|
GetConvertersForIngredient(ctx context.Context, apctx *appcontext.ContextInfo, ingredientID uuid.UUID) *contract.Response
|
||||||
ConvertUnit(ctx context.Context, apctx *appcontext.ContextInfo, req *contract.ConvertUnitRequest) *contract.Response
|
ConvertUnit(ctx context.Context, apctx *appcontext.ContextInfo, req *contract.ConvertUnitRequest) *contract.Response
|
||||||
|
GetUnitsByIngredientID(ctx context.Context, apctx *appcontext.ContextInfo, ingredientID uuid.UUID) *contract.Response
|
||||||
}
|
}
|
||||||
|
|
||||||
type IngredientUnitConverterServiceImpl struct {
|
type IngredientUnitConverterServiceImpl struct {
|
||||||
@ -149,3 +150,14 @@ func (s *IngredientUnitConverterServiceImpl) ConvertUnit(ctx context.Context, ap
|
|||||||
return contract.BuildSuccessResponse(contractResponse)
|
return contract.BuildSuccessResponse(contractResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *IngredientUnitConverterServiceImpl) GetUnitsByIngredientID(ctx context.Context, apctx *appcontext.ContextInfo, ingredientID uuid.UUID) *contract.Response {
|
||||||
|
unitsResponse, err := s.converterProcessor.GetUnitsByIngredientID(ctx, apctx.OrganizationID, ingredientID)
|
||||||
|
if err != nil {
|
||||||
|
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.IngredientUnitConverterServiceEntity, err.Error())
|
||||||
|
return contract.BuildErrorResponse([]*contract.ResponseError{errorResp})
|
||||||
|
}
|
||||||
|
|
||||||
|
contractResponse := transformer.IngredientUnitsModelResponseToResponse(unitsResponse)
|
||||||
|
return contract.BuildSuccessResponse(contractResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
205
internal/service/order_ingredient_transaction_service.go
Normal file
205
internal/service/order_ingredient_transaction_service.go
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"apskel-pos-be/internal/appcontext"
|
||||||
|
"apskel-pos-be/internal/contract"
|
||||||
|
"apskel-pos-be/internal/mappers"
|
||||||
|
"apskel-pos-be/internal/models"
|
||||||
|
"apskel-pos-be/internal/processor"
|
||||||
|
"apskel-pos-be/internal/repository"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OrderIngredientTransactionService struct {
|
||||||
|
processor processor.OrderIngredientTransactionProcessor
|
||||||
|
txManager *repository.TxManager
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewOrderIngredientTransactionService(processor processor.OrderIngredientTransactionProcessor, txManager *repository.TxManager) *OrderIngredientTransactionService {
|
||||||
|
return &OrderIngredientTransactionService{
|
||||||
|
processor: processor,
|
||||||
|
txManager: txManager,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *OrderIngredientTransactionService) CreateOrderIngredientTransaction(ctx context.Context, req *contract.CreateOrderIngredientTransactionRequest) (*contract.OrderIngredientTransactionResponse, error) {
|
||||||
|
// Get organization and outlet from context
|
||||||
|
appCtx := appcontext.FromGinContext(ctx)
|
||||||
|
organizationID := appCtx.OrganizationID
|
||||||
|
outletID := appCtx.OutletID
|
||||||
|
createdBy := appCtx.UserID
|
||||||
|
|
||||||
|
// Convert contract to model
|
||||||
|
modelReq := mappers.ContractToModelCreateOrderIngredientTransactionRequest(req)
|
||||||
|
|
||||||
|
// Create transaction
|
||||||
|
response, err := s.processor.CreateOrderIngredientTransaction(ctx, modelReq, organizationID, outletID, createdBy)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create order ingredient transaction: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert model to contract
|
||||||
|
contractResp := mappers.ModelToContractOrderIngredientTransactionResponse(response)
|
||||||
|
return contractResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *OrderIngredientTransactionService) GetOrderIngredientTransactionByID(ctx context.Context, id uuid.UUID) (*contract.OrderIngredientTransactionResponse, error) {
|
||||||
|
// Get organization from context
|
||||||
|
appCtx := appcontext.FromGinContext(ctx)
|
||||||
|
organizationID := appCtx.OrganizationID
|
||||||
|
|
||||||
|
// Get transaction
|
||||||
|
response, err := s.processor.GetOrderIngredientTransactionByID(ctx, id, organizationID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get order ingredient transaction: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert model to contract
|
||||||
|
contractResp := mappers.ModelToContractOrderIngredientTransactionResponse(response)
|
||||||
|
return contractResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *OrderIngredientTransactionService) UpdateOrderIngredientTransaction(ctx context.Context, id uuid.UUID, req *contract.UpdateOrderIngredientTransactionRequest) (*contract.OrderIngredientTransactionResponse, error) {
|
||||||
|
// Get organization from context
|
||||||
|
appCtx := appcontext.FromGinContext(ctx)
|
||||||
|
organizationID := appCtx.OrganizationID
|
||||||
|
|
||||||
|
// Convert contract to model
|
||||||
|
modelReq := mappers.ContractToModelUpdateOrderIngredientTransactionRequest(req)
|
||||||
|
|
||||||
|
// Update transaction
|
||||||
|
response, err := s.processor.UpdateOrderIngredientTransaction(ctx, id, modelReq, organizationID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to update order ingredient transaction: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert model to contract
|
||||||
|
contractResp := mappers.ModelToContractOrderIngredientTransactionResponse(response)
|
||||||
|
return contractResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *OrderIngredientTransactionService) DeleteOrderIngredientTransaction(ctx context.Context, id uuid.UUID) error {
|
||||||
|
// Get organization from context
|
||||||
|
appCtx := appcontext.FromGinContext(ctx)
|
||||||
|
organizationID := appCtx.OrganizationID
|
||||||
|
|
||||||
|
// Delete transaction
|
||||||
|
if err := s.processor.DeleteOrderIngredientTransaction(ctx, id, organizationID); err != nil {
|
||||||
|
return fmt.Errorf("failed to delete order ingredient transaction: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *OrderIngredientTransactionService) ListOrderIngredientTransactions(ctx context.Context, req *contract.ListOrderIngredientTransactionsRequest) ([]*contract.OrderIngredientTransactionResponse, int64, error) {
|
||||||
|
// Get organization from context
|
||||||
|
appCtx := appcontext.FromGinContext(ctx)
|
||||||
|
organizationID := appCtx.OrganizationID
|
||||||
|
|
||||||
|
// Convert contract to model
|
||||||
|
modelReq := mappers.ContractToModelListOrderIngredientTransactionsRequest(req)
|
||||||
|
|
||||||
|
// List transactions
|
||||||
|
responses, total, err := s.processor.ListOrderIngredientTransactions(ctx, modelReq, organizationID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, fmt.Errorf("failed to list order ingredient transactions: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert models to contracts
|
||||||
|
contractResponses := mappers.ModelToContractOrderIngredientTransactionResponses(responses)
|
||||||
|
return contractResponses, total, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *OrderIngredientTransactionService) GetOrderIngredientTransactionsByOrder(ctx context.Context, orderID uuid.UUID) ([]*contract.OrderIngredientTransactionResponse, error) {
|
||||||
|
// Get organization from context
|
||||||
|
appCtx := appcontext.FromGinContext(ctx)
|
||||||
|
organizationID := appCtx.OrganizationID
|
||||||
|
|
||||||
|
// Get transactions by order
|
||||||
|
responses, err := s.processor.GetOrderIngredientTransactionsByOrder(ctx, orderID, organizationID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get order ingredient transactions by order: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert models to contracts
|
||||||
|
contractResponses := mappers.ModelToContractOrderIngredientTransactionResponses(responses)
|
||||||
|
return contractResponses, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *OrderIngredientTransactionService) GetOrderIngredientTransactionsByOrderItem(ctx context.Context, orderItemID uuid.UUID) ([]*contract.OrderIngredientTransactionResponse, error) {
|
||||||
|
// Get organization from context
|
||||||
|
appCtx := appcontext.FromGinContext(ctx)
|
||||||
|
organizationID := appCtx.OrganizationID
|
||||||
|
|
||||||
|
// Get transactions by order item
|
||||||
|
responses, err := s.processor.GetOrderIngredientTransactionsByOrderItem(ctx, orderItemID, organizationID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get order ingredient transactions by order item: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert models to contracts
|
||||||
|
contractResponses := mappers.ModelToContractOrderIngredientTransactionResponses(responses)
|
||||||
|
return contractResponses, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *OrderIngredientTransactionService) GetOrderIngredientTransactionsByIngredient(ctx context.Context, ingredientID uuid.UUID) ([]*contract.OrderIngredientTransactionResponse, error) {
|
||||||
|
// Get organization from context
|
||||||
|
appCtx := appcontext.FromGinContext(ctx)
|
||||||
|
organizationID := appCtx.OrganizationID
|
||||||
|
|
||||||
|
// Get transactions by ingredient
|
||||||
|
responses, err := s.processor.GetOrderIngredientTransactionsByIngredient(ctx, ingredientID, organizationID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get order ingredient transactions by ingredient: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert models to contracts
|
||||||
|
contractResponses := mappers.ModelToContractOrderIngredientTransactionResponses(responses)
|
||||||
|
return contractResponses, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *OrderIngredientTransactionService) GetOrderIngredientTransactionSummary(ctx context.Context, req *contract.ListOrderIngredientTransactionsRequest) ([]*contract.OrderIngredientTransactionSummary, error) {
|
||||||
|
// Get organization from context
|
||||||
|
appCtx := appcontext.FromGinContext(ctx)
|
||||||
|
organizationID := appCtx.OrganizationID
|
||||||
|
|
||||||
|
// Convert contract to model
|
||||||
|
modelReq := mappers.ContractToModelListOrderIngredientTransactionsRequest(req)
|
||||||
|
|
||||||
|
// Get summary
|
||||||
|
summaries, err := s.processor.GetOrderIngredientTransactionSummary(ctx, modelReq, organizationID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get order ingredient transaction summary: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert models to contracts
|
||||||
|
contractSummaries := mappers.ModelToContractOrderIngredientTransactionSummaries(summaries)
|
||||||
|
return contractSummaries, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *OrderIngredientTransactionService) BulkCreateOrderIngredientTransactions(ctx context.Context, transactions []*contract.CreateOrderIngredientTransactionRequest) ([]*contract.OrderIngredientTransactionResponse, error) {
|
||||||
|
// Get organization and outlet from context
|
||||||
|
appCtx := appcontext.FromGinContext(ctx)
|
||||||
|
organizationID := appCtx.OrganizationID
|
||||||
|
outletID := appCtx.OutletID
|
||||||
|
createdBy := appCtx.UserID
|
||||||
|
|
||||||
|
// Convert contracts to models
|
||||||
|
modelReqs := make([]*models.CreateOrderIngredientTransactionRequest, len(transactions))
|
||||||
|
for i, req := range transactions {
|
||||||
|
modelReqs[i] = mappers.ContractToModelCreateOrderIngredientTransactionRequest(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bulk create transactions
|
||||||
|
responses, err := s.processor.BulkCreateOrderIngredientTransactions(ctx, modelReqs, organizationID, outletID, createdBy)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to bulk create order ingredient transactions: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert models to contracts
|
||||||
|
contractResponses := mappers.ModelToContractOrderIngredientTransactionResponses(responses)
|
||||||
|
return contractResponses, nil
|
||||||
|
}
|
||||||
@ -1,13 +1,17 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"apskel-pos-be/internal/appcontext"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"apskel-pos-be/internal/contract"
|
||||||
|
"apskel-pos-be/internal/entities"
|
||||||
"apskel-pos-be/internal/models"
|
"apskel-pos-be/internal/models"
|
||||||
"apskel-pos-be/internal/processor"
|
"apskel-pos-be/internal/processor"
|
||||||
"apskel-pos-be/internal/repository"
|
"apskel-pos-be/internal/repository"
|
||||||
|
"apskel-pos-be/internal/util"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
@ -29,12 +33,20 @@ type OrderService interface {
|
|||||||
type OrderServiceImpl struct {
|
type OrderServiceImpl struct {
|
||||||
orderProcessor processor.OrderProcessor
|
orderProcessor processor.OrderProcessor
|
||||||
tableRepo repository.TableRepositoryInterface
|
tableRepo repository.TableRepositoryInterface
|
||||||
|
orderIngredientTransactionService *OrderIngredientTransactionService
|
||||||
|
orderIngredientTransactionProcessor processor.OrderIngredientTransactionProcessor
|
||||||
|
productIngredientRepo repository.ProductIngredientRepository
|
||||||
|
txManager *repository.TxManager
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewOrderServiceImpl(orderProcessor processor.OrderProcessor, tableRepo repository.TableRepositoryInterface) *OrderServiceImpl {
|
func NewOrderServiceImpl(orderProcessor processor.OrderProcessor, tableRepo repository.TableRepositoryInterface, orderIngredientTransactionService *OrderIngredientTransactionService, orderIngredientTransactionProcessor processor.OrderIngredientTransactionProcessor, productIngredientRepo repository.ProductIngredientRepository, txManager *repository.TxManager) *OrderServiceImpl {
|
||||||
return &OrderServiceImpl{
|
return &OrderServiceImpl{
|
||||||
orderProcessor: orderProcessor,
|
orderProcessor: orderProcessor,
|
||||||
tableRepo: tableRepo,
|
tableRepo: tableRepo,
|
||||||
|
orderIngredientTransactionService: orderIngredientTransactionService,
|
||||||
|
orderIngredientTransactionProcessor: orderIngredientTransactionProcessor,
|
||||||
|
productIngredientRepo: productIngredientRepo,
|
||||||
|
txManager: txManager,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,47 +61,133 @@ func (s *OrderServiceImpl) CreateOrder(ctx context.Context, req *models.CreateOr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
response, err := s.orderProcessor.CreateOrder(ctx, req, organizationID)
|
var response *models.OrderResponse
|
||||||
|
var ingredientTransactions []*contract.CreateOrderIngredientTransactionRequest
|
||||||
|
|
||||||
|
// Use transaction to ensure atomicity
|
||||||
|
err := s.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
|
||||||
|
// Create the order
|
||||||
|
orderResp, err := s.orderProcessor.CreateOrder(txCtx, req, organizationID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create order: %w", err)
|
return fmt.Errorf("failed to create order: %w", err)
|
||||||
|
}
|
||||||
|
response = orderResp
|
||||||
|
|
||||||
|
// Create ingredient transactions for each order item
|
||||||
|
ingredientTransactions, err = s.createIngredientTransactions(txCtx, response.ID, response.OrderItems)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create ingredient transactions: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Bulk create ingredient transactions
|
||||||
|
if len(ingredientTransactions) > 0 {
|
||||||
|
_, err = s.orderIngredientTransactionService.BulkCreateOrderIngredientTransactions(txCtx, ingredientTransactions)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to bulk create ingredient transactions: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Occupy table if specified
|
||||||
if req.TableID != nil {
|
if req.TableID != nil {
|
||||||
if err := s.occupyTableWithOrder(ctx, *req.TableID, response.ID); err != nil {
|
if err := s.occupyTableWithOrder(txCtx, *req.TableID, response.ID); err != nil {
|
||||||
|
// Log warning but don't fail the transaction
|
||||||
fmt.Printf("Warning: failed to occupy table %s with order %s: %v\n", *req.TableID, response.ID, err)
|
fmt.Printf("Warning: failed to occupy table %s with order %s: %v\n", *req.TableID, response.ID, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return response, nil
|
return response, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// createIngredientTransactions creates ingredient transactions for order items efficiently
|
||||||
|
func (s *OrderServiceImpl) createIngredientTransactions(ctx context.Context, orderID uuid.UUID, orderItems []models.OrderItemResponse) ([]*contract.CreateOrderIngredientTransactionRequest, error) {
|
||||||
|
appCtx := appcontext.FromGinContext(ctx)
|
||||||
|
organizationID := appCtx.OrganizationID
|
||||||
|
var allTransactions []*contract.CreateOrderIngredientTransactionRequest
|
||||||
|
|
||||||
|
for _, orderItem := range orderItems {
|
||||||
|
// Get product ingredients for this product
|
||||||
|
productIngredients, err := s.productIngredientRepo.GetByProductID(ctx, orderItem.ProductID, organizationID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get product ingredients for product %s: %w", orderItem.ProductID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(productIngredients) == 0 {
|
||||||
|
continue // Skip if no ingredients
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate waste quantities
|
||||||
|
transactions, err := s.calculateWasteQuantities(productIngredients, float64(orderItem.Quantity))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to calculate waste quantities for product %s: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set common fields for all transactions
|
||||||
|
for _, transaction := range transactions {
|
||||||
|
transaction.OrderID = orderID
|
||||||
|
transaction.OrderItemID = &orderItem.ID
|
||||||
|
transaction.ProductID = orderItem.ProductID
|
||||||
|
transaction.ProductVariantID = orderItem.ProductVariantID
|
||||||
|
}
|
||||||
|
|
||||||
|
allTransactions = append(allTransactions, transactions...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return allTransactions, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *OrderServiceImpl) AddToOrder(ctx context.Context, orderID uuid.UUID, req *models.AddToOrderRequest) (*models.AddToOrderResponse, error) {
|
func (s *OrderServiceImpl) AddToOrder(ctx context.Context, orderID uuid.UUID, req *models.AddToOrderRequest) (*models.AddToOrderResponse, error) {
|
||||||
// Validate inputs
|
|
||||||
if orderID == uuid.Nil {
|
if orderID == uuid.Nil {
|
||||||
return nil, fmt.Errorf("invalid order ID")
|
return nil, fmt.Errorf("invalid order ID")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate request
|
|
||||||
if err := s.validateAddToOrderRequest(req); err != nil {
|
if err := s.validateAddToOrderRequest(req); err != nil {
|
||||||
return nil, fmt.Errorf("validation error: %w", err)
|
return nil, fmt.Errorf("validation error: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process adding items to order
|
var response *models.AddToOrderResponse
|
||||||
response, err := s.orderProcessor.AddToOrder(ctx, orderID, req)
|
var ingredientTransactions []*contract.CreateOrderIngredientTransactionRequest
|
||||||
|
|
||||||
|
err := s.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
|
||||||
|
addResp, err := s.orderProcessor.AddToOrder(txCtx, orderID, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to add items to order: %w", err)
|
return fmt.Errorf("failed to add items to order: %w", err)
|
||||||
|
}
|
||||||
|
response = addResp
|
||||||
|
|
||||||
|
ingredientTransactions, err = s.createIngredientTransactions(txCtx, orderID, response.AddedItems)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create ingredient transactions: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ingredientTransactions) > 0 {
|
||||||
|
_, err = s.orderIngredientTransactionService.BulkCreateOrderIngredientTransactions(txCtx, ingredientTransactions)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to bulk create ingredient transactions: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return response, nil
|
return response, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *OrderServiceImpl) UpdateOrder(ctx context.Context, id uuid.UUID, req *models.UpdateOrderRequest) (*models.OrderResponse, error) {
|
func (s *OrderServiceImpl) UpdateOrder(ctx context.Context, id uuid.UUID, req *models.UpdateOrderRequest) (*models.OrderResponse, error) {
|
||||||
// Validate request
|
|
||||||
if err := s.validateUpdateOrderRequest(req); err != nil {
|
if err := s.validateUpdateOrderRequest(req); err != nil {
|
||||||
return nil, fmt.Errorf("validation error: %w", err)
|
return nil, fmt.Errorf("validation error: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process order update
|
|
||||||
response, err := s.orderProcessor.UpdateOrder(ctx, id, req)
|
response, err := s.orderProcessor.UpdateOrder(ctx, id, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to update order: %w", err)
|
return nil, fmt.Errorf("failed to update order: %w", err)
|
||||||
@ -137,9 +235,7 @@ func (s *OrderServiceImpl) VoidOrder(ctx context.Context, req *models.VoidOrderR
|
|||||||
return fmt.Errorf("failed to void order: %w", err)
|
return fmt.Errorf("failed to void order: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Release table if order is voided
|
|
||||||
if err := s.handleTableReleaseOnVoid(ctx, req.OrderID); err != nil {
|
if err := s.handleTableReleaseOnVoid(ctx, req.OrderID); err != nil {
|
||||||
// Log the error but don't fail the void operation
|
|
||||||
fmt.Printf("Warning: failed to handle table release for voided order %s: %v\n", req.OrderID, err)
|
fmt.Printf("Warning: failed to handle table release for voided order %s: %v\n", req.OrderID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -547,3 +643,79 @@ func (s *OrderServiceImpl) handleTableReleaseOnVoid(ctx context.Context, orderID
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *OrderServiceImpl) createOrderIngredientTransactions(ctx context.Context, order *models.Order, orderItems []*models.OrderItem) error {
|
||||||
|
for _, orderItem := range orderItems {
|
||||||
|
productIngredients, err := s.productIngredientRepo.GetByProductID(ctx, orderItem.ProductID, order.OrganizationID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get product ingredients for product %s: %w", orderItem.ProductID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(productIngredients) == 0 {
|
||||||
|
continue // Skip if no ingredients
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate waste quantities using the utility function
|
||||||
|
transactions, err := s.calculateWasteQuantities(productIngredients, float64(orderItem.Quantity))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to calculate waste quantities for product %s: %w", orderItem.ProductID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set common fields for all transactions
|
||||||
|
for _, transaction := range transactions {
|
||||||
|
transaction.OrderID = order.ID
|
||||||
|
transaction.OrderItemID = &orderItem.ID
|
||||||
|
transaction.ProductID = orderItem.ProductID
|
||||||
|
transaction.ProductVariantID = orderItem.ProductVariantID
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bulk create transactions
|
||||||
|
if len(transactions) > 0 {
|
||||||
|
_, err := s.orderIngredientTransactionService.BulkCreateOrderIngredientTransactions(ctx, transactions)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create order ingredient transactions for product %s: %w", orderItem.ProductID, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculateWasteQuantities calculates gross, net, and waste quantities for product ingredients
|
||||||
|
func (s *OrderServiceImpl) calculateWasteQuantities(productIngredients []*entities.ProductIngredient, quantity float64) ([]*contract.CreateOrderIngredientTransactionRequest, error) {
|
||||||
|
if len(productIngredients) == 0 {
|
||||||
|
return []*contract.CreateOrderIngredientTransactionRequest{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
transactions := make([]*contract.CreateOrderIngredientTransactionRequest, 0, len(productIngredients))
|
||||||
|
|
||||||
|
for _, pi := range productIngredients {
|
||||||
|
// Calculate net quantity (actual quantity needed for the product)
|
||||||
|
netQty := pi.Quantity * quantity
|
||||||
|
|
||||||
|
// Calculate gross quantity (including waste)
|
||||||
|
wasteMultiplier := 1 + (pi.WastePercentage / 100)
|
||||||
|
grossQty := netQty * wasteMultiplier
|
||||||
|
|
||||||
|
// Calculate waste quantity
|
||||||
|
wasteQty := grossQty - netQty
|
||||||
|
|
||||||
|
// Get unit name from ingredient
|
||||||
|
unitName := "unit" // default
|
||||||
|
if pi.Ingredient != nil && pi.Ingredient.Unit != nil {
|
||||||
|
unitName = pi.Ingredient.Unit.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
transaction := &contract.CreateOrderIngredientTransactionRequest{
|
||||||
|
IngredientID: pi.IngredientID,
|
||||||
|
GrossQty: util.RoundToDecimalPlaces(grossQty, 3),
|
||||||
|
NetQty: util.RoundToDecimalPlaces(netQty, 3),
|
||||||
|
WasteQty: util.RoundToDecimalPlaces(wasteQty, 3),
|
||||||
|
Unit: unitName,
|
||||||
|
}
|
||||||
|
|
||||||
|
transactions = append(transactions, transaction)
|
||||||
|
}
|
||||||
|
|
||||||
|
return transactions, nil
|
||||||
|
}
|
||||||
|
|||||||
@ -152,3 +152,27 @@ func UnitModelResponseToResponse(model *models.UnitResponse) contract.UnitRespon
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IngredientUnitsModelResponseToResponse(model *models.IngredientUnitsResponse) contract.IngredientUnitsResponse {
|
||||||
|
if model == nil {
|
||||||
|
return contract.IngredientUnitsResponse{}
|
||||||
|
}
|
||||||
|
|
||||||
|
response := contract.IngredientUnitsResponse{
|
||||||
|
IngredientID: model.IngredientID,
|
||||||
|
IngredientName: model.IngredientName,
|
||||||
|
BaseUnitID: model.BaseUnitID,
|
||||||
|
BaseUnitName: model.BaseUnitName,
|
||||||
|
Units: make([]*contract.UnitResponse, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map units
|
||||||
|
for _, unit := range model.Units {
|
||||||
|
if unit != nil {
|
||||||
|
unitResp := UnitModelResponseToResponse(unit)
|
||||||
|
response.Units = append(response.Units, &unitResp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@ -8,7 +8,6 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"apskel-pos-be/internal/entities"
|
"apskel-pos-be/internal/entities"
|
||||||
"apskel-pos-be/internal/processor"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
@ -35,8 +34,8 @@ type AccountTemplate struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func CreateDefaultChartOfAccounts(ctx context.Context,
|
func CreateDefaultChartOfAccounts(ctx context.Context,
|
||||||
chartOfAccountProcessor processor.ChartOfAccountProcessor,
|
chartOfAccountProcessor interface{}, // Use interface{} to avoid import cycle
|
||||||
accountProcessor processor.AccountProcessor,
|
accountProcessor interface{}, // Use interface{} to avoid import cycle
|
||||||
organizationID uuid.UUID,
|
organizationID uuid.UUID,
|
||||||
outletID *uuid.UUID) error {
|
outletID *uuid.UUID) error {
|
||||||
|
|
||||||
@ -100,7 +99,7 @@ func loadDefaultChartOfAccountsTemplate() (*DefaultChartOfAccount, error) {
|
|||||||
return &template, nil
|
return &template, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getChartOfAccountTypeMap(ctx context.Context, chartOfAccountProcessor processor.ChartOfAccountProcessor) (map[string]uuid.UUID, error) {
|
func getChartOfAccountTypeMap(ctx context.Context, chartOfAccountProcessor interface{}) (map[string]uuid.UUID, error) {
|
||||||
// This is a placeholder - in a real implementation, you would call the processor
|
// This is a placeholder - in a real implementation, you would call the processor
|
||||||
// to get all chart of account types and create a map of code -> ID
|
// to get all chart of account types and create a map of code -> ID
|
||||||
return map[string]uuid.UUID{
|
return map[string]uuid.UUID{
|
||||||
|
|||||||
87
internal/util/waste_util.go
Normal file
87
internal/util/waste_util.go
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"apskel-pos-be/internal/entities"
|
||||||
|
"apskel-pos-be/internal/models"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CalculateWasteQuantities calculates gross, net, and waste quantities for product ingredients
|
||||||
|
func CalculateWasteQuantities(productIngredients []*entities.ProductIngredient, quantity float64) ([]*models.CreateOrderIngredientTransactionRequest, error) {
|
||||||
|
if len(productIngredients) == 0 {
|
||||||
|
return []*models.CreateOrderIngredientTransactionRequest{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
transactions := make([]*models.CreateOrderIngredientTransactionRequest, 0, len(productIngredients))
|
||||||
|
|
||||||
|
for _, pi := range productIngredients {
|
||||||
|
// Calculate net quantity (actual quantity needed for the product)
|
||||||
|
netQty := pi.Quantity * quantity
|
||||||
|
|
||||||
|
// Calculate gross quantity (including waste)
|
||||||
|
wasteMultiplier := 1 + (pi.WastePercentage / 100)
|
||||||
|
grossQty := netQty * wasteMultiplier
|
||||||
|
|
||||||
|
// Calculate waste quantity
|
||||||
|
wasteQty := grossQty - netQty
|
||||||
|
|
||||||
|
// Get unit name from ingredient
|
||||||
|
unitName := "unit" // default
|
||||||
|
if pi.Ingredient != nil && pi.Ingredient.Unit != nil {
|
||||||
|
unitName = pi.Ingredient.Unit.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
transaction := &models.CreateOrderIngredientTransactionRequest{
|
||||||
|
IngredientID: pi.IngredientID,
|
||||||
|
GrossQty: RoundToDecimalPlaces(grossQty, 3),
|
||||||
|
NetQty: RoundToDecimalPlaces(netQty, 3),
|
||||||
|
WasteQty: RoundToDecimalPlaces(wasteQty, 3),
|
||||||
|
Unit: unitName,
|
||||||
|
}
|
||||||
|
|
||||||
|
transactions = append(transactions, transaction)
|
||||||
|
}
|
||||||
|
|
||||||
|
return transactions, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateOrderIngredientTransactionsFromProduct creates order ingredient transactions for a product
|
||||||
|
func CreateOrderIngredientTransactionsFromProduct(
|
||||||
|
productID uuid.UUID,
|
||||||
|
productVariantID *uuid.UUID,
|
||||||
|
orderID uuid.UUID,
|
||||||
|
orderItemID *uuid.UUID,
|
||||||
|
quantity float64,
|
||||||
|
productIngredients []*entities.ProductIngredient,
|
||||||
|
organizationID, outletID, createdBy uuid.UUID,
|
||||||
|
) ([]*models.CreateOrderIngredientTransactionRequest, error) {
|
||||||
|
// Calculate waste quantities
|
||||||
|
wasteTransactions, err := CalculateWasteQuantities(productIngredients, quantity)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to calculate waste quantities: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set common fields for all transactions
|
||||||
|
for _, transaction := range wasteTransactions {
|
||||||
|
transaction.OrderID = orderID
|
||||||
|
transaction.OrderItemID = orderItemID
|
||||||
|
transaction.ProductID = productID
|
||||||
|
transaction.ProductVariantID = productVariantID
|
||||||
|
transaction.TransactionDate = &time.Time{}
|
||||||
|
*transaction.TransactionDate = time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
return wasteTransactions, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RoundToDecimalPlaces rounds a float64 to the specified number of decimal places
|
||||||
|
func RoundToDecimalPlaces(value float64, places int) float64 {
|
||||||
|
multiplier := 1.0
|
||||||
|
for i := 0; i < places; i++ {
|
||||||
|
multiplier *= 10
|
||||||
|
}
|
||||||
|
return float64(int(value*multiplier+0.5)) / multiplier
|
||||||
|
}
|
||||||
120
internal/validator/order_ingredient_transaction_validator.go
Normal file
120
internal/validator/order_ingredient_transaction_validator.go
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
package validator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"apskel-pos-be/internal/models"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OrderIngredientTransactionValidator interface {
|
||||||
|
ValidateCreateOrderIngredientTransactionRequest(req *models.CreateOrderIngredientTransactionRequest) error
|
||||||
|
ValidateUpdateOrderIngredientTransactionRequest(req *models.UpdateOrderIngredientTransactionRequest) error
|
||||||
|
ValidateListOrderIngredientTransactionsRequest(req *models.ListOrderIngredientTransactionsRequest) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type OrderIngredientTransactionValidatorImpl struct {
|
||||||
|
validator *validator.Validate
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewOrderIngredientTransactionValidator() OrderIngredientTransactionValidator {
|
||||||
|
v := validator.New()
|
||||||
|
return &OrderIngredientTransactionValidatorImpl{
|
||||||
|
validator: v,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *OrderIngredientTransactionValidatorImpl) ValidateCreateOrderIngredientTransactionRequest(req *models.CreateOrderIngredientTransactionRequest) error {
|
||||||
|
if err := v.validator.Struct(req); err != nil {
|
||||||
|
var validationErrors []string
|
||||||
|
for _, err := range err.(validator.ValidationErrors) {
|
||||||
|
validationErrors = append(validationErrors, fmt.Sprintf("%s: %s", err.Field(), getValidationMessage(err)))
|
||||||
|
}
|
||||||
|
return fmt.Errorf("validation failed: %s", strings.Join(validationErrors, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom validations
|
||||||
|
if req.GrossQty < req.NetQty {
|
||||||
|
return fmt.Errorf("gross quantity must be greater than or equal to net quantity")
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedWasteQty := req.GrossQty - req.NetQty
|
||||||
|
if req.WasteQty != expectedWasteQty {
|
||||||
|
return fmt.Errorf("waste quantity must equal gross quantity minus net quantity")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *OrderIngredientTransactionValidatorImpl) ValidateUpdateOrderIngredientTransactionRequest(req *models.UpdateOrderIngredientTransactionRequest) error {
|
||||||
|
if err := v.validator.Struct(req); err != nil {
|
||||||
|
var validationErrors []string
|
||||||
|
for _, err := range err.(validator.ValidationErrors) {
|
||||||
|
validationErrors = append(validationErrors, fmt.Sprintf("%s: %s", err.Field(), getValidationMessage(err)))
|
||||||
|
}
|
||||||
|
return fmt.Errorf("validation failed: %s", strings.Join(validationErrors, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom validations for partial updates
|
||||||
|
if req.GrossQty != nil && req.NetQty != nil {
|
||||||
|
if *req.GrossQty < *req.NetQty {
|
||||||
|
return fmt.Errorf("gross quantity must be greater than or equal to net quantity")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.GrossQty != nil && req.NetQty != nil && req.WasteQty != nil {
|
||||||
|
expectedWasteQty := *req.GrossQty - *req.NetQty
|
||||||
|
if *req.WasteQty != expectedWasteQty {
|
||||||
|
return fmt.Errorf("waste quantity must equal gross quantity minus net quantity")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *OrderIngredientTransactionValidatorImpl) ValidateListOrderIngredientTransactionsRequest(req *models.ListOrderIngredientTransactionsRequest) error {
|
||||||
|
if err := v.validator.Struct(req); err != nil {
|
||||||
|
var validationErrors []string
|
||||||
|
for _, err := range err.(validator.ValidationErrors) {
|
||||||
|
validationErrors = append(validationErrors, fmt.Sprintf("%s: %s", err.Field(), getValidationMessage(err)))
|
||||||
|
}
|
||||||
|
return fmt.Errorf("validation failed: %s", strings.Join(validationErrors, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom validations
|
||||||
|
if req.StartDate != nil && req.EndDate != nil {
|
||||||
|
if req.StartDate.After(*req.EndDate) {
|
||||||
|
return fmt.Errorf("start date must be before end date")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getValidationMessage(err validator.FieldError) string {
|
||||||
|
switch err.Tag() {
|
||||||
|
case "required":
|
||||||
|
return "is required"
|
||||||
|
case "min":
|
||||||
|
return fmt.Sprintf("must be at least %s", err.Param())
|
||||||
|
case "max":
|
||||||
|
return fmt.Sprintf("must be at most %s", err.Param())
|
||||||
|
case "gt":
|
||||||
|
return fmt.Sprintf("must be greater than %s", err.Param())
|
||||||
|
case "gte":
|
||||||
|
return fmt.Sprintf("must be greater than or equal to %s", err.Param())
|
||||||
|
case "lt":
|
||||||
|
return fmt.Sprintf("must be less than %s", err.Param())
|
||||||
|
case "lte":
|
||||||
|
return fmt.Sprintf("must be less than or equal to %s", err.Param())
|
||||||
|
case "email":
|
||||||
|
return "must be a valid email address"
|
||||||
|
case "uuid":
|
||||||
|
return "must be a valid UUID"
|
||||||
|
case "len":
|
||||||
|
return fmt.Sprintf("must be exactly %s characters long", err.Param())
|
||||||
|
default:
|
||||||
|
return "is invalid"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
-- Remove waste_percentage column from product_ingredients table
|
||||||
|
ALTER TABLE product_ingredients DROP COLUMN waste_percentage;
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
-- Add waste_percentage column to product_ingredients table
|
||||||
|
ALTER TABLE product_ingredients
|
||||||
|
ADD COLUMN waste_percentage DECIMAL(5,2) DEFAULT 0.00 CHECK (waste_percentage >= 0 AND waste_percentage <= 100);
|
||||||
|
|
||||||
|
-- Add comment to explain the column
|
||||||
|
COMMENT ON COLUMN product_ingredients.waste_percentage IS 'Waste percentage for this ingredient (0-100). Used to calculate gross quantity needed including waste.';
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
-- Drop order ingredients transactions table
|
||||||
|
DROP TABLE IF EXISTS order_ingredients_transactions;
|
||||||
@ -0,0 +1,37 @@
|
|||||||
|
-- Order ingredients transactions table
|
||||||
|
CREATE TABLE order_ingredients_transactions (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
organization_id UUID NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
|
||||||
|
outlet_id UUID REFERENCES outlets(id) ON DELETE CASCADE,
|
||||||
|
order_id UUID NOT NULL REFERENCES orders(id) ON DELETE CASCADE,
|
||||||
|
order_item_id UUID REFERENCES order_items(id) ON DELETE CASCADE,
|
||||||
|
product_id UUID NOT NULL REFERENCES products(id) ON DELETE CASCADE,
|
||||||
|
product_variant_id UUID REFERENCES product_variants(id) ON DELETE CASCADE,
|
||||||
|
ingredient_id UUID NOT NULL REFERENCES ingredients(id) ON DELETE CASCADE,
|
||||||
|
gross_qty DECIMAL(12,3) NOT NULL CHECK (gross_qty > 0),
|
||||||
|
net_qty DECIMAL(12,3) NOT NULL CHECK (net_qty > 0),
|
||||||
|
waste_qty DECIMAL(12,3) NOT NULL CHECK (waste_qty >= 0),
|
||||||
|
unit VARCHAR(50) NOT NULL,
|
||||||
|
transaction_date TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||||
|
created_by UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Indexes
|
||||||
|
CREATE INDEX idx_order_ingredients_transactions_organization_id ON order_ingredients_transactions(organization_id);
|
||||||
|
CREATE INDEX idx_order_ingredients_transactions_outlet_id ON order_ingredients_transactions(outlet_id);
|
||||||
|
CREATE INDEX idx_order_ingredients_transactions_order_id ON order_ingredients_transactions(order_id);
|
||||||
|
CREATE INDEX idx_order_ingredients_transactions_order_item_id ON order_ingredients_transactions(order_item_id);
|
||||||
|
CREATE INDEX idx_order_ingredients_transactions_product_id ON order_ingredients_transactions(product_id);
|
||||||
|
CREATE INDEX idx_order_ingredients_transactions_product_variant_id ON order_ingredients_transactions(product_variant_id);
|
||||||
|
CREATE INDEX idx_order_ingredients_transactions_ingredient_id ON order_ingredients_transactions(ingredient_id);
|
||||||
|
CREATE INDEX idx_order_ingredients_transactions_transaction_date ON order_ingredients_transactions(transaction_date);
|
||||||
|
CREATE INDEX idx_order_ingredients_transactions_created_by ON order_ingredients_transactions(created_by);
|
||||||
|
CREATE INDEX idx_order_ingredients_transactions_created_at ON order_ingredients_transactions(created_at);
|
||||||
|
|
||||||
|
-- Add comment to explain the table
|
||||||
|
COMMENT ON TABLE order_ingredients_transactions IS 'Tracks ingredient usage for orders including gross, net, and waste quantities';
|
||||||
|
COMMENT ON COLUMN order_ingredients_transactions.gross_qty IS 'Total quantity needed including waste';
|
||||||
|
COMMENT ON COLUMN order_ingredients_transactions.net_qty IS 'Actual quantity used in the product';
|
||||||
|
COMMENT ON COLUMN order_ingredients_transactions.waste_qty IS 'Waste quantity (gross_qty - net_qty)';
|
||||||
Loading…
x
Reference in New Issue
Block a user