self-order+notification #5

Merged
aefril merged 26 commits from self-order+notification into main 2026-05-12 11:41:03 +00:00
3 changed files with 87 additions and 4 deletions
Showing only changes of commit f73a5d533c - Show all commits

View File

@ -409,7 +409,7 @@ func (a *App) initServices(processors *processors, repos *repositories, cfg *con
productService := service.NewProductService(processors.productProcessor)
productVariantService := service.NewProductVariantService(processors.productVariantProcessor)
inventoryService := service.NewInventoryService(processors.inventoryProcessor)
orderService := service.NewOrderServiceImpl(processors.orderProcessor, repos.tableRepo, nil, processors.orderIngredientTransactionProcessor, *repos.productRecipeRepo, repos.txManager, repos.sessionRepo) // Will be updated after orderIngredientTransactionService is created
orderService := service.NewOrderServiceImpl(processors.orderProcessor, repos.tableRepo, nil, processors.orderIngredientTransactionProcessor, *repos.productRecipeRepo, repos.txManager, repos.sessionRepo, processors.notificationProcessor, repos.userRepo) // Will be updated after orderIngredientTransactionService is created
paymentMethodService := service.NewPaymentMethodService(processors.paymentMethodProcessor)
fileService := service.NewFileServiceImpl(processors.fileProcessor)
var customerService service.CustomerService = service.NewCustomerService(processors.customerProcessor)
@ -436,7 +436,7 @@ func (a *App) initServices(processors *processors, repos *repositories, cfg *con
notificationService := service.NewNotificationService(processors.notificationProcessor)
// Update order service with order ingredient transaction service
orderService = service.NewOrderServiceImpl(processors.orderProcessor, repos.tableRepo, orderIngredientTransactionService, processors.orderIngredientTransactionProcessor, *repos.productRecipeRepo, repos.txManager, repos.sessionRepo)
orderService = service.NewOrderServiceImpl(processors.orderProcessor, repos.tableRepo, orderIngredientTransactionService, processors.orderIngredientTransactionProcessor, *repos.productRecipeRepo, repos.txManager, repos.sessionRepo, processors.notificationProcessor, repos.userRepo)
return &services{
userService: service.NewUserService(processors.userProcessor),

View File

@ -61,6 +61,17 @@ func (r *UserRepositoryImpl) GetActiveUsers(ctx context.Context, organizationID
return users, err
}
func (r *UserRepositoryImpl) GetActiveByOutletID(ctx context.Context, organizationID, outletID uuid.UUID) ([]*entities.User, error) {
var users []*entities.User
err := r.db.WithContext(ctx).
Where(
"organization_id = ? AND is_active = ? AND (outlet_id = ? OR role IN ?)",
organizationID, true, outletID, []string{"admin", "manager"},
).
Find(&users).Error
return users, err
}
func (r *UserRepositoryImpl) Update(ctx context.Context, user *entities.User) error {
return r.db.WithContext(ctx).Save(user).Error
}

View File

@ -16,6 +16,11 @@ import (
"github.com/google/uuid"
)
// orderUserRepository is a minimal interface to fetch users by organization for notification purposes.
type orderUserRepository interface {
GetActiveByOutletID(ctx context.Context, organizationID, outletID uuid.UUID) ([]*entities.User, error)
}
type OrderService interface {
CreateOrder(ctx context.Context, req *models.CreateOrderRequest, organizationID uuid.UUID) (*models.OrderResponse, error)
AddToOrder(ctx context.Context, orderID uuid.UUID, req *models.AddToOrderRequest) (*models.AddToOrderResponse, error)
@ -38,9 +43,11 @@ type OrderServiceImpl struct {
productRecipeRepo repository.ProductRecipeRepository
txManager *repository.TxManager
sessionRepo repository.SessionRepository
notificationProcessor processor.NotificationProcessor
userRepo orderUserRepository
}
func NewOrderServiceImpl(orderProcessor processor.OrderProcessor, tableRepo repository.TableRepositoryInterface, orderIngredientTransactionService *OrderIngredientTransactionService, orderIngredientTransactionProcessor processor.OrderIngredientTransactionProcessor, productRecipeRepo repository.ProductRecipeRepository, txManager *repository.TxManager, sessionRepo repository.SessionRepository) *OrderServiceImpl {
func NewOrderServiceImpl(orderProcessor processor.OrderProcessor, tableRepo repository.TableRepositoryInterface, orderIngredientTransactionService *OrderIngredientTransactionService, orderIngredientTransactionProcessor processor.OrderIngredientTransactionProcessor, productRecipeRepo repository.ProductRecipeRepository, txManager *repository.TxManager, sessionRepo repository.SessionRepository, notificationProcessor processor.NotificationProcessor, userRepo orderUserRepository) *OrderServiceImpl {
return &OrderServiceImpl{
orderProcessor: orderProcessor,
tableRepo: tableRepo,
@ -49,6 +56,8 @@ func NewOrderServiceImpl(orderProcessor processor.OrderProcessor, tableRepo repo
productRecipeRepo: productRecipeRepo,
txManager: txManager,
sessionRepo: sessionRepo,
notificationProcessor: notificationProcessor,
userRepo: userRepo,
}
}
@ -104,10 +113,73 @@ func (s *OrderServiceImpl) CreateOrder(ctx context.Context, req *models.CreateOr
return nil, err
}
// Send notification to all org users if this is a self-order
if isSelfOrder(req.Metadata) {
go s.sendSelfOrderNotification(context.Background(), response, organizationID)
}
return response, nil
}
// createIngredientTransactions creates ingredient transactions for order items efficiently
// isSelfOrder checks if the order metadata indicates a self-order.
func isSelfOrder(metadata map[string]interface{}) bool {
if metadata == nil {
return false
}
v, ok := metadata["self_order"]
if !ok {
return false
}
b, ok := v.(bool)
return ok && b
}
// sendSelfOrderNotification sends a new-order notification to all active users
// that can access the outlet where the self-order was placed.
func (s *OrderServiceImpl) sendSelfOrderNotification(ctx context.Context, order *models.OrderResponse, organizationID uuid.UUID) {
if s.notificationProcessor == nil || s.userRepo == nil {
return
}
users, err := s.userRepo.GetActiveByOutletID(ctx, organizationID, order.OutletID)
if err != nil || len(users) == 0 {
return
}
receiverIDs := make([]uuid.UUID, 0, len(users))
for _, u := range users {
receiverIDs = append(receiverIDs, u.ID)
}
tableName := ""
if order.TableNumber != nil {
tableName = *order.TableNumber
}
title := "Pesanan Baru Masuk"
body := fmt.Sprintf("Ada pesanan baru dari meja %s", tableName)
if tableName == "" {
body = "Ada pesanan baru masuk"
}
orderID := order.ID
notifReq := &models.SendNotificationRequest{
Title: title,
Body: body,
Type: "order",
Category: "self_order",
NotifiableType: "order",
NotifiableID: &orderID,
ReceiverIDs: receiverIDs,
Data: map[string]interface{}{
"order_id": order.ID.String(),
"order_number": order.OrderNumber,
"table_name": tableName,
},
}
_, _ = s.notificationProcessor.Send(ctx, notifReq)
}
func (s *OrderServiceImpl) createIngredientTransactions(ctx context.Context, orderID uuid.UUID, orderItems []models.OrderItemResponse) ([]*contract.CreateOrderIngredientTransactionRequest, error) {
appCtx := appcontext.FromGinContext(ctx)
organizationID := appCtx.OrganizationID