Files
apskel-pos-backend/internal
efrilmandClaude Opus 5.5 798a36bd6c feat(enakgame): game sessions, rewards, vouchers, budgets and events
EnakGame phases 1-8 of docs/tasks-enakgame.md (EG-101 to EG-803), built on the
existing EnakPoint/EnakCoin wallet (docs/rfc-enakgame.md).

Foundation (phase 1)
- Migrations 000103-000106: games extended with organization, slug, status,
  entry cost and result rules, old games archived (not deleted); budgets,
  versioned reward configs, sessions and session rewards; the ledger types
  GAME_SPEND_REFUND, GAME_REWARD and REWARD_REDEEM_REFUND; audit_logs.
- AuditLogger writes in the caller's transaction only.
- enakgame.limit.user_daily and global_daily organization settings.

Games and sessions (phases 2-4)
- Admin /marketing/enakgame: games, reward config versions (immutable but for
  status, one ACTIVE per game), budgets with non-overlapping global periods and
  a daily job opening the next month.
- Customer /customer/enakgame: start (Idempotency-Key, entry cost and config
  frozen on the session), complete (result validation, reward engine, max_reward
  cap, daily limits via game_reward_counters, one GAME_REWARD per budget),
  automatic refunds for system errors and deactivated games, and a session job.
- Reward engine: FIXED, SCORE_BASED, OUTCOME_BASED, PROBABILITY (crypto/rand),
  rounded down.

Vouchers and budgets (phases 5-6)
- Migration 000108 and 000107: vouchers, codes, redemptions, cost attribution;
  Economy Guard counters.
- STATIC and CODE_POOL redemption in one transaction with the REDEEM PIN action;
  realized cost traced through the lots to the budget that paid the reward.
- Budget metrics: realized cost, forecast, exposure and status. Migrations
  000109-000110 add the wallet_lots indexes they need, built CONCURRENTLY.

Events (phase 7)
- Migration 000111: game events, each with its own EVENT budget. Event extras
  stack per PRD §16 defaults, with event and per-customer limits.

External vouchers (phase 8)
- VoucherProvider contract, two-step PENDING redemption and a recovery job,
  tested with a fake provider. No provider adapter is registered yet, so
  EXTERNAL vouchers stay out of the catalog.

Not yet decided before release: reward rounding, event stacking, budget
exhaustion policy and thresholds (RFC §19.2). Migrations 000103-000111 have
not been run on any shared database.

Also fixes a leftover PAYMENT filter in a wallet test and a data race in a
test PIN fake.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 20:53:14 +07:00
..
2026-05-09 12:24:44 +07:00
2026-10-04 21:54:13 +07:00
2026-05-09 01:01:25 +07:00
…
…

Internal Architecture

This document describes the clean architecture implementation for the POS backend with complete separation of concerns between database entities, business models, and constants.

📁 Package Structure

/constants - Business Constants

  • Purpose: All business logic constants, enums, and validation helpers
  • Usage: Used by models, services, and validation layers
  • Features:
    • Type-safe enums (UserRole, OrderStatus, PaymentStatus, etc.)
    • Business validation functions (IsValidUserRole, etc.)
    • Default values and limits
    • No dependencies on database or frameworks

/entities - Database Models

  • Purpose: Database-specific models with GORM tags and hooks
  • Usage: ONLY used by repository layer for database operations
  • Features:
    • GORM annotations (gorm: tags)
    • Database relationships and constraints
    • BeforeCreate/AfterCreate hooks
    • Table name specifications
    • SQL-specific data types
    • Never used in business logic

/models - Business Models

  • Purpose: Pure business domain models without any framework dependencies
  • Usage: Used by services, handlers, and business logic
  • Features:
    • Clean JSON serialization (json: tags)
    • Validation rules (validate: tags)
    • Request/Response DTOs
    • Zero GORM dependencies
    • Zero database annotations
    • Uses constants package for type safety
    • Pure business logic methods

/mappers - Data Transformation

  • Purpose: Convert between entities and business models
  • Usage: Bridge between repository and service layers
  • Features:
    • Entity ↔ Model conversion functions
    • Request DTO → Entity conversion
    • Entity → Response DTO conversion
    • Null-safe conversions
    • Slice/collection conversions
    • Type conversions between constants and entities

/repository - Data Access Layer

  • Purpose: Database operations using entities exclusively
  • Usage: Only works with database entities
  • Features:
    • CRUD operations with entities
    • Query methods with entities
    • Private repository implementations
    • Interface-based contracts
    • Never references business models

🔄 Data Flow

API Request (JSON)
       ↓
Request DTO (models)
       ↓
Business Logic (services with models + constants)
       ↓
