diff --git a/internal/entity/undian.go b/internal/entity/undian.go new file mode 100644 index 0000000..84d52fb --- /dev/null +++ b/internal/entity/undian.go @@ -0,0 +1,451 @@ +package entity + +import ( + "time" +) + +// ============================================= +// DATABASE ENTITIES +// ============================================= + +type UndianEventDB struct { + ID int64 `gorm:"primaryKey;autoIncrement" json:"id"` + Title string `gorm:"size:255;not null" json:"title"` + Description *string `gorm:"type:text" json:"description"` + ImageURL *string `gorm:"size:500" json:"image_url"` + Status string `gorm:"size:20;not null;default:upcoming" json:"status"` // upcoming, active, completed, cancelled + + // Event timing + StartDate time.Time `gorm:"not null" json:"start_date"` + EndDate time.Time `gorm:"not null" json:"end_date"` + DrawDate time.Time `gorm:"not null" json:"draw_date"` + + // Configuration + MinimumPurchase float64 `gorm:"type:decimal(10,2);default:50000" json:"minimum_purchase"` + + // Draw status + DrawCompleted bool `gorm:"default:false" json:"draw_completed"` + DrawCompletedAt *time.Time `json:"draw_completed_at"` + + // Metadata + TermsAndConditions *string `gorm:"type:text" json:"terms_and_conditions"` + CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"` + UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"` +} + +func (UndianEventDB) TableName() string { + return "undian_events" +} + +type UndianPrizeDB struct { + ID int64 `gorm:"primaryKey;autoIncrement" json:"id"` + UndianEventID int64 `gorm:"not null" json:"undian_event_id"` + Rank int `gorm:"not null" json:"rank"` + PrizeName string `gorm:"size:255;not null" json:"prize_name"` + PrizeValue *float64 `gorm:"type:decimal(15,2)" json:"prize_value"` + PrizeDescription *string `gorm:"type:text" json:"prize_description"` + PrizeType string `gorm:"size:50;default:voucher" json:"prize_type"` // gold, voucher, cash, product, service + PrizeImageURL *string `gorm:"size:500" json:"prize_image_url"` + + // Winner information (filled after draw) + WinningVoucherID *int64 `json:"winning_voucher_id"` + WinnerUserID *int64 `json:"winner_user_id"` + + // Relations + UndianEvent UndianEventDB `gorm:"foreignKey:UndianEventID" json:"undian_event,omitempty"` +} + +func (UndianPrizeDB) TableName() string { + return "undian_prizes" +} + +type UndianVoucherDB struct { + ID int64 `gorm:"primaryKey;autoIncrement" json:"id"` + UndianEventID int64 `gorm:"not null" json:"undian_event_id"` + CustomerID int64 `gorm:"not null" json:"customer_id"` + OrderID *int64 `json:"order_id"` + + // Voucher identification + VoucherCode string `gorm:"size:50;not null;uniqueIndex" json:"voucher_code"` + VoucherNumber *int `json:"voucher_number"` + + // Winner status + IsWinner bool `gorm:"default:false" json:"is_winner"` + PrizeRank *int `json:"prize_rank"` + WonAt *time.Time `json:"won_at"` + + CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"` + + // Relations + UndianEvent UndianEventDB `gorm:"foreignKey:UndianEventID" json:"undian_event,omitempty"` +} + +func (UndianVoucherDB) TableName() string { + return "undian_vouchers" +} + +// ============================================= +// REQUEST/RESPONSE ENTITIES +// ============================================= + +type UndianEventSearch struct { + Status string `json:"status"` + IsActive bool `json:"is_active"` + IsCompleted bool `json:"is_completed"` + Search string `json:"search"` + StartDateFrom time.Time `json:"start_date_from"` + StartDateTo time.Time `json:"start_date_to"` + Limit int `json:"limit"` + Offset int `json:"offset"` +} + +type CreateUndianEventRequest struct { + Title string `json:"title" validate:"required,min=3,max=255"` + Description *string `json:"description"` + ImageURL *string `json:"image_url"` + StartDate time.Time `json:"start_date" validate:"required"` + EndDate time.Time `json:"end_date" validate:"required"` + DrawDate time.Time `json:"draw_date" validate:"required"` + MinimumPurchase float64 `json:"minimum_purchase" validate:"min=0"` + TermsAndConditions *string `json:"terms_and_conditions"` + Prizes []CreateUndianPrizeRequest `json:"prizes" validate:"required,min=1"` +} + +type UpdateUndianEventRequest struct { + ID int64 `json:"id" validate:"required"` + Title string `json:"title" validate:"required,min=3,max=255"` + Description *string `json:"description"` + ImageURL *string `json:"image_url"` + StartDate time.Time `json:"start_date" validate:"required"` + EndDate time.Time `json:"end_date" validate:"required"` + DrawDate time.Time `json:"draw_date" validate:"required"` + MinimumPurchase float64 `json:"minimum_purchase" validate:"min=0"` + TermsAndConditions *string `json:"terms_and_conditions"` + Status string `json:"status" validate:"oneof=upcoming active completed cancelled"` +} + +type CreateUndianPrizeRequest struct { + Rank int `json:"rank" validate:"required,min=1"` + PrizeName string `json:"prize_name" validate:"required,min=3,max=255"` + PrizeValue *float64 `json:"prize_value"` + PrizeDescription *string `json:"prize_description"` + PrizeType string `json:"prize_type" validate:"oneof=gold voucher cash product service"` + PrizeImageURL *string `json:"prize_image_url"` +} + +type UndianEventResponse struct { + ID int64 `json:"id"` + Title string `json:"title"` + Description *string `json:"description"` + ImageURL *string `json:"image_url"` + Status string `json:"status"` + StartDate time.Time `json:"start_date"` + EndDate time.Time `json:"end_date"` + DrawDate time.Time `json:"draw_date"` + MinimumPurchase float64 `json:"minimum_purchase"` + DrawCompleted bool `json:"draw_completed"` + DrawCompletedAt *time.Time `json:"draw_completed_at"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + + // Statistics (populated when needed) + TotalVouchers int64 `json:"total_vouchers,omitempty"` + TotalParticipants int64 `json:"total_participants,omitempty"` + TotalPrizes int64 `json:"total_prizes,omitempty"` + + // Relations (populated when needed) + Prizes []UndianPrizeResponse `json:"prizes,omitempty"` +} + +type UndianPrizeResponse struct { + ID int64 `json:"id"` + UndianEventID int64 `json:"undian_event_id"` + Rank int `json:"rank"` + PrizeName string `json:"prize_name"` + PrizeValue *float64 `json:"prize_value"` + PrizeDescription *string `json:"prize_description"` + PrizeType string `json:"prize_type"` + PrizeImageURL *string `json:"prize_image_url"` + WinningVoucherID *int64 `json:"winning_voucher_id,omitempty"` + WinnerUserID *int64 `json:"winner_user_id,omitempty"` +} + +type UndianVoucherResponse struct { + ID int64 `json:"id"` + UndianEventID int64 `json:"undian_event_id"` + CustomerID int64 `json:"customer_id"` + OrderID *int64 `json:"order_id"` + VoucherCode string `json:"voucher_code"` + VoucherNumber *int `json:"voucher_number"` + IsWinner bool `json:"is_winner"` + PrizeRank *int `json:"prize_rank"` + WonAt *time.Time `json:"won_at"` + CreatedAt time.Time `json:"created_at"` + + // Relations (populated when needed) + UndianEvent *UndianEventResponse `json:"undian_event,omitempty"` + Prize *UndianPrizeResponse `json:"prize,omitempty"` +} + +// ============================================= +// COMPOSITE ENTITIES +// ============================================= + +type UndianEventWithStats struct { + Event UndianEventDB `json:"event"` + TotalVouchers int64 `json:"total_vouchers"` + TotalParticipants int64 `json:"total_participants"` + TotalPrizes int64 `json:"total_prizes"` +} + +type UndianEventWithPrizes struct { + Event UndianEventDB `json:"event"` + Prizes []*UndianPrizeDB `json:"prizes"` +} + +type CustomerUndianSummary struct { + CustomerID int64 `json:"customer_id"` + EventID int64 `json:"event_id"` + TotalVouchers int64 `json:"total_vouchers"` + WinningVouchers int64 `json:"winning_vouchers"` + IsParticipating bool `json:"is_participating"` +} + +// ============================================= +// API RESPONSE ENTITIES +// ============================================= + +type UndianEventListResponse struct { + Events []UndianEventResponse `json:"events"` + Total int `json:"total"` + Page int `json:"page"` + Limit int `json:"limit"` +} + +type CustomerUndianListResponse struct { + Events []CustomerUndianEventResponse `json:"events"` + Total int `json:"total"` +} + +type CustomerUndianEventResponse struct { + Event UndianEventResponse `json:"event"` + UserVouchers []UndianVoucherResponse `json:"user_vouchers"` + TotalVouchers int `json:"total_vouchers"` + WinningVouchers int `json:"winning_vouchers"` + IsParticipating bool `json:"is_participating"` +} + +type UndianEventDetailResponse struct { + Event UndianEventResponse `json:"event"` + Prizes []UndianPrizeResponse `json:"prizes"` + UserVouchers []UndianVoucherResponse `json:"user_vouchers,omitempty"` + TotalVouchers int64 `json:"total_vouchers"` + TotalParticipants int64 `json:"total_participants"` + UserParticipating bool `json:"user_participating"` + UserVoucherCount int `json:"user_voucher_count"` +} + +type DrawResultResponse struct { + EventID int64 `json:"event_id"` + EventTitle string `json:"event_title"` + DrawDate time.Time `json:"draw_date"` + DrawCompleted bool `json:"draw_completed"` + Winners []DrawWinnerResponse `json:"winners"` +} + +type DrawWinnerResponse struct { + Rank int `json:"rank"` + PrizeName string `json:"prize_name"` + PrizeValue *float64 `json:"prize_value"` + VoucherCode string `json:"voucher_code"` + WinnerUserID *int64 `json:"winner_user_id,omitempty"` +} + +// ============================================= +// ERROR RESPONSES +// ============================================= + +type UndianError struct { + Code string `json:"code"` + Message string `json:"message"` + Details string `json:"details,omitempty"` +} + +func (e UndianError) Error() string { + return e.Message +} + +// Common error codes +var ( + ErrUndianEventNotFound = UndianError{ + Code: "UNDIAN_EVENT_NOT_FOUND", + Message: "Undian event not found", + } + + ErrUndianEventNotActive = UndianError{ + Code: "UNDIAN_EVENT_NOT_ACTIVE", + Message: "Undian event is not active", + } + + ErrDrawAlreadyCompleted = UndianError{ + Code: "DRAW_ALREADY_COMPLETED", + Message: "Draw has already been completed for this event", + } + + ErrDrawDateNotReached = UndianError{ + Code: "DRAW_DATE_NOT_REACHED", + Message: "Draw date has not been reached yet", + } + + ErrInsufficientOrderAmount = UndianError{ + Code: "INSUFFICIENT_ORDER_AMOUNT", + Message: "Order amount does not meet minimum purchase requirement", + } + + ErrVoucherNotFound = UndianError{ + Code: "VOUCHER_NOT_FOUND", + Message: "Voucher not found", + } + + ErrVoucherAlreadyExists = UndianError{ + Code: "VOUCHER_ALREADY_EXISTS", + Message: "Voucher with this code already exists", + } + + ErrInvalidDateRange = UndianError{ + Code: "INVALID_DATE_RANGE", + Message: "Invalid date range: start_date must be before end_date, and end_date must be before draw_date", + } +) + +// ============================================= +// CONVERSION METHODS +// ============================================= + +func (e *UndianEventDB) ToResponse() UndianEventResponse { + return UndianEventResponse{ + ID: e.ID, + Title: e.Title, + Description: e.Description, + ImageURL: e.ImageURL, + Status: e.Status, + StartDate: e.StartDate, + EndDate: e.EndDate, + DrawDate: e.DrawDate, + MinimumPurchase: e.MinimumPurchase, + DrawCompleted: e.DrawCompleted, + DrawCompletedAt: e.DrawCompletedAt, + CreatedAt: e.CreatedAt, + UpdatedAt: e.UpdatedAt, + } +} + +func (p *UndianPrizeDB) ToResponse() UndianPrizeResponse { + return UndianPrizeResponse{ + ID: p.ID, + UndianEventID: p.UndianEventID, + Rank: p.Rank, + PrizeName: p.PrizeName, + PrizeValue: p.PrizeValue, + PrizeDescription: p.PrizeDescription, + PrizeType: p.PrizeType, + PrizeImageURL: p.PrizeImageURL, + WinningVoucherID: p.WinningVoucherID, + WinnerUserID: p.WinnerUserID, + } +} + +func (v *UndianVoucherDB) ToResponse() UndianVoucherResponse { + return UndianVoucherResponse{ + ID: v.ID, + UndianEventID: v.UndianEventID, + CustomerID: v.CustomerID, + OrderID: v.OrderID, + VoucherCode: v.VoucherCode, + VoucherNumber: v.VoucherNumber, + IsWinner: v.IsWinner, + PrizeRank: v.PrizeRank, + WonAt: v.WonAt, + CreatedAt: v.CreatedAt, + } +} + +// Helper method to convert slice of DB entities to response entities +func UndianEventsToResponse(events []*UndianEventDB) []UndianEventResponse { + responses := make([]UndianEventResponse, len(events)) + for i, event := range events { + responses[i] = event.ToResponse() + } + return responses +} + +func UndianPrizesToResponse(prizes []*UndianPrizeDB) []UndianPrizeResponse { + responses := make([]UndianPrizeResponse, len(prizes)) + for i, prize := range prizes { + responses[i] = prize.ToResponse() + } + return responses +} + +func UndianVouchersToResponse(vouchers []*UndianVoucherDB) []UndianVoucherResponse { + responses := make([]UndianVoucherResponse, len(vouchers)) + for i, voucher := range vouchers { + responses[i] = voucher.ToResponse() + } + return responses +} + +// ============================================= +// VALIDATION HELPERS +// ============================================= + +func (r *CreateUndianEventRequest) Validate() error { + if r.StartDate.After(r.EndDate) { + return ErrInvalidDateRange + } + if r.EndDate.After(r.DrawDate) { + return ErrInvalidDateRange + } + if r.MinimumPurchase < 0 { + return UndianError{ + Code: "INVALID_MINIMUM_PURCHASE", + Message: "Minimum purchase must be greater than or equal to 0", + } + } + return nil +} + +func (r *UpdateUndianEventRequest) Validate() error { + if r.StartDate.After(r.EndDate) { + return ErrInvalidDateRange + } + if r.EndDate.After(r.DrawDate) { + return ErrInvalidDateRange + } + if r.MinimumPurchase < 0 { + return UndianError{ + Code: "INVALID_MINIMUM_PURCHASE", + Message: "Minimum purchase must be greater than or equal to 0", + } + } + return nil +} + +// ============================================= +// STATUS CONSTANTS +// ============================================= + +const ( + UndianStatusUpcoming = "upcoming" + UndianStatusActive = "active" + UndianStatusCompleted = "completed" + UndianStatusCancelled = "cancelled" +) + +const ( + PrizeTypeGold = "gold" + PrizeTypeVoucher = "voucher" + PrizeTypeCash = "cash" + PrizeTypeProduct = "product" + PrizeTypeService = "service" +) diff --git a/internal/repository/repository.go b/internal/repository/repository.go index 3914c28..df9346a 100644 --- a/internal/repository/repository.go +++ b/internal/repository/repository.go @@ -59,6 +59,7 @@ type RepoManagerImpl struct { TransactionRepo TransactionRepo MemberRepository MemberRepository PartnerSetting PartnerSettingsRepository + UndianRepository UndianRepo } func NewRepoManagerImpl(db *gorm.DB, cfg *config.Config) *RepoManagerImpl { @@ -90,6 +91,7 @@ func NewRepoManagerImpl(db *gorm.DB, cfg *config.Config) *RepoManagerImpl { MemberRepository: NewMemberRepository(db), InProgressOrderRepo: NewInProgressOrderRepository(db), PartnerSetting: NewPartnerSettingsRepository(db), + UndianRepository: NewUndianRepository(db), } } diff --git a/internal/repository/undian_repo.go b/internal/repository/undian_repo.go new file mode 100644 index 0000000..178e8f3 --- /dev/null +++ b/internal/repository/undian_repo.go @@ -0,0 +1,88 @@ +package repository + +import ( + "context" + "enaklo-pos-be/internal/common/logger" + "enaklo-pos-be/internal/common/mycontext" + "enaklo-pos-be/internal/entity" + "fmt" + "time" + + "go.uber.org/zap" + "gorm.io/gorm" +) + +type UndianRepo interface { + GetUndianEventByID(ctx context.Context, id int64) (*entity.UndianEventDB, error) + GetActiveUndianEvents(ctx context.Context) ([]*entity.UndianEventDB, error) + CreateUndianVouchers(ctx context.Context, vouchers []*entity.UndianVoucherDB) error + GetNextVoucherSequence(ctx mycontext.Context) (int64, error) + GetNextVoucherSequenceBatch(ctx mycontext.Context, count int) (int64, error) +} + +type undianRepository struct { + db *gorm.DB +} + +func NewUndianRepository(db *gorm.DB) *undianRepository { + return &undianRepository{ + db: db, + } +} + +func (r *undianRepository) GetUndianEventByID(ctx context.Context, id int64) (*entity.UndianEventDB, error) { + event := new(entity.UndianEventDB) + if err := r.db.WithContext(ctx).First(event, id).Error; err != nil { + logger.ContextLogger(ctx).Error("error when get undian event by id", zap.Error(err)) + return nil, err + } + return event, nil +} + +func (r *undianRepository) GetActiveUndianEvents(ctx context.Context) ([]*entity.UndianEventDB, error) { + var events []*entity.UndianEventDB + now := time.Now() + + if err := r.db.WithContext(ctx). + Where("status = 'active' AND start_date <= ? AND end_date >= ?", now, now). + Find(&events).Error; err != nil { + logger.ContextLogger(ctx).Error("error when get active undian events", zap.Error(err)) + return nil, err + } + + return events, nil +} + +func (r *undianRepository) CreateUndianVouchers(ctx context.Context, vouchers []*entity.UndianVoucherDB) error { + err := r.db.WithContext(ctx).Create(&vouchers).Error + if err != nil { + logger.ContextLogger(ctx).Error("error when create undian vouchers", zap.Error(err)) + return err + } + return nil +} + +func (r *undianRepository) GetNextVoucherSequence(ctx mycontext.Context) (int64, error) { + var sequence int64 + + err := r.db.WithContext(ctx).Raw("SELECT nextval('voucher_sequence')").Scan(&sequence).Error + if err != nil { + logger.ContextLogger(ctx).Error("error when get next voucher sequence", zap.Error(err)) + return 0, err + } + + return sequence, nil +} + +func (r *undianRepository) GetNextVoucherSequenceBatch(ctx mycontext.Context, count int) (int64, error) { + var startSequence int64 + + query := fmt.Sprintf("SELECT nextval('voucher_sequence') + %d - %d", count-1, count-1) + err := r.db.WithContext(ctx).Raw(query).Scan(&startSequence).Error + if err != nil { + logger.ContextLogger(ctx).Error("error when get batch voucher sequence", zap.Error(err)) + return 0, err + } + + return startSequence, nil +} \ No newline at end of file diff --git a/internal/services/service.go b/internal/services/service.go index b97b7b6..e01d35c 100644 --- a/internal/services/service.go +++ b/internal/services/service.go @@ -59,7 +59,7 @@ func NewServiceManagerImpl(cfg *config.Config, repo *repository.RepoManagerImpl) productSvcV2 := productSvc.New(repo.ProductRepo) partnerSettings := partner_settings.NewPartnerSettingsService(repo.PartnerSetting) - orderService := orderSvc.New(repo.OrderRepo, productSvcV2, custSvcV2, repo.TransactionRepo, repo.Crypto, &cfg.Order, repo.EmailService, partnerSettings) + orderService := orderSvc.New(repo.OrderRepo, productSvcV2, custSvcV2, repo.TransactionRepo, repo.Crypto, &cfg.Order, repo.EmailService, partnerSettings, repo.UndianRepository) inprogressOrder := inprogress_order.NewInProgressOrderService(repo.OrderRepo, orderService, productSvcV2) return &ServiceManagerImpl{ AuthSvc: auth.New(repo.Auth, repo.Crypto, repo.User, repo.EmailService, cfg.Email, repo.Trx, repo.License), @@ -75,7 +75,7 @@ func NewServiceManagerImpl(cfg *config.Config, repo *repository.RepoManagerImpl) Transaction: transaction.New(repo.Transaction, repo.Wallet, repo.Trx), Balance: balance.NewBalanceService(repo.Wallet, repo.Trx, repo.Crypto, &cfg.Withdraw, repo.Transaction), DiscoverService: discovery.NewDiscoveryService(repo.Site, cfg.Discovery, repo.Product), - OrderV2Svc: orderSvc.New(repo.OrderRepo, productSvcV2, custSvcV2, repo.TransactionRepo, repo.Crypto, &cfg.Order, repo.EmailService, partnerSettings), + OrderV2Svc: orderSvc.New(repo.OrderRepo, productSvcV2, custSvcV2, repo.TransactionRepo, repo.Crypto, &cfg.Order, repo.EmailService, partnerSettings, repo.UndianRepository), MemberRegistrationSvc: member.NewMemberRegistrationService(repo.MemberRepository, repo.EmailService, custSvcV2, repo.Crypto), CustomerV2Svc: custSvcV2, InProgressSvc: inprogressOrder, diff --git a/internal/services/v2/order/create_order_inquiry.go b/internal/services/v2/order/create_order_inquiry.go index 81d1d57..9ae09f7 100644 --- a/internal/services/v2/order/create_order_inquiry.go +++ b/internal/services/v2/order/create_order_inquiry.go @@ -29,12 +29,17 @@ func (s *orderSvc) CreateOrderInquiry(ctx mycontext.Context, return nil, err } - customerID, err := s.customer.ResolveCustomer(ctx, &entity.CustomerResolutionRequest{ - ID: req.CustomerID, - Name: req.CustomerName, - Email: req.CustomerEmail, - PhoneNumber: req.CustomerPhoneNumber, - }) + customerID := int64(0) + + if req.CustomerID != nil { + customer, err := s.customer.GetCustomer(ctx, *req.CustomerID) + if err != nil { + logger.ContextLogger(ctx).Error("customer is not found", zap.Error(err)) + return nil, err + } + customerID = customer.ID + } + if err != nil { logger.ContextLogger(ctx).Error("failed to resolve customer", zap.Error(err)) return nil, err diff --git a/internal/services/v2/order/execute_order.go b/internal/services/v2/order/execute_order.go index 53bf5d2..42919e8 100644 --- a/internal/services/v2/order/execute_order.go +++ b/internal/services/v2/order/execute_order.go @@ -7,6 +7,7 @@ import ( "enaklo-pos-be/internal/entity" "fmt" "go.uber.org/zap" + "time" ) func (s *orderSvc) ExecuteOrderInquiry(ctx mycontext.Context, @@ -52,13 +53,12 @@ func (s *orderSvc) processPostOrderActions( } if order.CustomerID != nil && *order.CustomerID > 0 { - err = s.addCustomerPoints(ctx, *order.CustomerID, int(order.Total/50000), fmt.Sprintf("TRX #%s", trx.ID)) + err = s.addCustomerVouchers(ctx, *order.CustomerID, int64(order.Total), trx.OrderID) if err != nil { logger.ContextLogger(ctx).Error("error when adding points", zap.Error(err)) } } - s.sendTransactionReceipt(ctx, order, trx, "CASH") return nil } @@ -79,8 +79,64 @@ func (s *orderSvc) createTransaction(ctx mycontext.Context, order *entity.Order, return transaction, err } -func (s *orderSvc) addCustomerPoints(ctx mycontext.Context, customerID int64, points int, reference string) error { - return s.customer.AddPoints(ctx, customerID, points, reference) +func (s *orderSvc) addCustomerVouchers(ctx mycontext.Context, customerID int64, total int64, reference int64) error { + undians, err := s.voucherUndianRepo.GetActiveUndianEvents(ctx) + if err != nil { + return err + } + + eligibleVoucher := []*entity.UndianVoucherDB{} + totalVouchersNeeded := 0 + + for _, v := range undians { + if total >= int64(v.MinimumPurchase) { + voucherCount := int(total / int64(v.MinimumPurchase)) + totalVouchersNeeded += voucherCount + } + } + + if totalVouchersNeeded == 0 { + return nil + } + + startSequence, err := s.voucherUndianRepo.GetNextVoucherSequenceBatch(ctx, totalVouchersNeeded) + if err != nil { + return err + } + + currentSequence := startSequence + + for _, v := range undians { + if total >= int64(v.MinimumPurchase) { + voucherCount := int(total / int64(v.MinimumPurchase)) + + for i := 0; i < voucherCount; i++ { + voucherCode := s.generateVoucherCode(v.ID, reference, currentSequence) + + voucher := &entity.UndianVoucherDB{ + UndianEventID: v.ID, + CustomerID: customerID, + VoucherCode: voucherCode, + VoucherNumber: &i, + IsWinner: false, + CreatedAt: time.Now(), + } + + eligibleVoucher = append(eligibleVoucher, voucher) + currentSequence++ + } + } + } + + return s.voucherUndianRepo.CreateUndianVouchers(ctx, eligibleVoucher) +} + +func (s *orderSvc) generateVoucherCode(eventID int64, reference int64, sequence int64) string { + eventPart := eventID % 100 // Last 2 digits of event ID + sequencePart := sequence % 100000 // Last 5 digits of sequence + orderPart := reference % 1000 // Last 3 digits of order ID + + return fmt.Sprintf("%02d%05d%03d", eventPart, sequencePart, orderPart) } func (s *orderSvc) sendTransactionReceipt(ctx mycontext.Context, order *entity.Order, transaction *entity.Transaction, paymentMethod string) error { diff --git a/internal/services/v2/order/order.go b/internal/services/v2/order/order.go index 26dea36..047d025 100644 --- a/internal/services/v2/order/order.go +++ b/internal/services/v2/order/order.go @@ -116,16 +116,24 @@ type InProgressOrderRepository interface { GetListByPartnerID(ctx mycontext.Context, partnerID int64, limit, offset int) ([]*entity.InProgressOrder, error) } +type VoucherUndianRepo interface { + GetActiveUndianEvents(ctx context.Context) ([]*entity.UndianEventDB, error) + CreateUndianVouchers(ctx context.Context, vouchers []*entity.UndianVoucherDB) error + GetNextVoucherSequence(ctx mycontext.Context) (int64, error) + GetNextVoucherSequenceBatch(ctx mycontext.Context, count int) (int64, error) +} + type orderSvc struct { - repo Repository - product ProductService - customer CustomerService - transaction TransactionService - crypt CryptService - cfg Config - notification NotificationService - partnerSetting PartnerSettings - inprogressOrder InProgressOrderRepository + repo Repository + product ProductService + customer CustomerService + transaction TransactionService + crypt CryptService + cfg Config + notification NotificationService + partnerSetting PartnerSettings + inprogressOrder InProgressOrderRepository + voucherUndianRepo VoucherUndianRepo } func New( @@ -137,15 +145,17 @@ func New( cfg Config, notification NotificationService, partnerSetting PartnerSettings, + voucherUndianRepo VoucherUndianRepo, ) Service { return &orderSvc{ - repo: repo, - product: product, - customer: customer, - transaction: transaction, - crypt: crypt, - cfg: cfg, - notification: notification, - partnerSetting: partnerSetting, + repo: repo, + product: product, + customer: customer, + transaction: transaction, + crypt: crypt, + cfg: cfg, + notification: notification, + partnerSetting: partnerSetting, + voucherUndianRepo: voucherUndianRepo, } }