Add Voucher Undian
This commit is contained in:
parent
54144b2eba
commit
fa8f0ad380
@ -1,56 +1,44 @@
|
|||||||
package entity
|
package entity
|
||||||
|
|
||||||
import (
|
import "time"
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// =============================================
|
|
||||||
// DATABASE ENTITIES
|
|
||||||
// =============================================
|
|
||||||
|
|
||||||
type UndianEventDB struct {
|
type UndianEventDB struct {
|
||||||
ID int64 `gorm:"primaryKey;autoIncrement" json:"id"`
|
ID int64 `gorm:"primaryKey;autoIncrement" json:"id"`
|
||||||
Title string `gorm:"size:255;not null" json:"title"`
|
Title string `gorm:"size:255;not null" json:"title"`
|
||||||
Description *string `gorm:"type:text" json:"description"`
|
Description *string `gorm:"type:text" json:"description"`
|
||||||
ImageURL *string `gorm:"size:500" json:"image_url"`
|
ImageURL *string `gorm:"size:500" json:"image_url"`
|
||||||
Status string `gorm:"size:20;not null;default:upcoming" json:"status"` // upcoming, active, completed, cancelled
|
Status string `gorm:"size:20;not null;default:upcoming" json:"status"`
|
||||||
|
StartDate time.Time `gorm:"not null" json:"start_date"`
|
||||||
// Event timing
|
EndDate time.Time `gorm:"not null" json:"end_date"`
|
||||||
StartDate time.Time `gorm:"not null" json:"start_date"`
|
DrawDate time.Time `gorm:"not null" json:"draw_date"`
|
||||||
EndDate time.Time `gorm:"not null" json:"end_date"`
|
MinimumPurchase float64 `gorm:"type:numeric(10,2);default:50000" json:"minimum_purchase"`
|
||||||
DrawDate time.Time `gorm:"not null" json:"draw_date"`
|
DrawCompleted bool `gorm:"default:false" json:"draw_completed"`
|
||||||
|
DrawCompletedAt *time.Time `json:"draw_completed_at"`
|
||||||
// Configuration
|
TermsAndConditions *string `gorm:"column:terms_and_conditions;type:text" json:"terms_and_conditions"`
|
||||||
MinimumPurchase float64 `gorm:"type:decimal(10,2);default:50000" json:"minimum_purchase"`
|
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||||
|
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
||||||
// Draw status
|
Prefix *string `json:"prefix"`
|
||||||
DrawCompleted bool `gorm:"default:false" json:"draw_completed"`
|
Prizes []UndianPrizeDB `gorm:"foreignKey:UndianEventID" json:"prizes,omitempty"`
|
||||||
DrawCompletedAt *time.Time `json:"draw_completed_at"`
|
Vouchers []UndianVoucherDB `gorm:"foreignKey:UndianEventID" json:"vouchers,omitempty"`
|
||||||
|
|
||||||
// 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 {
|
func (UndianEventDB) TableName() string {
|
||||||
return "undian_events"
|
return "undian_events"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UndianPrizeDB represents the undian_prizes table
|
||||||
type UndianPrizeDB struct {
|
type UndianPrizeDB struct {
|
||||||
ID int64 `gorm:"primaryKey;autoIncrement" json:"id"`
|
ID int64 `gorm:"primaryKey;autoIncrement" json:"id"`
|
||||||
UndianEventID int64 `gorm:"not null" json:"undian_event_id"`
|
UndianEventID int64 `gorm:"not null" json:"undian_event_id"`
|
||||||
Rank int `gorm:"not null" json:"rank"`
|
Rank int `gorm:"not null" json:"rank"`
|
||||||
PrizeName string `gorm:"size:255;not null" json:"prize_name"`
|
PrizeName string `gorm:"size:255;not null" json:"prize_name"`
|
||||||
PrizeValue *float64 `gorm:"type:decimal(15,2)" json:"prize_value"`
|
PrizeValue *float64 `gorm:"type:numeric(15,2)" json:"prize_value"`
|
||||||
PrizeDescription *string `gorm:"type:text" json:"prize_description"`
|
PrizeDescription *string `gorm:"type:text" json:"prize_description"`
|
||||||
PrizeType string `gorm:"size:50;default:voucher" json:"prize_type"` // gold, voucher, cash, product, service
|
PrizeType string `gorm:"size:50;default:voucher" json:"prize_type"`
|
||||||
PrizeImageURL *string `gorm:"size:500" json:"prize_image_url"`
|
PrizeImageURL *string `gorm:"size:500" json:"prize_image_url"`
|
||||||
|
WinningVoucherID *int64 `json:"winning_voucher_id"`
|
||||||
// Winner information (filled after draw)
|
WinnerUserID *int64 `json:"winner_user_id"`
|
||||||
WinningVoucherID *int64 `json:"winning_voucher_id"`
|
Amount *int64 `json:"amount"`
|
||||||
WinnerUserID *int64 `json:"winner_user_id"`
|
|
||||||
|
|
||||||
// Relations
|
// Relations
|
||||||
UndianEvent UndianEventDB `gorm:"foreignKey:UndianEventID" json:"undian_event,omitempty"`
|
UndianEvent UndianEventDB `gorm:"foreignKey:UndianEventID" json:"undian_event,omitempty"`
|
||||||
}
|
}
|
||||||
@ -59,22 +47,18 @@ func (UndianPrizeDB) TableName() string {
|
|||||||
return "undian_prizes"
|
return "undian_prizes"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UndianVoucherDB represents the undian_vouchers table
|
||||||
type UndianVoucherDB struct {
|
type UndianVoucherDB struct {
|
||||||
ID int64 `gorm:"primaryKey;autoIncrement" json:"id"`
|
ID int64 `gorm:"primaryKey;autoIncrement" json:"id"`
|
||||||
UndianEventID int64 `gorm:"not null" json:"undian_event_id"`
|
UndianEventID int64 `gorm:"not null" json:"undian_event_id"`
|
||||||
CustomerID int64 `gorm:"not null" json:"customer_id"`
|
CustomerID int64 `gorm:"not null" json:"customer_id"`
|
||||||
OrderID *int64 `json:"order_id"`
|
OrderID *int64 `json:"order_id"`
|
||||||
|
VoucherCode string `gorm:"size:50;not null;uniqueIndex" json:"voucher_code"`
|
||||||
// Voucher identification
|
VoucherNumber *int `json:"voucher_number"`
|
||||||
VoucherCode string `gorm:"size:50;not null;uniqueIndex" json:"voucher_code"`
|
IsWinner bool `gorm:"default:false" json:"is_winner"`
|
||||||
VoucherNumber *int `json:"voucher_number"`
|
PrizeRank *int `json:"prize_rank"`
|
||||||
|
WonAt *time.Time `json:"won_at"`
|
||||||
// Winner status
|
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||||
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
|
// Relations
|
||||||
UndianEvent UndianEventDB `gorm:"foreignKey:UndianEventID" json:"undian_event,omitempty"`
|
UndianEvent UndianEventDB `gorm:"foreignKey:UndianEventID" json:"undian_event,omitempty"`
|
||||||
@ -84,368 +68,51 @@ func (UndianVoucherDB) TableName() string {
|
|||||||
return "undian_vouchers"
|
return "undian_vouchers"
|
||||||
}
|
}
|
||||||
|
|
||||||
// =============================================
|
// Response Models
|
||||||
// REQUEST/RESPONSE ENTITIES
|
type UndianListResponse struct {
|
||||||
// =============================================
|
Events []*UndianEventResponse `json:"events"`
|
||||||
|
|
||||||
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 {
|
type UndianEventResponse struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Description *string `json:"description"`
|
Description *string `json:"description"`
|
||||||
ImageURL *string `json:"image_url"`
|
ImageURL *string `json:"image_url"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
StartDate time.Time `json:"start_date"`
|
StartDate time.Time `json:"start_date"`
|
||||||
EndDate time.Time `json:"end_date"`
|
EndDate time.Time `json:"end_date"`
|
||||||
DrawDate time.Time `json:"draw_date"`
|
DrawDate time.Time `json:"draw_date"`
|
||||||
MinimumPurchase float64 `json:"minimum_purchase"`
|
MinimumPurchase float64 `json:"minimum_purchase"`
|
||||||
DrawCompleted bool `json:"draw_completed"`
|
DrawCompleted bool `json:"draw_completed"`
|
||||||
DrawCompletedAt *time.Time `json:"draw_completed_at"`
|
DrawCompletedAt *time.Time `json:"draw_completed_at"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
TermsConditions *string `json:"terms_and_conditions"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
Prefix *string `json:"prefix"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
// Statistics (populated when needed)
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
TotalVouchers int64 `json:"total_vouchers,omitempty"`
|
VoucherCount int `json:"voucher_count"`
|
||||||
TotalParticipants int64 `json:"total_participants,omitempty"`
|
Vouchers []*UndianVoucherResponse `json:"vouchers"`
|
||||||
TotalPrizes int64 `json:"total_prizes,omitempty"`
|
Prizes []*UndianPrizeResponse `json:"prizes"`
|
||||||
|
|
||||||
// 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 {
|
type UndianVoucherResponse struct {
|
||||||
ID int64 `json:"id"`
|
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"`
|
VoucherCode string `json:"voucher_code"`
|
||||||
VoucherNumber *int `json:"voucher_number"`
|
VoucherNumber *int `json:"voucher_number"`
|
||||||
IsWinner bool `json:"is_winner"`
|
IsWinner bool `json:"is_winner"`
|
||||||
PrizeRank *int `json:"prize_rank"`
|
PrizeRank *int `json:"prize_rank"`
|
||||||
WonAt *time.Time `json:"won_at"`
|
WonAt *time.Time `json:"won_at"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
|
||||||
// Relations (populated when needed)
|
|
||||||
UndianEvent *UndianEventResponse `json:"undian_event,omitempty"`
|
|
||||||
Prize *UndianPrizeResponse `json:"prize,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// =============================================
|
type UndianPrizeResponse struct {
|
||||||
// COMPOSITE ENTITIES
|
ID int64 `json:"id"`
|
||||||
// =============================================
|
Rank int `json:"rank"`
|
||||||
|
PrizeName string `json:"prize_name"`
|
||||||
type UndianEventWithStats struct {
|
PrizeValue *float64 `json:"prize_value"`
|
||||||
Event UndianEventDB `json:"event"`
|
PrizeDescription *string `json:"prize_description"`
|
||||||
TotalVouchers int64 `json:"total_vouchers"`
|
PrizeType string `json:"prize_type"`
|
||||||
TotalParticipants int64 `json:"total_participants"`
|
PrizeImageURL *string `json:"prize_image_url"`
|
||||||
TotalPrizes int64 `json:"total_prizes"`
|
WinningVoucherID *int64 `json:"winning_voucher_id"`
|
||||||
|
WinnerUserID *int64 `json:"winner_user_id"`
|
||||||
|
Amount *int64 `json:"amount"`
|
||||||
}
|
}
|
||||||
|
|
||||||
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"
|
|
||||||
)
|
|
||||||
|
|||||||
164
internal/handlers/http/customer_undian.go
Normal file
164
internal/handlers/http/customer_undian.go
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"enaklo-pos-be/internal/entity"
|
||||||
|
"enaklo-pos-be/internal/handlers/request"
|
||||||
|
"enaklo-pos-be/internal/handlers/response"
|
||||||
|
"enaklo-pos-be/internal/services/v2/undian"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CustomerUndianHandler struct {
|
||||||
|
undianService undian.Service
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCustomerUndianHandler(undianService undian.Service) *CustomerUndianHandler {
|
||||||
|
return &CustomerUndianHandler{
|
||||||
|
undianService: undianService,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *CustomerUndianHandler) Route(group *gin.RouterGroup, jwt gin.HandlerFunc) {
|
||||||
|
route := group.Group("/undian")
|
||||||
|
|
||||||
|
route.GET("/list", jwt, h.GetUndianList)
|
||||||
|
route.GET("/events", jwt, h.GetActiveEvents)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *CustomerUndianHandler) GetUndianList(c *gin.Context) {
|
||||||
|
ctx := request.GetMyContext(c)
|
||||||
|
userID := ctx.RequestedBy()
|
||||||
|
|
||||||
|
undianResponse, err := h.undianService.GetUndianList(ctx, userID)
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorWrapper(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
responseData := h.mapToUndianListResponse(undianResponse)
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, response.BaseResponse{
|
||||||
|
Success: true,
|
||||||
|
Status: http.StatusOK,
|
||||||
|
Data: responseData,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *CustomerUndianHandler) GetActiveEvents(c *gin.Context) {
|
||||||
|
ctx := request.GetMyContext(c)
|
||||||
|
|
||||||
|
events, err := h.undianService.GetActiveUndianEvents(ctx)
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorWrapper(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
responseData := h.mapToActiveEventsResponse(events)
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, response.BaseResponse{
|
||||||
|
Success: true,
|
||||||
|
Status: http.StatusOK,
|
||||||
|
Data: responseData,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *CustomerUndianHandler) mapToUndianListResponse(undianResponse *entity.UndianListResponse) response.UndianListResponse {
|
||||||
|
events := make([]response.UndianEventResponse, 0, len(undianResponse.Events))
|
||||||
|
|
||||||
|
for _, event := range undianResponse.Events {
|
||||||
|
vouchers := make([]response.UndianVoucherResponse, 0, len(event.Vouchers))
|
||||||
|
for _, voucher := range event.Vouchers {
|
||||||
|
vouchers = append(vouchers, response.UndianVoucherResponse{
|
||||||
|
ID: voucher.ID,
|
||||||
|
VoucherCode: voucher.VoucherCode,
|
||||||
|
VoucherNumber: voucher.VoucherNumber,
|
||||||
|
IsWinner: voucher.IsWinner,
|
||||||
|
PrizeRank: voucher.PrizeRank,
|
||||||
|
WonAt: h.formatTimePointer(voucher.WonAt),
|
||||||
|
CreatedAt: voucher.CreatedAt.Format("2006-01-02T15:04:05Z"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
prizes := make([]response.UndianPrizeResponse, 0, len(event.Prizes))
|
||||||
|
for _, prize := range event.Prizes {
|
||||||
|
prizes = append(prizes, response.UndianPrizeResponse{
|
||||||
|
ID: prize.ID,
|
||||||
|
Rank: prize.Rank,
|
||||||
|
PrizeName: prize.PrizeName,
|
||||||
|
PrizeValue: prize.PrizeValue,
|
||||||
|
PrizeDescription: prize.PrizeDescription,
|
||||||
|
PrizeType: prize.PrizeType,
|
||||||
|
PrizeImageURL: prize.PrizeImageURL,
|
||||||
|
WinningVoucherID: prize.WinningVoucherID,
|
||||||
|
WinnerUserID: prize.WinnerUserID,
|
||||||
|
IsWon: prize.WinningVoucherID != nil,
|
||||||
|
Amount: prize.Amount,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
events = append(events, response.UndianEventResponse{
|
||||||
|
ID: event.ID,
|
||||||
|
Title: event.Title,
|
||||||
|
Description: event.Description,
|
||||||
|
ImageURL: event.ImageURL,
|
||||||
|
Status: event.Status,
|
||||||
|
StartDate: event.StartDate.Format("2006-01-02T15:04:05Z"),
|
||||||
|
EndDate: event.EndDate.Format("2006-01-02T15:04:05Z"),
|
||||||
|
DrawDate: event.DrawDate.Format("2006-01-02T15:04:05Z"),
|
||||||
|
MinimumPurchase: event.MinimumPurchase,
|
||||||
|
DrawCompleted: event.DrawCompleted,
|
||||||
|
DrawCompletedAt: h.formatTimePointer(event.DrawCompletedAt),
|
||||||
|
TermsConditions: event.TermsConditions,
|
||||||
|
Prefix: event.Prefix,
|
||||||
|
CreatedAt: event.CreatedAt.Format("2006-01-02T15:04:05Z"),
|
||||||
|
UpdatedAt: event.UpdatedAt.Format("2006-01-02T15:04:05Z"),
|
||||||
|
VoucherCount: event.VoucherCount,
|
||||||
|
Vouchers: vouchers,
|
||||||
|
Prizes: prizes,
|
||||||
|
TotalPrizes: len(prizes),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.UndianListResponse{
|
||||||
|
Events: events,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *CustomerUndianHandler) mapToActiveEventsResponse(events []*entity.UndianEventDB) response.ActiveEventsResponse {
|
||||||
|
eventResponses := make([]response.ActiveEventResponse, 0, len(events))
|
||||||
|
|
||||||
|
for _, event := range events {
|
||||||
|
eventResponses = append(eventResponses, response.ActiveEventResponse{
|
||||||
|
ID: event.ID,
|
||||||
|
Title: event.Title,
|
||||||
|
Description: event.Description,
|
||||||
|
ImageURL: event.ImageURL,
|
||||||
|
Status: event.Status,
|
||||||
|
StartDate: event.StartDate.Format("2006-01-02T15:04:05Z"),
|
||||||
|
EndDate: event.EndDate.Format("2006-01-02T15:04:05Z"),
|
||||||
|
DrawDate: event.DrawDate.Format("2006-01-02T15:04:05Z"),
|
||||||
|
MinimumPurchase: event.MinimumPurchase,
|
||||||
|
DrawCompleted: event.DrawCompleted,
|
||||||
|
DrawCompletedAt: h.formatTimePointer(event.DrawCompletedAt),
|
||||||
|
TermsConditions: event.TermsAndConditions,
|
||||||
|
Prefix: event.Prefix,
|
||||||
|
CreatedAt: event.CreatedAt.Format("2006-01-02T15:04:05Z"),
|
||||||
|
UpdatedAt: event.UpdatedAt.Format("2006-01-02T15:04:05Z"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.ActiveEventsResponse{
|
||||||
|
Events: eventResponses,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatTimePointer formats time pointer to string
|
||||||
|
func (h *CustomerUndianHandler) formatTimePointer(t *time.Time) *string {
|
||||||
|
if t == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
formatted := t.Format("2006-01-02T15:04:05Z")
|
||||||
|
return &formatted
|
||||||
|
}
|
||||||
79
internal/handlers/response/undian.go
Normal file
79
internal/handlers/response/undian.go
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
package response
|
||||||
|
|
||||||
|
// UndianListResponse represents the response for undian list API
|
||||||
|
type UndianListResponse struct {
|
||||||
|
Events []UndianEventResponse `json:"events"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UndianEventResponse represents an undian event with customer's vouchers and prizes
|
||||||
|
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 string `json:"start_date"`
|
||||||
|
EndDate string `json:"end_date"`
|
||||||
|
DrawDate string `json:"draw_date"`
|
||||||
|
MinimumPurchase float64 `json:"minimum_purchase"`
|
||||||
|
DrawCompleted bool `json:"draw_completed"`
|
||||||
|
DrawCompletedAt *string `json:"draw_completed_at"`
|
||||||
|
TermsConditions *string `json:"terms_and_conditions"`
|
||||||
|
Prefix *string `json:"prefix"`
|
||||||
|
CreatedAt string `json:"created_at"`
|
||||||
|
UpdatedAt string `json:"updated_at"`
|
||||||
|
VoucherCount int `json:"voucher_count"`
|
||||||
|
TotalPrizes int `json:"total_prizes"`
|
||||||
|
Vouchers []UndianVoucherResponse `json:"vouchers"`
|
||||||
|
Prizes []UndianPrizeResponse `json:"prizes"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UndianVoucherResponse represents a customer's voucher
|
||||||
|
type UndianVoucherResponse struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
VoucherCode string `json:"voucher_code"`
|
||||||
|
VoucherNumber *int `json:"voucher_number"`
|
||||||
|
IsWinner bool `json:"is_winner"`
|
||||||
|
PrizeRank *int `json:"prize_rank"`
|
||||||
|
WonAt *string `json:"won_at"`
|
||||||
|
CreatedAt string `json:"created_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UndianPrizeResponse represents a prize in the undian event
|
||||||
|
type UndianPrizeResponse struct {
|
||||||
|
ID int64 `json:"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"`
|
||||||
|
IsWon bool `json:"is_won"`
|
||||||
|
Amount *int64 `json:"amount"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ActiveEventsResponse represents the response for active events API (without customer data)
|
||||||
|
type ActiveEventsResponse struct {
|
||||||
|
Events []ActiveEventResponse `json:"events"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ActiveEventResponse represents an active undian event (without customer-specific data)
|
||||||
|
type ActiveEventResponse struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Description *string `json:"description"`
|
||||||
|
ImageURL *string `json:"image_url"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
StartDate string `json:"start_date"`
|
||||||
|
EndDate string `json:"end_date"`
|
||||||
|
DrawDate string `json:"draw_date"`
|
||||||
|
MinimumPurchase float64 `json:"minimum_purchase"`
|
||||||
|
DrawCompleted bool `json:"draw_completed"`
|
||||||
|
DrawCompletedAt *string `json:"draw_completed_at"`
|
||||||
|
TermsConditions *string `json:"terms_and_conditions"`
|
||||||
|
Prefix *string `json:"prefix"`
|
||||||
|
CreatedAt string `json:"created_at"`
|
||||||
|
UpdatedAt string `json:"updated_at"`
|
||||||
|
}
|
||||||
@ -72,6 +72,34 @@ func CustomerAuthorizationMiddleware(cryp repository.Crypto) gin.HandlerFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func OptionalCustomerAuthorizationMiddleware(cryp repository.Crypto) gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
tokenString := c.GetHeader("Authorization")
|
||||||
|
if tokenString == "" {
|
||||||
|
c.Next()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenString = strings.TrimPrefix(tokenString, "Bearer ")
|
||||||
|
|
||||||
|
claims, err := cryp.ParseAndValidateJWTCustomer(tokenString)
|
||||||
|
if err != nil {
|
||||||
|
c.Next()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
customCtx, err := mycontext.NewMyContextCustomer(c.Request.Context(), claims)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "error initialize context"})
|
||||||
|
c.Next()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Set("myCtx", customCtx)
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func SuperAdminMiddleware() gin.HandlerFunc {
|
func SuperAdminMiddleware() gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
ctx, exists := c.Get("myCtx")
|
ctx, exists := c.Get("myCtx")
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
package repository
|
package repository
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"enaklo-pos-be/internal/common/logger"
|
"enaklo-pos-be/internal/common/logger"
|
||||||
"enaklo-pos-be/internal/common/mycontext"
|
"enaklo-pos-be/internal/common/mycontext"
|
||||||
"enaklo-pos-be/internal/entity"
|
"enaklo-pos-be/internal/entity"
|
||||||
@ -13,9 +12,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type UndianRepo interface {
|
type UndianRepo interface {
|
||||||
GetUndianEventByID(ctx context.Context, id int64) (*entity.UndianEventDB, error)
|
GetUndianEventByID(ctx mycontext.Context, id int64) (*entity.UndianEventDB, error)
|
||||||
GetActiveUndianEvents(ctx context.Context) ([]*entity.UndianEventDB, error)
|
GetActiveUndianEvents(ctx mycontext.Context) ([]*entity.UndianEventDB, error)
|
||||||
CreateUndianVouchers(ctx context.Context, vouchers []*entity.UndianVoucherDB) error
|
GetActiveUndianEventsWithPrizes(ctx mycontext.Context, customerID int64) ([]*entity.UndianEventDB, error)
|
||||||
|
GetCustomerVouchersByEventIDs(ctx mycontext.Context, customerID int64, eventIDs []int64) ([]*entity.UndianVoucherDB, error)
|
||||||
|
CreateUndianVouchers(ctx mycontext.Context, vouchers []*entity.UndianVoucherDB) error
|
||||||
GetNextVoucherSequence(ctx mycontext.Context) (int64, error)
|
GetNextVoucherSequence(ctx mycontext.Context) (int64, error)
|
||||||
GetNextVoucherSequenceBatch(ctx mycontext.Context, count int) (int64, error)
|
GetNextVoucherSequenceBatch(ctx mycontext.Context, count int) (int64, error)
|
||||||
}
|
}
|
||||||
@ -24,13 +25,13 @@ type undianRepository struct {
|
|||||||
db *gorm.DB
|
db *gorm.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUndianRepository(db *gorm.DB) *undianRepository {
|
func NewUndianRepository(db *gorm.DB) UndianRepo {
|
||||||
return &undianRepository{
|
return &undianRepository{
|
||||||
db: db,
|
db: db,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *undianRepository) GetUndianEventByID(ctx context.Context, id int64) (*entity.UndianEventDB, error) {
|
func (r *undianRepository) GetUndianEventByID(ctx mycontext.Context, id int64) (*entity.UndianEventDB, error) {
|
||||||
event := new(entity.UndianEventDB)
|
event := new(entity.UndianEventDB)
|
||||||
if err := r.db.WithContext(ctx).First(event, id).Error; err != nil {
|
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))
|
logger.ContextLogger(ctx).Error("error when get undian event by id", zap.Error(err))
|
||||||
@ -39,12 +40,13 @@ func (r *undianRepository) GetUndianEventByID(ctx context.Context, id int64) (*e
|
|||||||
return event, nil
|
return event, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *undianRepository) GetActiveUndianEvents(ctx context.Context) ([]*entity.UndianEventDB, error) {
|
func (r *undianRepository) GetActiveUndianEvents(ctx mycontext.Context) ([]*entity.UndianEventDB, error) {
|
||||||
var events []*entity.UndianEventDB
|
var events []*entity.UndianEventDB
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
|
||||||
if err := r.db.WithContext(ctx).
|
if err := r.db.WithContext(ctx).
|
||||||
Where("status = 'active' AND start_date <= ? AND end_date >= ?", now, now).
|
Where("status = 'active' AND start_date <= ? AND end_date >= ?", now, now).
|
||||||
|
Order("created_at DESC").
|
||||||
Find(&events).Error; err != nil {
|
Find(&events).Error; err != nil {
|
||||||
logger.ContextLogger(ctx).Error("error when get active undian events", zap.Error(err))
|
logger.ContextLogger(ctx).Error("error when get active undian events", zap.Error(err))
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -53,7 +55,57 @@ func (r *undianRepository) GetActiveUndianEvents(ctx context.Context) ([]*entity
|
|||||||
return events, nil
|
return events, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *undianRepository) CreateUndianVouchers(ctx context.Context, vouchers []*entity.UndianVoucherDB) error {
|
func (r *undianRepository) GetActiveUndianEventsWithPrizes(ctx mycontext.Context, customerID int64) ([]*entity.UndianEventDB, error) {
|
||||||
|
var events []*entity.UndianEventDB
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
query := r.db.WithContext(ctx).
|
||||||
|
Preload("Prizes", func(db *gorm.DB) *gorm.DB {
|
||||||
|
return db.Order("rank ASC")
|
||||||
|
}).
|
||||||
|
Where("status = 'active' AND start_date <= ? AND end_date >= ?", now, now).
|
||||||
|
Order("created_at DESC")
|
||||||
|
|
||||||
|
// If customer ID is provided, preload only that customer's vouchers
|
||||||
|
if customerID > 0 {
|
||||||
|
query = query.Preload("Vouchers", func(db *gorm.DB) *gorm.DB {
|
||||||
|
return db.Where("customer_id = ?", customerID).Order("created_at ASC")
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// If no customer ID, don't load any vouchers (or load all if needed)
|
||||||
|
query = query.Preload("Vouchers", func(db *gorm.DB) *gorm.DB {
|
||||||
|
return db.Where("1 = 0") // This loads no vouchers
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := query.Find(&events).Error; err != nil {
|
||||||
|
logger.ContextLogger(ctx).Error("error when get active undian events with prizes",
|
||||||
|
zap.Int64("customerID", customerID),
|
||||||
|
zap.Error(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return events, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *undianRepository) GetCustomerVouchersByEventIDs(ctx mycontext.Context, customerID int64, eventIDs []int64) ([]*entity.UndianVoucherDB, error) {
|
||||||
|
var vouchers []*entity.UndianVoucherDB
|
||||||
|
|
||||||
|
if err := r.db.WithContext(ctx).
|
||||||
|
Where("customer_id = ? AND undian_event_id IN ?", customerID, eventIDs).
|
||||||
|
Order("undian_event_id ASC, created_at ASC").
|
||||||
|
Find(&vouchers).Error; err != nil {
|
||||||
|
logger.ContextLogger(ctx).Error("error when get customer vouchers by event IDs",
|
||||||
|
zap.Int64("customerID", customerID),
|
||||||
|
zap.Int64s("eventIDs", eventIDs),
|
||||||
|
zap.Error(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return vouchers, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *undianRepository) CreateUndianVouchers(ctx mycontext.Context, vouchers []*entity.UndianVoucherDB) error {
|
||||||
err := r.db.WithContext(ctx).Create(&vouchers).Error
|
err := r.db.WithContext(ctx).Create(&vouchers).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.ContextLogger(ctx).Error("error when create undian vouchers", zap.Error(err))
|
logger.ContextLogger(ctx).Error("error when create undian vouchers", zap.Error(err))
|
||||||
@ -85,4 +137,4 @@ func (r *undianRepository) GetNextVoucherSequenceBatch(ctx mycontext.Context, co
|
|||||||
}
|
}
|
||||||
|
|
||||||
return startSequence, nil
|
return startSequence, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,6 +16,7 @@ func RegisterCustomerRoutes(app *app.Server, serviceManager *services.ServiceMan
|
|||||||
approute := app.Group("/api/v1/customer")
|
approute := app.Group("/api/v1/customer")
|
||||||
|
|
||||||
authMiddleware := middlewares.CustomerAuthorizationMiddleware(repoManager.Crypto)
|
authMiddleware := middlewares.CustomerAuthorizationMiddleware(repoManager.Crypto)
|
||||||
|
optionlMiddleWare := middlewares.OptionalCustomerAuthorizationMiddleware(repoManager.Crypto)
|
||||||
|
|
||||||
serverRoutes := []HTTPHandlerRoutes{
|
serverRoutes := []HTTPHandlerRoutes{
|
||||||
discovery.NewHandler(serviceManager.DiscoverService),
|
discovery.NewHandler(serviceManager.DiscoverService),
|
||||||
@ -27,4 +28,6 @@ func RegisterCustomerRoutes(app *app.Server, serviceManager *services.ServiceMan
|
|||||||
for _, handler := range serverRoutes {
|
for _, handler := range serverRoutes {
|
||||||
handler.Route(approute, authMiddleware)
|
handler.Route(approute, authMiddleware)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
http.NewCustomerUndianHandler(serviceManager.UndianSvc).Route(approute, optionlMiddleWare)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -44,7 +44,6 @@ func RegisterPrivateRoutes(app *app.Server, serviceManager *services.ServiceMana
|
|||||||
approute := app.Group("/api/v1")
|
approute := app.Group("/api/v1")
|
||||||
|
|
||||||
authMiddleware := middlewares.AuthorizationMiddleware(repoManager.Crypto)
|
authMiddleware := middlewares.AuthorizationMiddleware(repoManager.Crypto)
|
||||||
|
|
||||||
serverRoutes := []HTTPHandlerRoutes{
|
serverRoutes := []HTTPHandlerRoutes{
|
||||||
auth.NewAuthHandler(serviceManager.AuthSvc),
|
auth.NewAuthHandler(serviceManager.AuthSvc),
|
||||||
event.NewHandler(serviceManager.EventSvc),
|
event.NewHandler(serviceManager.EventSvc),
|
||||||
|
|||||||
@ -18,6 +18,7 @@ import (
|
|||||||
customerSvc "enaklo-pos-be/internal/services/v2/customer"
|
customerSvc "enaklo-pos-be/internal/services/v2/customer"
|
||||||
"enaklo-pos-be/internal/services/v2/inprogress_order"
|
"enaklo-pos-be/internal/services/v2/inprogress_order"
|
||||||
orderSvc "enaklo-pos-be/internal/services/v2/order"
|
orderSvc "enaklo-pos-be/internal/services/v2/order"
|
||||||
|
"enaklo-pos-be/internal/services/v2/undian"
|
||||||
|
|
||||||
"enaklo-pos-be/internal/services/v2/partner_settings"
|
"enaklo-pos-be/internal/services/v2/partner_settings"
|
||||||
productSvc "enaklo-pos-be/internal/services/v2/product"
|
productSvc "enaklo-pos-be/internal/services/v2/product"
|
||||||
@ -51,6 +52,7 @@ type ServiceManagerImpl struct {
|
|||||||
MemberRegistrationSvc member.RegistrationService
|
MemberRegistrationSvc member.RegistrationService
|
||||||
InProgressSvc inprogress_order.InProgressOrderService
|
InProgressSvc inprogress_order.InProgressOrderService
|
||||||
AuthV2Svc authSvc.Service
|
AuthV2Svc authSvc.Service
|
||||||
|
UndianSvc undian.Service
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServiceManagerImpl(cfg *config.Config, repo *repository.RepoManagerImpl) *ServiceManagerImpl {
|
func NewServiceManagerImpl(cfg *config.Config, repo *repository.RepoManagerImpl) *ServiceManagerImpl {
|
||||||
@ -81,6 +83,7 @@ func NewServiceManagerImpl(cfg *config.Config, repo *repository.RepoManagerImpl)
|
|||||||
InProgressSvc: inprogressOrder,
|
InProgressSvc: inprogressOrder,
|
||||||
ProductV2Svc: productSvcV2,
|
ProductV2Svc: productSvcV2,
|
||||||
AuthV2Svc: authSvc.New(repo.CustomerRepo, repo.Crypto),
|
AuthV2Svc: authSvc.New(repo.CustomerRepo, repo.Crypto),
|
||||||
|
UndianSvc: undian.New(repo.UndianRepository),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -117,10 +117,9 @@ type InProgressOrderRepository interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type VoucherUndianRepo interface {
|
type VoucherUndianRepo interface {
|
||||||
GetActiveUndianEvents(ctx context.Context) ([]*entity.UndianEventDB, error)
|
GetActiveUndianEvents(ctx mycontext.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)
|
GetNextVoucherSequenceBatch(ctx mycontext.Context, count int) (int64, error)
|
||||||
|
CreateUndianVouchers(ctx mycontext.Context, vouchers []*entity.UndianVoucherDB) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type orderSvc struct {
|
type orderSvc struct {
|
||||||
|
|||||||
123
internal/services/v2/undian/undian.go
Normal file
123
internal/services/v2/undian/undian.go
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
package undian
|
||||||
|
|
||||||
|
import (
|
||||||
|
"enaklo-pos-be/internal/common/logger"
|
||||||
|
"enaklo-pos-be/internal/common/mycontext"
|
||||||
|
"enaklo-pos-be/internal/entity"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Service interface {
|
||||||
|
GetUndianList(ctx mycontext.Context, customerID int64) (*entity.UndianListResponse, error)
|
||||||
|
GetActiveUndianEvents(ctx mycontext.Context) ([]*entity.UndianEventDB, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Repository interface {
|
||||||
|
GetUndianEventByID(ctx mycontext.Context, id int64) (*entity.UndianEventDB, error)
|
||||||
|
GetActiveUndianEvents(ctx mycontext.Context) ([]*entity.UndianEventDB, error)
|
||||||
|
GetActiveUndianEventsWithPrizes(ctx mycontext.Context, customerID int64) ([]*entity.UndianEventDB, error)
|
||||||
|
GetCustomerVouchersByEventIDs(ctx mycontext.Context, customerID int64, eventIDs []int64) ([]*entity.UndianVoucherDB, error)
|
||||||
|
CreateUndianVouchers(ctx mycontext.Context, vouchers []*entity.UndianVoucherDB) error
|
||||||
|
GetNextVoucherSequence(ctx mycontext.Context) (int64, error)
|
||||||
|
GetNextVoucherSequenceBatch(ctx mycontext.Context, count int) (int64, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type undianSvc struct {
|
||||||
|
repo Repository
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(repo Repository) Service {
|
||||||
|
return &undianSvc{
|
||||||
|
repo: repo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *undianSvc) GetUndianList(ctx mycontext.Context, customerID int64) (*entity.UndianListResponse, error) {
|
||||||
|
events, err := s.repo.GetActiveUndianEventsWithPrizes(ctx, customerID)
|
||||||
|
if err != nil {
|
||||||
|
logger.ContextLogger(ctx).Error("failed to get active undian events with prizes",
|
||||||
|
zap.Int64("customerID", customerID),
|
||||||
|
zap.Error(err))
|
||||||
|
return nil, errors.Wrap(err, "failed to get active undian events with prizes")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(events) == 0 {
|
||||||
|
return &entity.UndianListResponse{
|
||||||
|
Events: []*entity.UndianEventResponse{},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build response
|
||||||
|
eventResponses := make([]*entity.UndianEventResponse, 0, len(events))
|
||||||
|
for _, event := range events {
|
||||||
|
voucherResponses := make([]*entity.UndianVoucherResponse, 0, len(event.Vouchers))
|
||||||
|
for _, voucher := range event.Vouchers {
|
||||||
|
voucherResponse := &entity.UndianVoucherResponse{
|
||||||
|
ID: voucher.ID,
|
||||||
|
VoucherCode: voucher.VoucherCode,
|
||||||
|
VoucherNumber: voucher.VoucherNumber,
|
||||||
|
IsWinner: voucher.IsWinner,
|
||||||
|
PrizeRank: voucher.PrizeRank,
|
||||||
|
WonAt: voucher.WonAt,
|
||||||
|
CreatedAt: voucher.CreatedAt,
|
||||||
|
}
|
||||||
|
voucherResponses = append(voucherResponses, voucherResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert prizes to response format
|
||||||
|
prizeResponses := make([]*entity.UndianPrizeResponse, 0, len(event.Prizes))
|
||||||
|
for _, prize := range event.Prizes {
|
||||||
|
prizeResponse := &entity.UndianPrizeResponse{
|
||||||
|
ID: prize.ID,
|
||||||
|
Rank: prize.Rank,
|
||||||
|
PrizeName: prize.PrizeName,
|
||||||
|
PrizeValue: prize.PrizeValue,
|
||||||
|
PrizeDescription: prize.PrizeDescription,
|
||||||
|
PrizeType: prize.PrizeType,
|
||||||
|
PrizeImageURL: prize.PrizeImageURL,
|
||||||
|
WinningVoucherID: prize.WinningVoucherID,
|
||||||
|
WinnerUserID: prize.WinnerUserID,
|
||||||
|
Amount: prize.Amount,
|
||||||
|
}
|
||||||
|
prizeResponses = append(prizeResponses, prizeResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
eventResponse := &entity.UndianEventResponse{
|
||||||
|
ID: event.ID,
|
||||||
|
Title: event.Title,
|
||||||
|
Description: event.Description,
|
||||||
|
ImageURL: event.ImageURL,
|
||||||
|
Status: event.Status,
|
||||||
|
StartDate: event.StartDate,
|
||||||
|
EndDate: event.EndDate,
|
||||||
|
DrawDate: event.DrawDate,
|
||||||
|
MinimumPurchase: event.MinimumPurchase,
|
||||||
|
DrawCompleted: event.DrawCompleted,
|
||||||
|
DrawCompletedAt: event.DrawCompletedAt,
|
||||||
|
TermsConditions: event.TermsAndConditions,
|
||||||
|
Prefix: event.Prefix,
|
||||||
|
CreatedAt: event.CreatedAt,
|
||||||
|
UpdatedAt: event.UpdatedAt,
|
||||||
|
VoucherCount: len(voucherResponses), // Fixed: use voucherResponses instead of eventVouchers
|
||||||
|
Vouchers: voucherResponses, // Fixed: use voucherResponses instead of eventVouchers
|
||||||
|
Prizes: prizeResponses,
|
||||||
|
}
|
||||||
|
|
||||||
|
eventResponses = append(eventResponses, eventResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &entity.UndianListResponse{
|
||||||
|
Events: eventResponses,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *undianSvc) GetActiveUndianEvents(ctx mycontext.Context) ([]*entity.UndianEventDB, error) {
|
||||||
|
events, err := s.repo.GetActiveUndianEvents(ctx)
|
||||||
|
if err != nil {
|
||||||
|
logger.ContextLogger(ctx).Error("failed to get active undian events", zap.Error(err))
|
||||||
|
return nil, errors.Wrap(err, "failed to get active undian events")
|
||||||
|
}
|
||||||
|
|
||||||
|
return events, nil
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user