Entity (via mapper)
       ↓
Repository Layer (entities only)
       ↓
Database
       ↓
Entity (from database)
       ↓
Business Model (via mapper)
       ↓
Response DTO (models)
       ↓
API Response (JSON)

🎯 Key Design Principles

✅ Clean Business Models


type User struct {
    ID    uuid.UUID           `json:"id"`
    Role  constants.UserRole  `json:"role"`
    }

type User struct {
    ID   uuid.UUID `gorm:"primaryKey" json:"id"`
    Role string    `gorm:"size:50" json:"role"`
    }

✅ Type-Safe Constants


type UserRole string
const (
    RoleAdmin UserRole = "admin"
    )
func IsValidUserRole(role UserRole) bool { /* ... */ }

const AdminRole = "admin"  ```

### ✅ **Repository Isolation**
```go

func (r *userRepository) Create(ctx context.Context, user *entities.User) error {
    return r.db.Create(user).Error
}

func (r *userRepository) Create(ctx context.Context, user *models.User) error {
    }

📊 Example Usage

Service Layer (Business Logic)

func (s *userService) CreateUser(req *models.UserCreateRequest) (*models.UserResponse, error) {
        if !constants.IsValidUserRole(req.Role) {
        return nil, errors.New("invalid role")
    }
    
        entity := mappers.UserCreateRequestToEntity(req, hashedPassword)
    
        err := s.userRepo.Create(ctx, entity)
    if err != nil {
        return nil, err
    }
    
        return mappers.UserEntityToResponse(entity), nil
}

Repository Layer (Data Access)

func (r *userRepository) Create(ctx context.Context, user *entities.User) error {
        return r.db.WithContext(ctx).Create(user).Error
}

Handler Layer (API)

func (h *userHandler) CreateUser(c *gin.Context) {
    var req models.UserCreateRequest
    
        if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }
    
        resp, err := h.userService.CreateUser(&req)
    if err != nil {
        c.JSON(500, gin.H{"error": err.Error()})
        return
    }
    
        c.JSON(201, resp)
}

🏗️ Architecture Benefits

  1. 🎯 Single Responsibility: Each package has one clear purpose
  2. 🔒 Zero Database Leakage: Business logic never sees database concerns
  3. 🧪 Testability: Easy to mock interfaces and test business logic
  4. 🔧 Maintainability: Changes to database don't affect business models
  5. 🚀 Flexibility: Can change ORM without touching business logic
  6. 📜 API Stability: Business models provide stable contracts
  7. 🛡️ Type Safety: Constants package prevents invalid states
  8. 🧹 Clean Code: No mixed concerns anywhere in the codebase

📋 Development Guidelines

Constants Package (/constants)

  • ✅ Define all business enums and constants
  • ✅ Provide validation helper functions
  • ✅ Include default values and limits
  • ❌ Never import database or framework packages
  • ❌ No business logic, only constants and validation

Models Package (/models)

  • ✅ Pure business structs with JSON tags only
  • ✅ Use constants package for type safety
  • ✅ Include validation tags for input validation
  • ✅ Separate Request/Response DTOs
  • ✅ Add business logic methods (validation, calculations)
  • ❌ NEVER include GORM tags or database annotations
  • ❌ NEVER import database packages
  • ❌ No database relationships or foreign keys

Entities Package (/entities)

  • ✅ Include GORM tags and database constraints
  • ✅ Define relationships and foreign keys
  • ✅ Add database hooks (BeforeCreate, etc.)
  • ✅ Use database-specific types
  • ❌ NEVER use in business logic or handlers
  • ❌ NEVER add business validation rules

Mappers Package (/mappers)

  • ✅ Always check for nil inputs
  • ✅ Handle type conversions between constants and strings
  • ✅ Provide slice conversion helpers
  • ✅ Keep conversions simple and direct
  • ❌ No business logic in mappers
  • ❌ No database operations

Repository Package (/repository)

  • ✅ Work exclusively with entities
  • ✅ Use private repository implementations
  • ✅ Provide clean interface contracts
  • ❌ NEVER reference business models
  • ❌ NEVER import models package

🚀 Migration Complete

All packages have been successfully reorganized:

  • ✅ 4 Constants files - All business constants moved to type-safe enums
  • ✅ 10 Clean Model files - Zero GORM dependencies, pure business logic
  • ✅ 11 Entity files - Database-only models with GORM tags
  • ✅ 11 Repository files - Updated to use entities exclusively
  • ✅ 2 Mapper files - Handle conversions between layers
  • ✅ Complete separation - No cross-layer dependencies

The codebase now follows strict clean architecture principles with complete separation of database concerns from business logic! 🎉