Revert "voucher"
This reverts commit cfe690a40f569e492e16605cbd3321f9ad142247.
This commit is contained in:
parent
36c2352cb2
commit
12ee54390f
@ -94,8 +94,6 @@ func (a *App) Initialize(cfg *config.Config) error {
|
|||||||
validators.accountValidator,
|
validators.accountValidator,
|
||||||
*services.orderIngredientTransactionService,
|
*services.orderIngredientTransactionService,
|
||||||
validators.orderIngredientTransactionValidator,
|
validators.orderIngredientTransactionValidator,
|
||||||
services.voucherService,
|
|
||||||
validators.voucherValidator,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -169,7 +167,6 @@ type repositories struct {
|
|||||||
chartOfAccountRepo *repository.ChartOfAccountRepositoryImpl
|
chartOfAccountRepo *repository.ChartOfAccountRepositoryImpl
|
||||||
accountRepo *repository.AccountRepositoryImpl
|
accountRepo *repository.AccountRepositoryImpl
|
||||||
orderIngredientTransactionRepo *repository.OrderIngredientTransactionRepositoryImpl
|
orderIngredientTransactionRepo *repository.OrderIngredientTransactionRepositoryImpl
|
||||||
voucherRepo *repository.VoucherRepository
|
|
||||||
txManager *repository.TxManager
|
txManager *repository.TxManager
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -203,8 +200,7 @@ func (a *App) initRepositories() *repositories {
|
|||||||
chartOfAccountRepo: repository.NewChartOfAccountRepositoryImpl(a.db),
|
chartOfAccountRepo: repository.NewChartOfAccountRepositoryImpl(a.db),
|
||||||
accountRepo: repository.NewAccountRepositoryImpl(a.db),
|
accountRepo: repository.NewAccountRepositoryImpl(a.db),
|
||||||
orderIngredientTransactionRepo: repository.NewOrderIngredientTransactionRepositoryImpl(a.db).(*repository.OrderIngredientTransactionRepositoryImpl),
|
orderIngredientTransactionRepo: repository.NewOrderIngredientTransactionRepositoryImpl(a.db).(*repository.OrderIngredientTransactionRepositoryImpl),
|
||||||
voucherRepo: repository.NewVoucherRepository(a.db),
|
txManager: repository.NewTxManager(a.db),
|
||||||
txManager: repository.NewTxManager(a.db),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -233,7 +229,6 @@ type processors struct {
|
|||||||
chartOfAccountProcessor *processor.ChartOfAccountProcessorImpl
|
chartOfAccountProcessor *processor.ChartOfAccountProcessorImpl
|
||||||
accountProcessor *processor.AccountProcessorImpl
|
accountProcessor *processor.AccountProcessorImpl
|
||||||
orderIngredientTransactionProcessor *processor.OrderIngredientTransactionProcessorImpl
|
orderIngredientTransactionProcessor *processor.OrderIngredientTransactionProcessorImpl
|
||||||
voucherProcessor *processor.VoucherProcessor
|
|
||||||
fileClient processor.FileClient
|
fileClient processor.FileClient
|
||||||
inventoryMovementService service.InventoryMovementService
|
inventoryMovementService service.InventoryMovementService
|
||||||
}
|
}
|
||||||
@ -267,7 +262,6 @@ func (a *App) initProcessors(cfg *config.Config, repos *repositories) *processor
|
|||||||
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.productRecipeRepo, repos.ingredientRepo, repos.unitRepo).(*processor.OrderIngredientTransactionProcessorImpl),
|
orderIngredientTransactionProcessor: processor.NewOrderIngredientTransactionProcessorImpl(repos.orderIngredientTransactionRepo, repos.productRecipeRepo, repos.ingredientRepo, repos.unitRepo).(*processor.OrderIngredientTransactionProcessorImpl),
|
||||||
voucherProcessor: processor.NewVoucherProcessor(repos.voucherRepo),
|
|
||||||
fileClient: fileClient,
|
fileClient: fileClient,
|
||||||
inventoryMovementService: inventoryMovementService,
|
inventoryMovementService: inventoryMovementService,
|
||||||
}
|
}
|
||||||
@ -300,7 +294,6 @@ type services struct {
|
|||||||
chartOfAccountService service.ChartOfAccountService
|
chartOfAccountService service.ChartOfAccountService
|
||||||
accountService service.AccountService
|
accountService service.AccountService
|
||||||
orderIngredientTransactionService *service.OrderIngredientTransactionService
|
orderIngredientTransactionService *service.OrderIngredientTransactionService
|
||||||
voucherService service.VoucherService
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) initServices(processors *processors, repos *repositories, cfg *config.Config) *services {
|
func (a *App) initServices(processors *processors, repos *repositories, cfg *config.Config) *services {
|
||||||
@ -331,7 +324,6 @@ func (a *App) initServices(processors *processors, repos *repositories, cfg *con
|
|||||||
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)
|
orderIngredientTransactionService := service.NewOrderIngredientTransactionService(processors.orderIngredientTransactionProcessor, repos.txManager)
|
||||||
voucherService := service.NewVoucherService(processors.voucherProcessor)
|
|
||||||
|
|
||||||
// Update order service with order ingredient transaction service
|
// Update order service with order ingredient transaction service
|
||||||
orderService = service.NewOrderServiceImpl(processors.orderProcessor, repos.tableRepo, orderIngredientTransactionService, processors.orderIngredientTransactionProcessor, *repos.productRecipeRepo, repos.txManager)
|
orderService = service.NewOrderServiceImpl(processors.orderProcessor, repos.tableRepo, orderIngredientTransactionService, processors.orderIngredientTransactionProcessor, *repos.productRecipeRepo, repos.txManager)
|
||||||
@ -363,7 +355,6 @@ func (a *App) initServices(processors *processors, repos *repositories, cfg *con
|
|||||||
chartOfAccountService: chartOfAccountService,
|
chartOfAccountService: chartOfAccountService,
|
||||||
accountService: accountService,
|
accountService: accountService,
|
||||||
orderIngredientTransactionService: orderIngredientTransactionService,
|
orderIngredientTransactionService: orderIngredientTransactionService,
|
||||||
voucherService: voucherService,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -397,7 +388,6 @@ type validators struct {
|
|||||||
chartOfAccountValidator *validator.ChartOfAccountValidatorImpl
|
chartOfAccountValidator *validator.ChartOfAccountValidatorImpl
|
||||||
accountValidator *validator.AccountValidatorImpl
|
accountValidator *validator.AccountValidatorImpl
|
||||||
orderIngredientTransactionValidator *validator.OrderIngredientTransactionValidatorImpl
|
orderIngredientTransactionValidator *validator.OrderIngredientTransactionValidatorImpl
|
||||||
voucherValidator validator.VoucherValidator
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) initValidators() *validators {
|
func (a *App) initValidators() *validators {
|
||||||
@ -421,6 +411,5 @@ func (a *App) initValidators() *validators {
|
|||||||
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),
|
orderIngredientTransactionValidator: validator.NewOrderIngredientTransactionValidator().(*validator.OrderIngredientTransactionValidatorImpl),
|
||||||
voucherValidator: validator.NewVoucherValidator(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,51 +0,0 @@
|
|||||||
package contract
|
|
||||||
|
|
||||||
type VoucherResponse struct {
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Email string `json:"email"`
|
|
||||||
PhoneNumber string `json:"phone_number"`
|
|
||||||
VoucherCode string `json:"voucher_code"`
|
|
||||||
WinnerNumber int `json:"winner_number"`
|
|
||||||
IsWinner bool `json:"is_winner"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type VoucherSpinResponse struct {
|
|
||||||
VoucherCode string `json:"voucher_code"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
PhoneNumber string `json:"phone_number"` // This will be masked
|
|
||||||
IsWinner bool `json:"is_winner"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ListVouchersForSpinRequest struct {
|
|
||||||
Limit int `json:"limit" validate:"min=1,max=50"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ListVouchersForSpinResponse struct {
|
|
||||||
Vouchers []VoucherSpinResponse `json:"vouchers"`
|
|
||||||
Count int `json:"count"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type PaginatedVoucherResponse struct {
|
|
||||||
Data []VoucherResponse `json:"data"`
|
|
||||||
TotalCount int `json:"total_count"`
|
|
||||||
Page int `json:"page"`
|
|
||||||
Limit int `json:"limit"`
|
|
||||||
TotalPages int `json:"total_pages"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type VoucherRow struct {
|
|
||||||
RowNumber int `json:"row_number"`
|
|
||||||
Vouchers []VoucherSpinResponse `json:"vouchers"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ListVouchersByRowsRequest struct {
|
|
||||||
Rows int `json:"rows" validate:"min=1,max=10"`
|
|
||||||
WinnerNumber *int `json:"winner_number,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ListVouchersByRowsResponse struct {
|
|
||||||
Rows []VoucherRow `json:"rows"`
|
|
||||||
TotalRows int `json:"total_rows"`
|
|
||||||
TotalVouchers int `json:"total_vouchers"`
|
|
||||||
}
|
|
||||||
@ -23,7 +23,6 @@ func GetAllEntities() []interface{} {
|
|||||||
&PurchaseOrderItem{},
|
&PurchaseOrderItem{},
|
||||||
&PurchaseOrderAttachment{},
|
&PurchaseOrderAttachment{},
|
||||||
&IngredientUnitConverter{},
|
&IngredientUnitConverter{},
|
||||||
&Voucher{},
|
|
||||||
// Analytics entities are not database tables, they are query results
|
// Analytics entities are not database tables, they are query results
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,15 +0,0 @@
|
|||||||
package entities
|
|
||||||
|
|
||||||
type Voucher struct {
|
|
||||||
ID int64 `gorm:"type:bigserial;primary_key;autoIncrement" json:"id"`
|
|
||||||
Name string `gorm:"not null;size:100" json:"name" validate:"required"`
|
|
||||||
Email string `gorm:"not null;size:255" json:"email" validate:"required"`
|
|
||||||
PhoneNumber string `gorm:"not null;size:20" json:"phone_number" validate:"required"`
|
|
||||||
VoucherCode string `gorm:"not null;size:50" json:"voucher_code" validate:"required"`
|
|
||||||
WinnerNumber int `gorm:"not null;default:0" json:"winner_number" validate:"required"`
|
|
||||||
IsWinner bool `gorm:"not null;default:false" json:"is_winner"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (Voucher) TableName() string {
|
|
||||||
return "vouchers"
|
|
||||||
}
|
|
||||||
@ -1,177 +0,0 @@
|
|||||||
package handler
|
|
||||||
|
|
||||||
import (
|
|
||||||
"apskel-pos-be/internal/constants"
|
|
||||||
"apskel-pos-be/internal/contract"
|
|
||||||
"apskel-pos-be/internal/logger"
|
|
||||||
"apskel-pos-be/internal/service"
|
|
||||||
"apskel-pos-be/internal/util"
|
|
||||||
"apskel-pos-be/internal/validator"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
)
|
|
||||||
|
|
||||||
type VoucherHandler struct {
|
|
||||||
voucherService service.VoucherService
|
|
||||||
voucherValidator validator.VoucherValidator
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewVoucherHandler(
|
|
||||||
voucherService service.VoucherService,
|
|
||||||
voucherValidator validator.VoucherValidator,
|
|
||||||
) *VoucherHandler {
|
|
||||||
return &VoucherHandler{
|
|
||||||
voucherService: voucherService,
|
|
||||||
voucherValidator: voucherValidator,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *VoucherHandler) GetRandomVouchersForSpin(c *gin.Context) {
|
|
||||||
ctx := c.Request.Context()
|
|
||||||
|
|
||||||
req := &contract.ListVouchersForSpinRequest{
|
|
||||||
Limit: 10, // Default limit
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse query parameters
|
|
||||||
if limitStr := c.Query("limit"); limitStr != "" {
|
|
||||||
if limit, err := strconv.Atoi(limitStr); err == nil {
|
|
||||||
req.Limit = limit
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
validationError, validationErrorCode := h.voucherValidator.ValidateListVouchersForSpinRequest(req)
|
|
||||||
if validationError != nil {
|
|
||||||
logger.FromContext(ctx).WithError(validationError).Error("VoucherHandler::GetRandomVouchersForSpin -> request validation failed")
|
|
||||||
validationResponseError := contract.NewResponseError(validationErrorCode, constants.RequestEntity, validationError.Error())
|
|
||||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "VoucherHandler::GetRandomVouchersForSpin")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
vouchersResponse, err := h.voucherService.GetRandomVouchersForSpin(c.Request.Context(), req)
|
|
||||||
if err != nil {
|
|
||||||
logger.FromContext(ctx).WithError(err).Error("VoucherHandler::GetRandomVouchersForSpin -> Failed to get random vouchers from service")
|
|
||||||
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.RequestEntity, err.Error())
|
|
||||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{errorResp}), "VoucherHandler::GetRandomVouchersForSpin")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.FromContext(ctx).Infof("VoucherHandler::GetRandomVouchersForSpin -> Successfully retrieved %d vouchers for spin", vouchersResponse.Count)
|
|
||||||
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(vouchersResponse), "VoucherHandler::GetRandomVouchersForSpin")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *VoucherHandler) GetRandomVouchersByRows(c *gin.Context) {
|
|
||||||
ctx := c.Request.Context()
|
|
||||||
|
|
||||||
req := &contract.ListVouchersByRowsRequest{
|
|
||||||
Rows: 4, // Default 4 rows
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse query parameters
|
|
||||||
if rowsStr := c.Query("rows"); rowsStr != "" {
|
|
||||||
if rows, err := strconv.Atoi(rowsStr); err == nil {
|
|
||||||
req.Rows = rows
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse winner_number parameter (optional)
|
|
||||||
if winnerNumberStr := c.Query("winner_number"); winnerNumberStr != "" {
|
|
||||||
if wn, err := strconv.Atoi(winnerNumberStr); err == nil {
|
|
||||||
req.WinnerNumber = &wn
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
validationError, validationErrorCode := h.voucherValidator.ValidateListVouchersByRowsRequest(req)
|
|
||||||
if validationError != nil {
|
|
||||||
logger.FromContext(ctx).WithError(validationError).Error("VoucherHandler::GetRandomVouchersByRows -> request validation failed")
|
|
||||||
validationResponseError := contract.NewResponseError(validationErrorCode, constants.RequestEntity, validationError.Error())
|
|
||||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "VoucherHandler::GetRandomVouchersByRows")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
vouchersResponse, err := h.voucherService.GetRandomVouchersByRows(c.Request.Context(), req)
|
|
||||||
if err != nil {
|
|
||||||
logger.FromContext(ctx).WithError(err).Error("VoucherHandler::GetRandomVouchersByRows -> Failed to get random vouchers by rows from service")
|
|
||||||
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.RequestEntity, err.Error())
|
|
||||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{errorResp}), "VoucherHandler::GetRandomVouchersByRows")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.FromContext(ctx).Infof("VoucherHandler::GetRandomVouchersByRows -> Successfully retrieved %d rows with %d total vouchers", vouchersResponse.TotalRows, vouchersResponse.TotalVouchers)
|
|
||||||
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(vouchersResponse), "VoucherHandler::GetRandomVouchersByRows")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *VoucherHandler) GetVoucherByID(c *gin.Context) {
|
|
||||||
ctx := c.Request.Context()
|
|
||||||
|
|
||||||
voucherID, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
logger.FromContext(ctx).WithError(err).Error("VoucherHandler::GetVoucherByID -> Invalid voucher ID")
|
|
||||||
validationResponseError := contract.NewResponseError(constants.MalformedFieldErrorCode, constants.RequestEntity, "Invalid voucher ID")
|
|
||||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "VoucherHandler::GetVoucherByID")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
voucherResponse, err := h.voucherService.GetVoucherByID(c.Request.Context(), voucherID)
|
|
||||||
if err != nil {
|
|
||||||
logger.FromContext(ctx).WithError(err).Error("VoucherHandler::GetVoucherByID -> Failed to get voucher from service")
|
|
||||||
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.RequestEntity, err.Error())
|
|
||||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{errorResp}), "VoucherHandler::GetVoucherByID")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(voucherResponse), "VoucherHandler::GetVoucherByID")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *VoucherHandler) GetVoucherByCode(c *gin.Context) {
|
|
||||||
ctx := c.Request.Context()
|
|
||||||
|
|
||||||
voucherCode := c.Param("code")
|
|
||||||
if voucherCode == "" {
|
|
||||||
logger.FromContext(ctx).Error("VoucherHandler::GetVoucherByCode -> Missing voucher code")
|
|
||||||
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, "Voucher code is required")
|
|
||||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "VoucherHandler::GetVoucherByCode")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
voucherResponse, err := h.voucherService.GetVoucherByCode(c.Request.Context(), voucherCode)
|
|
||||||
if err != nil {
|
|
||||||
logger.FromContext(ctx).WithError(err).Error("VoucherHandler::GetVoucherByCode -> Failed to get voucher from service")
|
|
||||||
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.RequestEntity, err.Error())
|
|
||||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{errorResp}), "VoucherHandler::GetVoucherByCode")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(voucherResponse), "VoucherHandler::GetVoucherByCode")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *VoucherHandler) ListVouchers(c *gin.Context) {
|
|
||||||
ctx := c.Request.Context()
|
|
||||||
|
|
||||||
page := 1
|
|
||||||
limit := 10
|
|
||||||
|
|
||||||
// Parse query parameters
|
|
||||||
if pageStr := c.Query("page"); pageStr != "" {
|
|
||||||
if p, err := strconv.Atoi(pageStr); err == nil && p > 0 {
|
|
||||||
page = p
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if limitStr := c.Query("limit"); limitStr != "" {
|
|
||||||
if l, err := strconv.Atoi(limitStr); err == nil && l > 0 && l <= 100 {
|
|
||||||
limit = l
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
vouchersResponse, err := h.voucherService.ListVouchers(c.Request.Context(), page, limit)
|
|
||||||
if err != nil {
|
|
||||||
logger.FromContext(ctx).WithError(err).Error("VoucherHandler::ListVouchers -> Failed to list vouchers from service")
|
|
||||||
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.RequestEntity, err.Error())
|
|
||||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{errorResp}), "VoucherHandler::ListVouchers")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(vouchersResponse), "VoucherHandler::ListVouchers")
|
|
||||||
}
|
|
||||||
@ -1,120 +0,0 @@
|
|||||||
package mappers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"apskel-pos-be/internal/contract"
|
|
||||||
"apskel-pos-be/internal/entities"
|
|
||||||
"apskel-pos-be/internal/models"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func VoucherEntityToResponse(voucher *entities.Voucher) *models.VoucherResponse {
|
|
||||||
return &models.VoucherResponse{
|
|
||||||
ID: voucher.ID,
|
|
||||||
Name: voucher.Name,
|
|
||||||
Email: voucher.Email,
|
|
||||||
PhoneNumber: voucher.PhoneNumber,
|
|
||||||
VoucherCode: voucher.VoucherCode,
|
|
||||||
WinnerNumber: voucher.WinnerNumber,
|
|
||||||
IsWinner: voucher.IsWinner,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func VoucherModelToContract(voucher *models.VoucherResponse) *contract.VoucherResponse {
|
|
||||||
return &contract.VoucherResponse{
|
|
||||||
ID: voucher.ID,
|
|
||||||
Name: voucher.Name,
|
|
||||||
Email: voucher.Email,
|
|
||||||
PhoneNumber: voucher.PhoneNumber,
|
|
||||||
VoucherCode: voucher.VoucherCode,
|
|
||||||
WinnerNumber: voucher.WinnerNumber,
|
|
||||||
IsWinner: voucher.IsWinner,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func VoucherEntityToSpinResponse(voucher *entities.Voucher) *models.VoucherSpinResponse {
|
|
||||||
maskedPhone := maskPhoneNumber(&voucher.PhoneNumber)
|
|
||||||
return &models.VoucherSpinResponse{
|
|
||||||
VoucherCode: voucher.VoucherCode,
|
|
||||||
Name: voucher.Name,
|
|
||||||
PhoneNumber: maskedPhone,
|
|
||||||
IsWinner: voucher.IsWinner,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func VoucherSpinModelToContract(voucher *models.VoucherSpinResponse) *contract.VoucherSpinResponse {
|
|
||||||
return &contract.VoucherSpinResponse{
|
|
||||||
VoucherCode: voucher.VoucherCode,
|
|
||||||
Name: voucher.Name,
|
|
||||||
PhoneNumber: voucher.PhoneNumber,
|
|
||||||
IsWinner: voucher.IsWinner,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ListVouchersForSpinRequestToModel(req *contract.ListVouchersForSpinRequest) *models.ListVouchersForSpinRequest {
|
|
||||||
return &models.ListVouchersForSpinRequest{
|
|
||||||
Limit: req.Limit,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ListVouchersForSpinResponseToContract(resp *models.ListVouchersForSpinResponse) *contract.ListVouchersForSpinResponse {
|
|
||||||
vouchers := make([]contract.VoucherSpinResponse, len(resp.Vouchers))
|
|
||||||
for i, voucher := range resp.Vouchers {
|
|
||||||
vouchers[i] = *VoucherSpinModelToContract(&voucher)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &contract.ListVouchersForSpinResponse{
|
|
||||||
Vouchers: vouchers,
|
|
||||||
Count: resp.Count,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func VoucherRowModelToContract(row *models.VoucherRow) *contract.VoucherRow {
|
|
||||||
vouchers := make([]contract.VoucherSpinResponse, len(row.Vouchers))
|
|
||||||
for i, voucher := range row.Vouchers {
|
|
||||||
vouchers[i] = *VoucherSpinModelToContract(&voucher)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &contract.VoucherRow{
|
|
||||||
RowNumber: row.RowNumber,
|
|
||||||
Vouchers: vouchers,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ListVouchersByRowsRequestToModel(req *contract.ListVouchersByRowsRequest) *models.ListVouchersByRowsRequest {
|
|
||||||
return &models.ListVouchersByRowsRequest{
|
|
||||||
Rows: req.Rows,
|
|
||||||
WinnerNumber: req.WinnerNumber,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ListVouchersByRowsResponseToContract(resp *models.ListVouchersByRowsResponse) *contract.ListVouchersByRowsResponse {
|
|
||||||
rows := make([]contract.VoucherRow, len(resp.Rows))
|
|
||||||
for i, row := range resp.Rows {
|
|
||||||
rows[i] = *VoucherRowModelToContract(&row)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &contract.ListVouchersByRowsResponse{
|
|
||||||
Rows: rows,
|
|
||||||
TotalRows: resp.TotalRows,
|
|
||||||
TotalVouchers: resp.TotalVouchers,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// maskPhoneNumber masks phone number for privacy
|
|
||||||
func maskPhoneNumber(phone *string) string {
|
|
||||||
if phone == nil || *phone == "" {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
phoneStr := *phone
|
|
||||||
if len(phoneStr) <= 4 {
|
|
||||||
return strings.Repeat("*", len(phoneStr))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show first 2 and last 2 characters, mask the middle
|
|
||||||
start := phoneStr[:2]
|
|
||||||
end := phoneStr[len(phoneStr)-2:]
|
|
||||||
middle := strings.Repeat("*", len(phoneStr)-4)
|
|
||||||
|
|
||||||
return start + middle + end
|
|
||||||
}
|
|
||||||
@ -1,51 +0,0 @@
|
|||||||
package models
|
|
||||||
|
|
||||||
type VoucherResponse struct {
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Email string `json:"email"`
|
|
||||||
PhoneNumber string `json:"phone_number"`
|
|
||||||
VoucherCode string `json:"voucher_code"`
|
|
||||||
WinnerNumber int `json:"winner_number"`
|
|
||||||
IsWinner bool `json:"is_winner"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type VoucherSpinResponse struct {
|
|
||||||
VoucherCode string `json:"voucher_code"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
PhoneNumber string `json:"phone_number"` // This will be masked
|
|
||||||
IsWinner bool `json:"is_winner"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ListVouchersForSpinRequest struct {
|
|
||||||
Limit int `json:"limit" validate:"min=1,max=50"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ListVouchersForSpinResponse struct {
|
|
||||||
Vouchers []VoucherSpinResponse `json:"vouchers"`
|
|
||||||
Count int `json:"count"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type PaginatedVoucherResponse struct {
|
|
||||||
Data []VoucherResponse `json:"data"`
|
|
||||||
TotalCount int `json:"total_count"`
|
|
||||||
Page int `json:"page"`
|
|
||||||
Limit int `json:"limit"`
|
|
||||||
TotalPages int `json:"total_pages"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type VoucherRow struct {
|
|
||||||
RowNumber int `json:"row_number"`
|
|
||||||
Vouchers []VoucherSpinResponse `json:"vouchers"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ListVouchersByRowsRequest struct {
|
|
||||||
Rows int `json:"rows" validate:"min=1,max=10"`
|
|
||||||
WinnerNumber *int `json:"winner_number,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ListVouchersByRowsResponse struct {
|
|
||||||
Rows []VoucherRow `json:"rows"`
|
|
||||||
TotalRows int `json:"total_rows"`
|
|
||||||
TotalVouchers int `json:"total_vouchers"`
|
|
||||||
}
|
|
||||||
@ -1,156 +0,0 @@
|
|||||||
package processor
|
|
||||||
|
|
||||||
import (
|
|
||||||
"apskel-pos-be/internal/mappers"
|
|
||||||
"apskel-pos-be/internal/models"
|
|
||||||
"apskel-pos-be/internal/repository"
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"math/rand"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type VoucherProcessor struct {
|
|
||||||
voucherRepo *repository.VoucherRepository
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewVoucherProcessor(voucherRepo *repository.VoucherRepository) *VoucherProcessor {
|
|
||||||
return &VoucherProcessor{
|
|
||||||
voucherRepo: voucherRepo,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetRandomVouchersForSpin retrieves random vouchers for spin feature
|
|
||||||
func (p *VoucherProcessor) GetRandomVouchersForSpin(ctx context.Context, req *models.ListVouchersForSpinRequest) (*models.ListVouchersForSpinResponse, error) {
|
|
||||||
// Set default limit if not provided
|
|
||||||
limit := req.Limit
|
|
||||||
if limit <= 0 {
|
|
||||||
limit = 10 // Default limit
|
|
||||||
}
|
|
||||||
if limit > 50 {
|
|
||||||
limit = 50 // Max limit
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get random vouchers from repository
|
|
||||||
vouchers, err := p.voucherRepo.GetRandomVouchers(ctx, limit)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get random vouchers: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert to spin response format
|
|
||||||
voucherResponses := make([]models.VoucherSpinResponse, len(vouchers))
|
|
||||||
for i, voucher := range vouchers {
|
|
||||||
voucherResponses[i] = *mappers.VoucherEntityToSpinResponse(&voucher)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &models.ListVouchersForSpinResponse{
|
|
||||||
Vouchers: voucherResponses,
|
|
||||||
Count: len(voucherResponses),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetVoucherByID retrieves a voucher by ID
|
|
||||||
func (p *VoucherProcessor) GetVoucherByID(ctx context.Context, voucherID int64) (*models.VoucherResponse, error) {
|
|
||||||
voucher, err := p.voucherRepo.GetByID(ctx, voucherID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("voucher not found: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return mappers.VoucherEntityToResponse(voucher), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetVoucherByCode retrieves a voucher by voucher code
|
|
||||||
func (p *VoucherProcessor) GetVoucherByCode(ctx context.Context, voucherCode string) (*models.VoucherResponse, error) {
|
|
||||||
voucher, err := p.voucherRepo.GetByVoucherCode(ctx, voucherCode)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("voucher not found: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return mappers.VoucherEntityToResponse(voucher), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListVouchers retrieves vouchers with pagination
|
|
||||||
func (p *VoucherProcessor) ListVouchers(ctx context.Context, page, limit int) (*models.PaginatedVoucherResponse, error) {
|
|
||||||
offset := (page - 1) * limit
|
|
||||||
|
|
||||||
vouchers, total, err := p.voucherRepo.List(ctx, offset, limit)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to list vouchers: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert to response format
|
|
||||||
voucherResponses := make([]models.VoucherResponse, len(vouchers))
|
|
||||||
for i, voucher := range vouchers {
|
|
||||||
voucherResponses[i] = *mappers.VoucherEntityToResponse(&voucher)
|
|
||||||
}
|
|
||||||
|
|
||||||
totalPages := int((total + int64(limit) - 1) / int64(limit))
|
|
||||||
|
|
||||||
return &models.PaginatedVoucherResponse{
|
|
||||||
Data: voucherResponses,
|
|
||||||
TotalCount: int(total),
|
|
||||||
Page: page,
|
|
||||||
Limit: limit,
|
|
||||||
TotalPages: totalPages,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetRandomVouchersByRows retrieves random vouchers organized into rows
|
|
||||||
func (p *VoucherProcessor) GetRandomVouchersByRows(ctx context.Context, req *models.ListVouchersByRowsRequest) (*models.ListVouchersByRowsResponse, error) {
|
|
||||||
// Set default values if not provided
|
|
||||||
rows := req.Rows
|
|
||||||
if rows <= 0 {
|
|
||||||
rows = 4 // Default to 4 rows
|
|
||||||
}
|
|
||||||
if rows > 10 {
|
|
||||||
rows = 10 // Max 10 rows
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get random vouchers organized by rows
|
|
||||||
voucherRows, err := p.voucherRepo.GetRandomVouchersByRows(ctx, rows, req.WinnerNumber)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get random vouchers by rows: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert to response format and select winners
|
|
||||||
responseRows := make([]models.VoucherRow, len(voucherRows))
|
|
||||||
totalVouchers := 0
|
|
||||||
|
|
||||||
for i, row := range voucherRows {
|
|
||||||
vouchers := make([]models.VoucherSpinResponse, len(row))
|
|
||||||
|
|
||||||
// Select a random winner from this row if there are vouchers
|
|
||||||
if len(row) > 0 {
|
|
||||||
// Select random winner
|
|
||||||
rand.Seed(time.Now().UnixNano() + int64(i)) // Add row index for different seed
|
|
||||||
winnerIndex := rand.Intn(len(row))
|
|
||||||
|
|
||||||
// Mark as winner in database
|
|
||||||
err := p.voucherRepo.MarkAsWinner(ctx, row[winnerIndex].ID)
|
|
||||||
if err != nil {
|
|
||||||
// Log error but continue - don't fail the entire request
|
|
||||||
fmt.Printf("Failed to mark voucher %d as winner: %v\n", row[winnerIndex].ID, err)
|
|
||||||
} else {
|
|
||||||
// Update the voucher object to reflect the change
|
|
||||||
row[winnerIndex].IsWinner = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert all vouchers to response format
|
|
||||||
for j, voucher := range row {
|
|
||||||
vouchers[j] = *mappers.VoucherEntityToSpinResponse(&voucher)
|
|
||||||
}
|
|
||||||
|
|
||||||
responseRows[i] = models.VoucherRow{
|
|
||||||
RowNumber: i + 1, // Row numbers start from 1
|
|
||||||
Vouchers: vouchers,
|
|
||||||
}
|
|
||||||
totalVouchers += len(row)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &models.ListVouchersByRowsResponse{
|
|
||||||
Rows: responseRows,
|
|
||||||
TotalRows: len(responseRows),
|
|
||||||
TotalVouchers: totalVouchers,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
@ -1,228 +0,0 @@
|
|||||||
package repository
|
|
||||||
|
|
||||||
import (
|
|
||||||
"apskel-pos-be/internal/entities"
|
|
||||||
"context"
|
|
||||||
"math/rand"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
type VoucherRepository struct {
|
|
||||||
db *gorm.DB
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewVoucherRepository(db *gorm.DB) *VoucherRepository {
|
|
||||||
return &VoucherRepository{db: db}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *VoucherRepository) Create(ctx context.Context, voucher *entities.Voucher) error {
|
|
||||||
return r.db.WithContext(ctx).Create(voucher).Error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *VoucherRepository) GetByID(ctx context.Context, id int64) (*entities.Voucher, error) {
|
|
||||||
var voucher entities.Voucher
|
|
||||||
err := r.db.WithContext(ctx).Where("id = ?", id).First(&voucher).Error
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &voucher, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *VoucherRepository) GetByVoucherCode(ctx context.Context, voucherCode string) (*entities.Voucher, error) {
|
|
||||||
var voucher entities.Voucher
|
|
||||||
err := r.db.WithContext(ctx).Where("voucher_code = ?", voucherCode).First(&voucher).Error
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &voucher, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *VoucherRepository) List(ctx context.Context, offset, limit int) ([]entities.Voucher, int64, error) {
|
|
||||||
var vouchers []entities.Voucher
|
|
||||||
var total int64
|
|
||||||
|
|
||||||
query := r.db.WithContext(ctx)
|
|
||||||
|
|
||||||
if err := query.Model(&entities.Voucher{}).Count(&total).Error; err != nil {
|
|
||||||
return nil, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err := query.Offset(offset).Limit(limit).Find(&vouchers).Error
|
|
||||||
if err != nil {
|
|
||||||
return nil, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return vouchers, total, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *VoucherRepository) GetRandomVouchers(ctx context.Context, limit int) ([]entities.Voucher, error) {
|
|
||||||
var vouchers []entities.Voucher
|
|
||||||
|
|
||||||
// First, get the total count
|
|
||||||
var total int64
|
|
||||||
if err := r.db.WithContext(ctx).Model(&entities.Voucher{}).Count(&total).Error; err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if total == 0 {
|
|
||||||
return vouchers, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we have fewer vouchers than requested, return all
|
|
||||||
if int(total) <= limit {
|
|
||||||
err := r.db.WithContext(ctx).Find(&vouchers).Error
|
|
||||||
return vouchers, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate random offsets to get random vouchers
|
|
||||||
rand.Seed(time.Now().UnixNano())
|
|
||||||
usedOffsets := make(map[int]bool)
|
|
||||||
|
|
||||||
for len(vouchers) < limit && len(usedOffsets) < int(total) {
|
|
||||||
offset := rand.Intn(int(total))
|
|
||||||
if usedOffsets[offset] {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
usedOffsets[offset] = true
|
|
||||||
|
|
||||||
var voucher entities.Voucher
|
|
||||||
err := r.db.WithContext(ctx).Offset(offset).Limit(1).First(&voucher).Error
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
vouchers = append(vouchers, voucher)
|
|
||||||
}
|
|
||||||
|
|
||||||
return vouchers, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *VoucherRepository) GetRandomVouchersByRows(ctx context.Context, rows int, winnerNumber *int) ([][]entities.Voucher, error) {
|
|
||||||
var allVouchers []entities.Voucher
|
|
||||||
var err error
|
|
||||||
|
|
||||||
// First, try to get vouchers based on winner_number parameter
|
|
||||||
if winnerNumber != nil {
|
|
||||||
// If winner_number is provided, filter by it and exclude already won vouchers
|
|
||||||
query := r.db.WithContext(ctx).Where("winner_number = ? AND is_winner = ?", *winnerNumber, false)
|
|
||||||
err = query.Find(&allVouchers).Error
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// If no vouchers found for the specified winner_number, fallback to winner_number = 0
|
|
||||||
if len(allVouchers) == 0 {
|
|
||||||
fallbackQuery := r.db.WithContext(ctx).Where("winner_number = ? AND is_winner = ?", 0, false)
|
|
||||||
err = fallbackQuery.Find(&allVouchers).Error
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If still no vouchers found, try without is_winner filter for winner_number = 0
|
|
||||||
if len(allVouchers) == 0 {
|
|
||||||
fallbackQuery2 := r.db.WithContext(ctx).Where("winner_number = ?", 0)
|
|
||||||
err = fallbackQuery2.Find(&allVouchers).Error
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If still no vouchers found, get any vouchers available
|
|
||||||
if len(allVouchers) == 0 {
|
|
||||||
err = r.db.WithContext(ctx).Find(&allVouchers).Error
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// If winner_number is not provided, use default winner_number = 0 and exclude already won vouchers
|
|
||||||
query := r.db.WithContext(ctx).Where("winner_number = ? AND is_winner = ?", 0, false)
|
|
||||||
err = query.Find(&allVouchers).Error
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// If no vouchers found, try without is_winner filter for winner_number = 0
|
|
||||||
if len(allVouchers) == 0 {
|
|
||||||
fallbackQuery := r.db.WithContext(ctx).Where("winner_number = ?", 0)
|
|
||||||
err = fallbackQuery.Find(&allVouchers).Error
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If still no vouchers found, get any vouchers available
|
|
||||||
if len(allVouchers) == 0 {
|
|
||||||
err = r.db.WithContext(ctx).Find(&allVouchers).Error
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(allVouchers) == 0 {
|
|
||||||
// Return empty rows if no vouchers available
|
|
||||||
return make([][]entities.Voucher, rows), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shuffle the vouchers for randomness
|
|
||||||
rand.Seed(time.Now().UnixNano())
|
|
||||||
rand.Shuffle(len(allVouchers), func(i, j int) {
|
|
||||||
allVouchers[i], allVouchers[j] = allVouchers[j], allVouchers[i]
|
|
||||||
})
|
|
||||||
|
|
||||||
// Calculate vouchers per row (distribute evenly)
|
|
||||||
vouchersPerRow := len(allVouchers) / rows
|
|
||||||
if vouchersPerRow == 0 {
|
|
||||||
vouchersPerRow = 1 // At least 1 voucher per row
|
|
||||||
}
|
|
||||||
|
|
||||||
// Organize vouchers into rows
|
|
||||||
voucherRows := make([][]entities.Voucher, rows)
|
|
||||||
voucherIndex := 0
|
|
||||||
|
|
||||||
for i := 0; i < rows; i++ {
|
|
||||||
// Calculate how many vouchers this row should have
|
|
||||||
remainingRows := rows - i
|
|
||||||
remainingVouchers := len(allVouchers) - voucherIndex
|
|
||||||
|
|
||||||
// Distribute remaining vouchers evenly among remaining rows
|
|
||||||
rowSize := remainingVouchers / remainingRows
|
|
||||||
if remainingVouchers%remainingRows > 0 {
|
|
||||||
rowSize++
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure we don't exceed available vouchers
|
|
||||||
if voucherIndex+rowSize > len(allVouchers) {
|
|
||||||
rowSize = len(allVouchers) - voucherIndex
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the row
|
|
||||||
if voucherIndex < len(allVouchers) {
|
|
||||||
endIdx := voucherIndex + rowSize
|
|
||||||
if endIdx > len(allVouchers) {
|
|
||||||
endIdx = len(allVouchers)
|
|
||||||
}
|
|
||||||
voucherRows[i] = allVouchers[voucherIndex:endIdx]
|
|
||||||
voucherIndex = endIdx
|
|
||||||
} else {
|
|
||||||
voucherRows[i] = []entities.Voucher{} // Empty row
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return voucherRows, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *VoucherRepository) Update(ctx context.Context, voucher *entities.Voucher) error {
|
|
||||||
return r.db.WithContext(ctx).Save(voucher).Error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *VoucherRepository) Delete(ctx context.Context, id int64) error {
|
|
||||||
return r.db.WithContext(ctx).Delete(&entities.Voucher{}, id).Error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *VoucherRepository) MarkAsWinner(ctx context.Context, id int64) error {
|
|
||||||
return r.db.WithContext(ctx).Model(&entities.Voucher{}).Where("id = ?", id).Update("is_winner", true).Error
|
|
||||||
}
|
|
||||||
@ -40,7 +40,6 @@ type Router struct {
|
|||||||
chartOfAccountHandler *handler.ChartOfAccountHandler
|
chartOfAccountHandler *handler.ChartOfAccountHandler
|
||||||
accountHandler *handler.AccountHandler
|
accountHandler *handler.AccountHandler
|
||||||
orderIngredientTransactionHandler *handler.OrderIngredientTransactionHandler
|
orderIngredientTransactionHandler *handler.OrderIngredientTransactionHandler
|
||||||
voucherHandler *handler.VoucherHandler
|
|
||||||
authMiddleware *middleware.AuthMiddleware
|
authMiddleware *middleware.AuthMiddleware
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,9 +90,7 @@ func NewRouter(cfg *config.Config,
|
|||||||
accountService service.AccountService,
|
accountService service.AccountService,
|
||||||
accountValidator validator.AccountValidator,
|
accountValidator validator.AccountValidator,
|
||||||
orderIngredientTransactionService service.OrderIngredientTransactionService,
|
orderIngredientTransactionService service.OrderIngredientTransactionService,
|
||||||
orderIngredientTransactionValidator validator.OrderIngredientTransactionValidator,
|
orderIngredientTransactionValidator validator.OrderIngredientTransactionValidator) *Router {
|
||||||
voucherService service.VoucherService,
|
|
||||||
voucherValidator validator.VoucherValidator) *Router {
|
|
||||||
|
|
||||||
return &Router{
|
return &Router{
|
||||||
config: cfg,
|
config: cfg,
|
||||||
@ -123,7 +120,6 @@ func NewRouter(cfg *config.Config,
|
|||||||
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),
|
orderIngredientTransactionHandler: handler.NewOrderIngredientTransactionHandler(&orderIngredientTransactionService, orderIngredientTransactionValidator),
|
||||||
voucherHandler: handler.NewVoucherHandler(voucherService, voucherValidator),
|
|
||||||
authMiddleware: authMiddleware,
|
authMiddleware: authMiddleware,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -430,16 +426,6 @@ func (r *Router) addAppRoutes(rg *gin.Engine) {
|
|||||||
orderIngredientTransactions.POST("/bulk", r.orderIngredientTransactionHandler.BulkCreateOrderIngredientTransactions)
|
orderIngredientTransactions.POST("/bulk", r.orderIngredientTransactionHandler.BulkCreateOrderIngredientTransactions)
|
||||||
}
|
}
|
||||||
|
|
||||||
vouchers := protected.Group("/vouchers")
|
|
||||||
vouchers.Use(r.authMiddleware.RequireAdminOrManager())
|
|
||||||
{
|
|
||||||
vouchers.GET("/spin", r.voucherHandler.GetRandomVouchersForSpin)
|
|
||||||
vouchers.GET("/rows", r.voucherHandler.GetRandomVouchersByRows)
|
|
||||||
vouchers.GET("", r.voucherHandler.ListVouchers)
|
|
||||||
vouchers.GET("/:id", r.voucherHandler.GetVoucherByID)
|
|
||||||
vouchers.GET("/code/:code", r.voucherHandler.GetVoucherByCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
outlets := protected.Group("/outlets")
|
outlets := protected.Group("/outlets")
|
||||||
outlets.Use(r.authMiddleware.RequireAdminOrManager())
|
outlets.Use(r.authMiddleware.RequireAdminOrManager())
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,93 +0,0 @@
|
|||||||
package service
|
|
||||||
|
|
||||||
import (
|
|
||||||
"apskel-pos-be/internal/contract"
|
|
||||||
"apskel-pos-be/internal/mappers"
|
|
||||||
"apskel-pos-be/internal/processor"
|
|
||||||
"context"
|
|
||||||
)
|
|
||||||
|
|
||||||
type VoucherService interface {
|
|
||||||
GetRandomVouchersForSpin(ctx context.Context, req *contract.ListVouchersForSpinRequest) (*contract.ListVouchersForSpinResponse, error)
|
|
||||||
GetRandomVouchersByRows(ctx context.Context, req *contract.ListVouchersByRowsRequest) (*contract.ListVouchersByRowsResponse, error)
|
|
||||||
GetVoucherByID(ctx context.Context, voucherID int64) (*contract.VoucherResponse, error)
|
|
||||||
GetVoucherByCode(ctx context.Context, voucherCode string) (*contract.VoucherResponse, error)
|
|
||||||
ListVouchers(ctx context.Context, page, limit int) (*contract.PaginatedVoucherResponse, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type VoucherServiceImpl struct {
|
|
||||||
voucherProcessor *processor.VoucherProcessor
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewVoucherService(voucherProcessor *processor.VoucherProcessor) *VoucherServiceImpl {
|
|
||||||
return &VoucherServiceImpl{
|
|
||||||
voucherProcessor: voucherProcessor,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *VoucherServiceImpl) GetRandomVouchersForSpin(ctx context.Context, req *contract.ListVouchersForSpinRequest) (*contract.ListVouchersForSpinResponse, error) {
|
|
||||||
modelReq := mappers.ListVouchersForSpinRequestToModel(req)
|
|
||||||
|
|
||||||
response, err := s.voucherProcessor.GetRandomVouchersForSpin(ctx, modelReq)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
contractResponse := mappers.ListVouchersForSpinResponseToContract(response)
|
|
||||||
return contractResponse, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *VoucherServiceImpl) GetRandomVouchersByRows(ctx context.Context, req *contract.ListVouchersByRowsRequest) (*contract.ListVouchersByRowsResponse, error) {
|
|
||||||
modelReq := mappers.ListVouchersByRowsRequestToModel(req)
|
|
||||||
|
|
||||||
response, err := s.voucherProcessor.GetRandomVouchersByRows(ctx, modelReq)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
contractResponse := mappers.ListVouchersByRowsResponseToContract(response)
|
|
||||||
return contractResponse, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *VoucherServiceImpl) GetVoucherByID(ctx context.Context, voucherID int64) (*contract.VoucherResponse, error) {
|
|
||||||
response, err := s.voucherProcessor.GetVoucherByID(ctx, voucherID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
contractResponse := mappers.VoucherModelToContract(response)
|
|
||||||
return contractResponse, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *VoucherServiceImpl) GetVoucherByCode(ctx context.Context, voucherCode string) (*contract.VoucherResponse, error) {
|
|
||||||
response, err := s.voucherProcessor.GetVoucherByCode(ctx, voucherCode)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
contractResponse := mappers.VoucherModelToContract(response)
|
|
||||||
return contractResponse, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *VoucherServiceImpl) ListVouchers(ctx context.Context, page, limit int) (*contract.PaginatedVoucherResponse, error) {
|
|
||||||
response, err := s.voucherProcessor.ListVouchers(ctx, page, limit)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert to contract response
|
|
||||||
vouchers := make([]contract.VoucherResponse, len(response.Data))
|
|
||||||
for i, voucher := range response.Data {
|
|
||||||
vouchers[i] = *mappers.VoucherModelToContract(&voucher)
|
|
||||||
}
|
|
||||||
|
|
||||||
contractResponse := &contract.PaginatedVoucherResponse{
|
|
||||||
Data: vouchers,
|
|
||||||
TotalCount: response.TotalCount,
|
|
||||||
Page: response.Page,
|
|
||||||
Limit: response.Limit,
|
|
||||||
TotalPages: response.TotalPages,
|
|
||||||
}
|
|
||||||
|
|
||||||
return contractResponse, nil
|
|
||||||
}
|
|
||||||
@ -1,41 +0,0 @@
|
|||||||
package validator
|
|
||||||
|
|
||||||
import (
|
|
||||||
"apskel-pos-be/internal/contract"
|
|
||||||
"errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
type VoucherValidator interface {
|
|
||||||
ValidateListVouchersForSpinRequest(req *contract.ListVouchersForSpinRequest) (error, string)
|
|
||||||
ValidateListVouchersByRowsRequest(req *contract.ListVouchersByRowsRequest) (error, string)
|
|
||||||
}
|
|
||||||
|
|
||||||
type VoucherValidatorImpl struct{}
|
|
||||||
|
|
||||||
func NewVoucherValidator() VoucherValidator {
|
|
||||||
return &VoucherValidatorImpl{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *VoucherValidatorImpl) ValidateListVouchersForSpinRequest(req *contract.ListVouchersForSpinRequest) (error, string) {
|
|
||||||
if req.Limit < 1 {
|
|
||||||
return errors.New("limit must be at least 1"), "INVALID_LIMIT"
|
|
||||||
}
|
|
||||||
|
|
||||||
if req.Limit > 50 {
|
|
||||||
return errors.New("limit cannot exceed 50"), "INVALID_LIMIT"
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *VoucherValidatorImpl) ValidateListVouchersByRowsRequest(req *contract.ListVouchersByRowsRequest) (error, string) {
|
|
||||||
if req.Rows < 1 {
|
|
||||||
return errors.New("rows must be at least 1"), "INVALID_ROWS"
|
|
||||||
}
|
|
||||||
|
|
||||||
if req.Rows > 10 {
|
|
||||||
return errors.New("rows cannot exceed 10"), "INVALID_ROWS"
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, ""
|
|
||||||
}
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
-- Create vouchers table
|
|
||||||
CREATE TABLE IF NOT EXISTS vouchers (
|
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
||||||
name VARCHAR(255) NOT NULL,
|
|
||||||
email VARCHAR(255),
|
|
||||||
phone_number VARCHAR(20),
|
|
||||||
voucher_code VARCHAR(50) NOT NULL UNIQUE,
|
|
||||||
winner_number INTEGER NOT NULL,
|
|
||||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Create index on voucher_code for faster lookups
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_vouchers_voucher_code ON vouchers(voucher_code);
|
|
||||||
|
|
||||||
-- Create index on winner_number for sorting
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_vouchers_winner_number ON vouchers(winner_number);
|
|
||||||
@ -1,222 +0,0 @@
|
|||||||
# Voucher API Test Guide
|
|
||||||
|
|
||||||
## API Endpoints
|
|
||||||
|
|
||||||
### 1. Get Random Vouchers for Spin
|
|
||||||
**GET** `/api/v1/vouchers/spin`
|
|
||||||
|
|
||||||
Query Parameters:
|
|
||||||
- `limit` (optional): Number of vouchers to return (1-50, default: 10)
|
|
||||||
|
|
||||||
**Response:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"message": "Success",
|
|
||||||
"data": {
|
|
||||||
"vouchers": [
|
|
||||||
{
|
|
||||||
"voucher_code": "VOUCHER001",
|
|
||||||
"name": "John Doe",
|
|
||||||
"phone_number": "08**1234",
|
|
||||||
"is_winner": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"voucher_code": "VOUCHER002",
|
|
||||||
"name": "Jane Smith",
|
|
||||||
"phone_number": "09**5678",
|
|
||||||
"is_winner": false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"count": 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Get Random Vouchers by Rows (UPDATED!)
|
|
||||||
**GET** `/api/v1/vouchers/rows`
|
|
||||||
|
|
||||||
Query Parameters:
|
|
||||||
- `rows` (optional): Number of rows to return (1-10, default: 4)
|
|
||||||
- `winner_number` (optional): Winner number to filter by (if not provided, defaults to 0)
|
|
||||||
|
|
||||||
**Logic:**
|
|
||||||
- If `winner_number` is provided: Randomly select from vouchers where `winner_number = provided_value` AND `is_winner = false`
|
|
||||||
- If no vouchers found for the specified `winner_number`: Fallback to `winner_number = 0` AND `is_winner = false`
|
|
||||||
- If `winner_number` is not provided: Randomly select from vouchers where `winner_number = 0` AND `is_winner = false`
|
|
||||||
- Excludes vouchers already marked as winners
|
|
||||||
- Distributes vouchers evenly across the specified number of rows
|
|
||||||
- **Automatically selects one random winner from each row and sets their `is_winner` to `true`**
|
|
||||||
|
|
||||||
**Response:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"message": "Success",
|
|
||||||
"data": {
|
|
||||||
"rows": [
|
|
||||||
{
|
|
||||||
"row_number": 1,
|
|
||||||
"vouchers": [
|
|
||||||
{
|
|
||||||
"voucher_code": "VOUCHER001",
|
|
||||||
"name": "John Doe",
|
|
||||||
"phone_number": "08**1234",
|
|
||||||
"is_winner": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"voucher_code": "VOUCHER002",
|
|
||||||
"name": "Jane Smith",
|
|
||||||
"phone_number": "09**5678",
|
|
||||||
"is_winner": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"voucher_code": "VOUCHER003",
|
|
||||||
"name": "Bob Johnson",
|
|
||||||
"phone_number": "08**9012",
|
|
||||||
"is_winner": false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"row_number": 2,
|
|
||||||
"vouchers": [
|
|
||||||
{
|
|
||||||
"voucher_code": "VOUCHER004",
|
|
||||||
"name": "Alice Brown",
|
|
||||||
"phone_number": "09**3456",
|
|
||||||
"is_winner": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"voucher_code": "VOUCHER005",
|
|
||||||
"name": "Charlie Wilson",
|
|
||||||
"phone_number": "08**7890",
|
|
||||||
"is_winner": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"total_rows": 2,
|
|
||||||
"total_vouchers": 5
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. List All Vouchers
|
|
||||||
**GET** `/api/v1/vouchers`
|
|
||||||
|
|
||||||
Query Parameters:
|
|
||||||
- `page` (optional): Page number (default: 1)
|
|
||||||
- `limit` (optional): Items per page (default: 10, max: 100)
|
|
||||||
|
|
||||||
**Response:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"message": "Success",
|
|
||||||
"data": {
|
|
||||||
"data": [
|
|
||||||
{
|
|
||||||
"id": "uuid",
|
|
||||||
"name": "John Doe",
|
|
||||||
"email": "john@example.com",
|
|
||||||
"phone_number": "08123456789",
|
|
||||||
"voucher_code": "VOUCHER001",
|
|
||||||
"winner_number": 1,
|
|
||||||
"created_at": "2024-01-01T00:00:00Z",
|
|
||||||
"updated_at": "2024-01-01T00:00:00Z"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"total_count": 1,
|
|
||||||
"page": 1,
|
|
||||||
"limit": 10,
|
|
||||||
"total_pages": 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Get Voucher by ID
|
|
||||||
**GET** `/api/v1/vouchers/{id}`
|
|
||||||
|
|
||||||
**Response:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"message": "Success",
|
|
||||||
"data": {
|
|
||||||
"id": "uuid",
|
|
||||||
"name": "John Doe",
|
|
||||||
"email": "john@example.com",
|
|
||||||
"phone_number": "08123456789",
|
|
||||||
"voucher_code": "VOUCHER001",
|
|
||||||
"winner_number": 1,
|
|
||||||
"created_at": "2024-01-01T00:00:00Z",
|
|
||||||
"updated_at": "2024-01-01T00:00:00Z"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5. Get Voucher by Code
|
|
||||||
**GET** `/api/v1/vouchers/code/{code}`
|
|
||||||
|
|
||||||
**Response:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"message": "Success",
|
|
||||||
"data": {
|
|
||||||
"id": 1,
|
|
||||||
"name": "John Doe",
|
|
||||||
"email": "john@example.com",
|
|
||||||
"phone_number": "08123456789",
|
|
||||||
"voucher_code": "VOUCHER001",
|
|
||||||
"winner_number": 1,
|
|
||||||
"is_winner": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
1. **Random Selection**: The `/spin` endpoint returns random vouchers from the database
|
|
||||||
2. **Phone Number Masking**: Phone numbers are masked for privacy (shows first 2 and last 2 digits)
|
|
||||||
3. **Pagination**: List endpoint supports pagination
|
|
||||||
4. **Authentication**: All endpoints require admin or manager role
|
|
||||||
5. **Validation**: Input validation for limit parameters
|
|
||||||
|
|
||||||
## Database Schema
|
|
||||||
|
|
||||||
```sql
|
|
||||||
CREATE TABLE vouchers (
|
|
||||||
id BIGSERIAL PRIMARY KEY,
|
|
||||||
name VARCHAR(100) NOT NULL,
|
|
||||||
email VARCHAR(255) NOT NULL,
|
|
||||||
phone_number VARCHAR(20) NOT NULL,
|
|
||||||
voucher_code VARCHAR(50) NOT NULL,
|
|
||||||
winner_number INT NOT NULL DEFAULT 0,
|
|
||||||
is_winner BOOLEAN NOT NULL DEFAULT FALSE
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage Examples
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Get 5 random vouchers for spin
|
|
||||||
curl -X GET "http://localhost:8080/api/v1/vouchers/spin?limit=5" \
|
|
||||||
-H "Authorization: Bearer YOUR_JWT_TOKEN"
|
|
||||||
|
|
||||||
# Get 4 rows (default) - vouchers distributed evenly across rows
|
|
||||||
curl -X GET "http://localhost:8080/api/v1/vouchers/rows" \
|
|
||||||
-H "Authorization: Bearer YOUR_JWT_TOKEN"
|
|
||||||
|
|
||||||
# Get 3 rows - vouchers distributed evenly across 3 rows (from winner_number = 0)
|
|
||||||
curl -X GET "http://localhost:8080/api/v1/vouchers/rows?rows=3" \
|
|
||||||
-H "Authorization: Bearer YOUR_JWT_TOKEN"
|
|
||||||
|
|
||||||
# Get 3 rows - vouchers from winner_number = 1 (fallback to winner_number = 0 if none found)
|
|
||||||
curl -X GET "http://localhost:8080/api/v1/vouchers/rows?rows=3&winner_number=1" \
|
|
||||||
-H "Authorization: Bearer YOUR_JWT_TOKEN"
|
|
||||||
|
|
||||||
# Get 3 rows - vouchers from winner_number = 5 (will fallback to winner_number = 0 if none found)
|
|
||||||
curl -X GET "http://localhost:8080/api/v1/vouchers/rows?rows=3&winner_number=5" \
|
|
||||||
-H "Authorization: Bearer YOUR_JWT_TOKEN"
|
|
||||||
|
|
||||||
# List all vouchers with pagination
|
|
||||||
curl -X GET "http://localhost:8080/api/v1/vouchers?page=1&limit=20" \
|
|
||||||
-H "Authorization: Bearer YOUR_JWT_TOKEN"
|
|
||||||
```
|
|
||||||
Loading…
x
Reference in New Issue
Block a user