Update Voucher
This commit is contained in:
parent
b5d6f7ff5b
commit
54144b2eba
451
internal/entity/undian.go
Normal file
451
internal/entity/undian.go
Normal file
@ -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"
|
||||||
|
)
|
||||||
@ -59,6 +59,7 @@ type RepoManagerImpl struct {
|
|||||||
TransactionRepo TransactionRepo
|
TransactionRepo TransactionRepo
|
||||||
MemberRepository MemberRepository
|
MemberRepository MemberRepository
|
||||||
PartnerSetting PartnerSettingsRepository
|
PartnerSetting PartnerSettingsRepository
|
||||||
|
UndianRepository UndianRepo
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRepoManagerImpl(db *gorm.DB, cfg *config.Config) *RepoManagerImpl {
|
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),
|
MemberRepository: NewMemberRepository(db),
|
||||||
InProgressOrderRepo: NewInProgressOrderRepository(db),
|
InProgressOrderRepo: NewInProgressOrderRepository(db),
|
||||||
PartnerSetting: NewPartnerSettingsRepository(db),
|
PartnerSetting: NewPartnerSettingsRepository(db),
|
||||||
|
UndianRepository: NewUndianRepository(db),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
88
internal/repository/undian_repo.go
Normal file
88
internal/repository/undian_repo.go
Normal file
@ -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
|
||||||
|
}
|
||||||
@ -59,7 +59,7 @@ func NewServiceManagerImpl(cfg *config.Config, repo *repository.RepoManagerImpl)
|
|||||||
productSvcV2 := productSvc.New(repo.ProductRepo)
|
productSvcV2 := productSvc.New(repo.ProductRepo)
|
||||||
partnerSettings := partner_settings.NewPartnerSettingsService(repo.PartnerSetting)
|
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)
|
inprogressOrder := inprogress_order.NewInProgressOrderService(repo.OrderRepo, orderService, productSvcV2)
|
||||||
return &ServiceManagerImpl{
|
return &ServiceManagerImpl{
|
||||||
AuthSvc: auth.New(repo.Auth, repo.Crypto, repo.User, repo.EmailService, cfg.Email, repo.Trx, repo.License),
|
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),
|
Transaction: transaction.New(repo.Transaction, repo.Wallet, repo.Trx),
|
||||||
Balance: balance.NewBalanceService(repo.Wallet, repo.Trx, repo.Crypto, &cfg.Withdraw, repo.Transaction),
|
Balance: balance.NewBalanceService(repo.Wallet, repo.Trx, repo.Crypto, &cfg.Withdraw, repo.Transaction),
|
||||||
DiscoverService: discovery.NewDiscoveryService(repo.Site, cfg.Discovery, repo.Product),
|
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),
|
MemberRegistrationSvc: member.NewMemberRegistrationService(repo.MemberRepository, repo.EmailService, custSvcV2, repo.Crypto),
|
||||||
CustomerV2Svc: custSvcV2,
|
CustomerV2Svc: custSvcV2,
|
||||||
InProgressSvc: inprogressOrder,
|
InProgressSvc: inprogressOrder,
|
||||||
|
|||||||
@ -29,12 +29,17 @@ func (s *orderSvc) CreateOrderInquiry(ctx mycontext.Context,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
customerID, err := s.customer.ResolveCustomer(ctx, &entity.CustomerResolutionRequest{
|
customerID := int64(0)
|
||||||
ID: req.CustomerID,
|
|
||||||
Name: req.CustomerName,
|
if req.CustomerID != nil {
|
||||||
Email: req.CustomerEmail,
|
customer, err := s.customer.GetCustomer(ctx, *req.CustomerID)
|
||||||
PhoneNumber: req.CustomerPhoneNumber,
|
if err != nil {
|
||||||
})
|
logger.ContextLogger(ctx).Error("customer is not found", zap.Error(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
customerID = customer.ID
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.ContextLogger(ctx).Error("failed to resolve customer", zap.Error(err))
|
logger.ContextLogger(ctx).Error("failed to resolve customer", zap.Error(err))
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import (
|
|||||||
"enaklo-pos-be/internal/entity"
|
"enaklo-pos-be/internal/entity"
|
||||||
"fmt"
|
"fmt"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *orderSvc) ExecuteOrderInquiry(ctx mycontext.Context,
|
func (s *orderSvc) ExecuteOrderInquiry(ctx mycontext.Context,
|
||||||
@ -52,13 +53,12 @@ func (s *orderSvc) processPostOrderActions(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if order.CustomerID != nil && *order.CustomerID > 0 {
|
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 {
|
if err != nil {
|
||||||
logger.ContextLogger(ctx).Error("error when adding points", zap.Error(err))
|
logger.ContextLogger(ctx).Error("error when adding points", zap.Error(err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
s.sendTransactionReceipt(ctx, order, trx, "CASH")
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,8 +79,64 @@ func (s *orderSvc) createTransaction(ctx mycontext.Context, order *entity.Order,
|
|||||||
return transaction, err
|
return transaction, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *orderSvc) addCustomerPoints(ctx mycontext.Context, customerID int64, points int, reference string) error {
|
func (s *orderSvc) addCustomerVouchers(ctx mycontext.Context, customerID int64, total int64, reference int64) error {
|
||||||
return s.customer.AddPoints(ctx, customerID, points, reference)
|
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 {
|
func (s *orderSvc) sendTransactionReceipt(ctx mycontext.Context, order *entity.Order, transaction *entity.Transaction, paymentMethod string) error {
|
||||||
|
|||||||
@ -116,16 +116,24 @@ type InProgressOrderRepository interface {
|
|||||||
GetListByPartnerID(ctx mycontext.Context, partnerID int64, limit, offset int) ([]*entity.InProgressOrder, error)
|
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 {
|
type orderSvc struct {
|
||||||
repo Repository
|
repo Repository
|
||||||
product ProductService
|
product ProductService
|
||||||
customer CustomerService
|
customer CustomerService
|
||||||
transaction TransactionService
|
transaction TransactionService
|
||||||
crypt CryptService
|
crypt CryptService
|
||||||
cfg Config
|
cfg Config
|
||||||
notification NotificationService
|
notification NotificationService
|
||||||
partnerSetting PartnerSettings
|
partnerSetting PartnerSettings
|
||||||
inprogressOrder InProgressOrderRepository
|
inprogressOrder InProgressOrderRepository
|
||||||
|
voucherUndianRepo VoucherUndianRepo
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(
|
func New(
|
||||||
@ -137,15 +145,17 @@ func New(
|
|||||||
cfg Config,
|
cfg Config,
|
||||||
notification NotificationService,
|
notification NotificationService,
|
||||||
partnerSetting PartnerSettings,
|
partnerSetting PartnerSettings,
|
||||||
|
voucherUndianRepo VoucherUndianRepo,
|
||||||
) Service {
|
) Service {
|
||||||
return &orderSvc{
|
return &orderSvc{
|
||||||
repo: repo,
|
repo: repo,
|
||||||
product: product,
|
product: product,
|
||||||
customer: customer,
|
customer: customer,
|
||||||
transaction: transaction,
|
transaction: transaction,
|
||||||
crypt: crypt,
|
crypt: crypt,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
notification: notification,
|
notification: notification,
|
||||||
partnerSetting: partnerSetting,
|
partnerSetting: partnerSetting,
|
||||||
|
voucherUndianRepo: voucherUndianRepo,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user