package models import ( "encoding/json" "time" "github.com/google/uuid" "apskel-pos-be/internal/entities" ) // EnakGameInput is what an admin sends to create or change a game // (docs/rfc-enakgame.md §11). On a change, fields left out keep their value. type EnakGameInput struct { Name string `json:"name"` // SPIN, RAFFLE or MINIGAME; MINIGAME when left out on create. Type string `json:"type"` Slug string `json:"slug"` Description *string `json:"description"` ThumbnailURL *string `json:"thumbnail_url"` GameURL *string `json:"game_url"` Version *string `json:"version"` // Create only: DRAFT (default), ACTIVE or INACTIVE. Changed later through the // status endpoint. Status string `json:"status"` EntryCost int64 `json:"entry_cost"` // 600 when left out on create. SessionTTLSeconds int `json:"session_ttl_seconds"` ResultRules entities.GameResultRules `json:"result_rules"` } type EnakGame struct { ID uuid.UUID `json:"id"` Name string `json:"name"` Type string `json:"type"` Slug string `json:"slug"` Description *string `json:"description"` ThumbnailURL *string `json:"thumbnail_url"` GameURL *string `json:"game_url"` Version *string `json:"version"` Status string `json:"status"` EntryCost int64 `json:"entry_cost"` SessionTTLSeconds int `json:"session_ttl_seconds"` ResultRules entities.GameResultRules `json:"result_rules"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } // EnakGameListQuery filters the admin game list. ARCHIVED games are left out unless // asked for by status. type EnakGameListQuery struct { Status string `form:"status"` Search string `form:"search"` Page int `form:"page"` Limit int `form:"limit"` } type EnakGameStatusInput struct { Status string `json:"status"` Reason *string `json:"reason"` } // GameRewardConfigInput is a new version of a game's reward configuration (§5.2). type GameRewardConfigInput struct { RewardType string `json:"reward_type"` Rules json.RawMessage `json:"rules"` MaxReward int64 `json:"max_reward"` EffectiveAt *time.Time `json:"effective_at"` Reason *string `json:"reason"` } type GameRewardConfigActivateInput struct { Reason *string `json:"reason"` } type GameRewardConfig struct { ID uuid.UUID `json:"id"` GameID uuid.UUID `json:"game_id"` Version int `json:"version"` RewardType string `json:"reward_type"` Rules json.RawMessage `json:"rules"` MaxReward int64 `json:"max_reward"` Status string `json:"status"` EffectiveAt *time.Time `json:"effective_at"` CreatedBy uuid.UUID `json:"created_by"` Reason *string `json:"reason"` CreatedAt time.Time `json:"created_at"` } // GameBudgetThresholds are percents of utilization or forecast (PRD §8, §32). type GameBudgetThresholds struct { Warning *int64 `json:"warning,omitempty"` Critical *int64 `json:"critical,omitempty"` } // GameBudgetInput creates or changes a budget (§5.6). Dates are YYYY-MM-DD and // inclusive. On a change, fields left out keep their value; the scope never changes. type GameBudgetInput struct { Scope string `json:"scope"` Name string `json:"name"` PeriodStart string `json:"period_start"` PeriodEnd string `json:"period_end"` Amount int64 `json:"amount"` Thresholds GameBudgetThresholds `json:"thresholds"` } type GameBudget struct { ID uuid.UUID `json:"id"` Scope string `json:"scope"` Name string `json:"name"` PeriodStart string `json:"period_start"` PeriodEnd string `json:"period_end"` Amount int64 `json:"amount"` Thresholds GameBudgetThresholds `json:"thresholds"` CreatedBy uuid.UUID `json:"created_by"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } type GameBudgetListQuery struct { Scope string `form:"scope"` Page int `form:"page"` Limit int `form:"limit"` } // CustomerEnakGame is a game as the customer app sees it: what it costs and where to // load it, without the validation limits. type CustomerEnakGame struct { ID uuid.UUID `json:"id"` Slug string `json:"slug"` Name string `json:"name"` Description *string `json:"description"` ThumbnailURL *string `json:"thumbnail_url"` GameURL *string `json:"game_url"` Version *string `json:"version"` EntryCost int64 `json:"entry_cost"` SessionTTLSeconds int `json:"session_ttl_seconds"` // The events making the game pay more right now, highest priority first. Events []CustomerGameEvent `json:"events"` } // GameSessionStart is the response of starting a game (§7.1). type GameSessionStart struct { SessionID uuid.UUID `json:"session_id"` GameID uuid.UUID `json:"game_id"` EntryCost int64 `json:"entry_cost"` ExpiresAt time.Time `json:"expires_at"` CoinBalance int64 `json:"coin_balance"` // True when this repeats an earlier start with the same Idempotency-Key. Replayed bool `json:"replayed"` } // CustomerGameSession is a session as its customer sees it. The reward breakdown, // the RNG draw and whether it was flagged stay internal. type CustomerGameSession struct { ID uuid.UUID `json:"id"` GameID uuid.UUID `json:"game_id"` Status string `json:"status"` EntryCost int64 `json:"entry_cost"` RewardTotal int64 `json:"reward_total"` StartedAt time.Time `json:"started_at"` ExpiresAt time.Time `json:"expires_at"` EndedAt *time.Time `json:"ended_at"` RefundReason *string `json:"refund_reason"` } // GameSessionCompleteInput is what the client reports at the end of a play (§7.2): data // only. Anything else it sends, a reward amount above all, is ignored (P1). type GameSessionCompleteInput struct { Score *int64 `json:"score"` Outcome *string `json:"outcome"` Data json.RawMessage `json:"data"` } // GameSessionCompletion is the response of completing a session. Sending the same // completion again returns the same response. type GameSessionCompletion struct { SessionID uuid.UUID `json:"session_id"` // COMPLETED, or REFUNDED when the game was turned off during the play. Status string `json:"status"` RefundReason *string `json:"refund_reason,omitempty"` RewardTotal int64 `json:"reward_total"` // The parts of the reward that are safe to show. Reward GameSessionRewardParts `json:"reward"` CoinBalance int64 `json:"coin_balance"` // The daily limits that made the reward smaller than earned: USER_DAILY, // GAME_DAILY or GLOBAL_DAILY. LimitedBy []string `json:"limited_by,omitempty"` } type GameSessionRewardParts struct { Base int64 `json:"base"` // Added by events; 0 until events exist. Event int64 `json:"event"` } // VoucherInput creates or changes a voucher (§5.7). On a change, fields left out keep // their value; the stock mode never changes. type VoucherInput struct { Name string `json:"name"` Description *string `json:"description"` ImageURL *string `json:"image_url"` VoucherType string `json:"voucher_type"` FaceValue int64 `json:"face_value"` PointCost int64 `json:"point_cost"` BusinessCost *int64 `json:"business_cost"` StockMode string `json:"stock_mode"` // STATIC only. Stock *int64 `json:"stock"` // EXTERNAL only. Provider *string `json:"provider"` ProviderRef *string `json:"provider_ref"` MaxPerCustomer *int `json:"max_per_customer"` ValidFrom *time.Time `json:"valid_from"` ValidUntil *time.Time `json:"valid_until"` Terms json.RawMessage `json:"terms"` // Create only: DRAFT (default), ACTIVE or INACTIVE. Status string `json:"status"` } type Voucher struct { ID uuid.UUID `json:"id"` Name string `json:"name"` Description *string `json:"description"` ImageURL *string `json:"image_url"` VoucherType string `json:"voucher_type"` FaceValue int64 `json:"face_value"` PointCost int64 `json:"point_cost"` BusinessCost *int64 `json:"business_cost"` StockMode string `json:"stock_mode"` Stock *int64 `json:"stock"` Provider *string `json:"provider"` ProviderRef *string `json:"provider_ref"` MaxPerCustomer *int `json:"max_per_customer"` ValidFrom *time.Time `json:"valid_from"` ValidUntil *time.Time `json:"valid_until"` Terms json.RawMessage `json:"terms"` Status string `json:"status"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } type VoucherListQuery struct { Status string `form:"status"` Search string `form:"search"` Page int `form:"page"` Limit int `form:"limit"` } type VoucherStatusInput struct { Status string `json:"status"` Reason *string `json:"reason"` } // VoucherCodeImportResult says what an import of codes did. type VoucherCodeImportResult struct { Imported int `json:"imported"` // Codes already in the pool or repeated in the file, skipped. DuplicateCount int `json:"duplicate_count"` Duplicates []string `json:"duplicates"` // Lines that could not be read, skipped. Invalid []VoucherCodeImportProblem `json:"invalid"` } type VoucherCodeImportProblem struct { Line int `json:"line"` Reason string `json:"reason"` } type VoucherCodeListQuery struct { Status string `form:"status"` Page int `form:"page"` Limit int `form:"limit"` } type VoucherCode struct { ID uuid.UUID `json:"id"` Code string `json:"code"` Status string `json:"status"` RedemptionID *uuid.UUID `json:"redemption_id"` ExpiresAt *time.Time `json:"expires_at"` CreatedAt time.Time `json:"created_at"` } // VoucherCodes is a pool's codes: how many in each status, and one page of them. type VoucherCodes struct { Counts map[string]int64 `json:"counts"` Codes PaginatedResponse[VoucherCode] `json:"codes"` } // CustomerVoucher is a voucher in the customer app's catalog. Available is how many // are left, without saying how many were redeemed or expired. type CustomerVoucher struct { ID uuid.UUID `json:"id"` Name string `json:"name"` Description *string `json:"description"` ImageURL *string `json:"image_url"` VoucherType string `json:"voucher_type"` FaceValue int64 `json:"face_value"` PointCost int64 `json:"point_cost"` MaxPerCustomer *int `json:"max_per_customer"` ValidUntil *time.Time `json:"valid_until"` Terms json.RawMessage `json:"terms"` Available *int64 `json:"available"` } // CustomerVoucherRedemption is a redemption as its customer sees it, with the code to // use. The response of a redeem adds the EnakPoint balance. type CustomerVoucherRedemption struct { ID uuid.UUID `json:"id"` VoucherID uuid.UUID `json:"voucher_id"` VoucherName string `json:"voucher_name"` VoucherImageURL *string `json:"voucher_image_url"` VoucherType string `json:"voucher_type"` Status string `json:"status"` FaceValue int64 `json:"face_value"` PointCost int64 `json:"point_cost"` Code *string `json:"code"` CodeExpiresAt *time.Time `json:"code_expires_at"` CompletedAt *time.Time `json:"completed_at"` CreatedAt time.Time `json:"created_at"` } type VoucherRedeemResult struct { CustomerVoucherRedemption PointBalance int64 `json:"point_balance"` // True when this repeats an earlier redeem with the same Idempotency-Key. Replayed bool `json:"replayed"` } // GameBudgetMetrics is how a budget stands (docs/rfc-enakgame.md §10, PRD §8). Money is // in rupiah, percents have two decimals. type GameBudgetMetrics struct { BudgetID uuid.UUID `json:"budget_id"` Scope string `json:"scope"` PeriodStart string `json:"period_start"` PeriodEnd string `json:"period_end"` // The day, in Asia/Jakarta, the metrics are for. AsOf string `json:"as_of"` Amount int64 `json:"amount"` RealizedCost int64 `json:"realized_cost"` Remaining int64 `json:"remaining"` UtilizationPercent float64 `json:"utilization_percent"` // Average realized cost per day over the last WindowDays days, today included. DailyBurn int64 `json:"daily_burn"` WindowDays int64 `json:"window_days"` // Days of the period after today. RemainingDays int64 `json:"remaining_days"` ForecastCost int64 `json:"forecast_cost"` ForecastRemaining int64 `json:"forecast_remaining"` ForecastUtilizationPercent float64 `json:"forecast_utilization_percent"` CoinIssued int64 `json:"coin_issued"` // What the budget's rewards still hold, spendable: the most that can still turn // into cost. Exposure GameBudgetExposure `json:"exposure"` // The thresholds used: the budget's own, or the defaults for those it does not set. Thresholds GameBudgetThresholds `json:"thresholds"` // HEALTHY, WARNING, CRITICAL or EXHAUSTED. Status string `json:"status"` } type GameBudgetExposure struct { Coins int64 `json:"coins"` Points int64 `json:"points"` } // GameEventInput creates or changes an event (§5.5). On a change, fields left out // keep their value. type GameEventInput struct { Name string `json:"name"` Slug string `json:"slug"` Description *string `json:"description"` BannerURL *string `json:"banner_url"` StartAt time.Time `json:"start_at"` EndAt time.Time `json:"end_at"` // Asia/Jakarta when left out. Timezone string `json:"timezone"` Priority int `json:"priority"` // At least 1, two decimals at most. 2 adds the base reward once more. Multiplier *float64 `json:"multiplier"` Bonus *int64 `json:"bonus"` // An EVENT budget of the organization; it pays what the event adds. BudgetID uuid.UUID `json:"budget_id"` RewardLimit *int64 `json:"reward_limit"` UserDailyLimit *int64 `json:"user_daily_limit"` GameIDs []uuid.UUID `json:"game_ids"` // Create only: DRAFT (default) or ACTIVE. Status string `json:"status"` } type GameEvent struct { ID uuid.UUID `json:"id"` Name string `json:"name"` Slug string `json:"slug"` Description *string `json:"description"` BannerURL *string `json:"banner_url"` StartAt time.Time `json:"start_at"` EndAt time.Time `json:"end_at"` Timezone string `json:"timezone"` Status string `json:"status"` Priority int `json:"priority"` Multiplier *float64 `json:"multiplier"` Bonus *int64 `json:"bonus"` BudgetID uuid.UUID `json:"budget_id"` RewardLimit *int64 `json:"reward_limit"` UserDailyLimit *int64 `json:"user_daily_limit"` GameIDs []uuid.UUID `json:"game_ids"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } type GameEventListQuery struct { Status string `form:"status"` Page int `form:"page"` Limit int `form:"limit"` } type GameEventStatusInput struct { Status string `json:"status"` Reason *string `json:"reason"` } // CustomerGameEvent is an event running on a game, as the customer app shows it. type CustomerGameEvent struct { ID uuid.UUID `json:"id"` Name string `json:"name"` BannerURL *string `json:"banner_url"` Multiplier *float64 `json:"multiplier"` Bonus *int64 `json:"bonus"` EndAt time.Time `json:"end_at"` }