394 lines
16 KiB
Go
394 lines
16 KiB
Go
|
|
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
|
||
|
|
}
|