Adds cmd/wallet-migrate (make wallet-migrate, args=-dry-run to only report), which moves customer_points and customer_tokens into the wallet (docs/prd-point-coin.md ยง10, PC-105). Each customer gets a MIGRATION ledger row and a non-expiring lot per currency, written through WalletProcessor in one transaction per customer. EnakCoin is the sum of every token type (Q6), with the legacy rows listed in the row's metadata. It credits the difference between the legacy balance and what earlier runs migrated, so running it again never doubles a balance and picks up only what the old code added since. A legacy balance that shrank after being migrated is reported and left alone, since only an admin adjustment may take balance away, and the command then exits non-zero. It ends with a legacy / migrated / wallet total per currency. Migration 000092 renames TOKENS to COINS in campaigns.type and campaign_rules.reward_type. The campaign API now validates COINS; it still accepts TOKENS, including as a list filter, and stores it as COINS so older dashboards keep working while they are updated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
135 lines
6.1 KiB
Go
135 lines
6.1 KiB
Go
package models
|
|
|
|
import (
|
|
"time"
|
|
|
|
"apskel-pos-be/internal/entities"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
type CreateCampaignRequest struct {
|
|
Name string `json:"name" binding:"required,min=1,max=150"`
|
|
Description *string `json:"description,omitempty"`
|
|
Type string `json:"type" binding:"required,oneof=REWARD POINTS COINS TOKENS MIXED"`
|
|
StartDate time.Time `json:"start_date" binding:"required"`
|
|
EndDate time.Time `json:"end_date" binding:"required"`
|
|
IsActive bool `json:"is_active"`
|
|
ShowOnApp bool `json:"show_on_app"`
|
|
Position int `json:"position" binding:"min=0"`
|
|
Metadata *entities.Metadata `json:"metadata,omitempty"`
|
|
Rules []CampaignRuleStruct `json:"rules" binding:"required,min=1"`
|
|
}
|
|
|
|
type UpdateCampaignRequest struct {
|
|
ID uuid.UUID `json:"id" binding:"required"`
|
|
Name string `json:"name" binding:"required,min=1,max=150"`
|
|
Description *string `json:"description,omitempty"`
|
|
Type string `json:"type" binding:"required,oneof=REWARD POINTS COINS TOKENS MIXED"`
|
|
StartDate time.Time `json:"start_date" binding:"required"`
|
|
EndDate time.Time `json:"end_date" binding:"required"`
|
|
IsActive bool `json:"is_active"`
|
|
ShowOnApp bool `json:"show_on_app"`
|
|
Position int `json:"position" binding:"min=0"`
|
|
Metadata *entities.Metadata `json:"metadata,omitempty"`
|
|
Rules []CampaignRuleStruct `json:"rules" binding:"required,min=1"`
|
|
}
|
|
|
|
type ListCampaignsRequest struct {
|
|
Page int `form:"page" binding:"min=1"`
|
|
Limit int `form:"limit" binding:"min=1,max=100"`
|
|
Search string `form:"search"`
|
|
Type string `form:"type"`
|
|
IsActive *bool `form:"is_active"`
|
|
ShowOnApp *bool `form:"show_on_app"`
|
|
StartDate *time.Time `form:"start_date"`
|
|
EndDate *time.Time `form:"end_date"`
|
|
}
|
|
|
|
type CampaignResponse struct {
|
|
ID uuid.UUID `json:"id"`
|
|
Name string `json:"name"`
|
|
Description *string `json:"description,omitempty"`
|
|
Type string `json:"type"`
|
|
StartDate time.Time `json:"start_date"`
|
|
EndDate time.Time `json:"end_date"`
|
|
IsActive bool `json:"is_active"`
|
|
ShowOnApp bool `json:"show_on_app"`
|
|
Position int `json:"position"`
|
|
Metadata *entities.Metadata `json:"metadata,omitempty"`
|
|
Rules []CampaignRuleResponse `json:"rules,omitempty"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
}
|
|
|
|
type CampaignRuleResponse struct {
|
|
ID uuid.UUID `json:"id"`
|
|
CampaignID uuid.UUID `json:"campaign_id"`
|
|
RuleType string `json:"rule_type"`
|
|
ConditionValue *string `json:"condition_value,omitempty"`
|
|
RewardType string `json:"reward_type"`
|
|
RewardValue *int64 `json:"reward_value,omitempty"`
|
|
RewardSubtype *string `json:"reward_subtype,omitempty"`
|
|
RewardRefID *uuid.UUID `json:"reward_ref_id,omitempty"`
|
|
Metadata *entities.Metadata `json:"metadata,omitempty"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
}
|
|
|
|
type ListCampaignsResponse struct {
|
|
Campaigns []CampaignResponse `json:"campaigns"`
|
|
Total int64 `json:"total"`
|
|
Page int `json:"page"`
|
|
Limit int `json:"limit"`
|
|
}
|
|
|
|
// Campaign Rule Models
|
|
type CreateCampaignRuleRequest struct {
|
|
CampaignID uuid.UUID `json:"campaign_id" binding:"required"`
|
|
RuleType string `json:"rule_type" binding:"required,oneof=TIER SPEND PRODUCT CATEGORY DAY LOCATION"`
|
|
ConditionValue *string `json:"condition_value,omitempty"`
|
|
RewardType string `json:"reward_type" binding:"required,oneof=POINTS COINS TOKENS REWARD"`
|
|
RewardValue *int64 `json:"reward_value,omitempty"`
|
|
RewardSubtype *string `json:"reward_subtype,omitempty"`
|
|
RewardRefID *uuid.UUID `json:"reward_ref_id,omitempty"`
|
|
Metadata *entities.Metadata `json:"metadata,omitempty"`
|
|
}
|
|
|
|
type UpdateCampaignRuleRequest struct {
|
|
ID uuid.UUID `json:"id" binding:"required"`
|
|
CampaignID uuid.UUID `json:"campaign_id" binding:"required"`
|
|
RuleType string `json:"rule_type" binding:"required,oneof=TIER SPEND PRODUCT CATEGORY DAY LOCATION"`
|
|
ConditionValue *string `json:"condition_value,omitempty"`
|
|
RewardType string `json:"reward_type" binding:"required,oneof=POINTS COINS TOKENS REWARD"`
|
|
RewardValue *int64 `json:"reward_value,omitempty"`
|
|
RewardSubtype *string `json:"reward_subtype,omitempty"`
|
|
RewardRefID *uuid.UUID `json:"reward_ref_id,omitempty"`
|
|
Metadata *entities.Metadata `json:"metadata,omitempty"`
|
|
}
|
|
|
|
type ListCampaignRulesRequest struct {
|
|
Page int `form:"page" binding:"min=1"`
|
|
Limit int `form:"limit" binding:"min=1,max=100"`
|
|
CampaignID string `form:"campaign_id"`
|
|
RuleType string `form:"rule_type"`
|
|
RewardType string `form:"reward_type"`
|
|
}
|
|
|
|
type ListCampaignRulesResponse struct {
|
|
Rules []CampaignRuleResponse `json:"rules"`
|
|
Total int64 `json:"total"`
|
|
Page int `json:"page"`
|
|
Limit int `json:"limit"`
|
|
}
|
|
|
|
// Helper structs
|
|
type CampaignRuleStruct struct {
|
|
RuleType string `json:"rule_type" binding:"required,oneof=TIER SPEND PRODUCT CATEGORY DAY LOCATION"`
|
|
ConditionValue *string `json:"condition_value,omitempty"`
|
|
RewardType string `json:"reward_type" binding:"required,oneof=POINTS COINS TOKENS REWARD"`
|
|
RewardValue *int64 `json:"reward_value,omitempty"`
|
|
RewardSubtype *string `json:"reward_subtype,omitempty"`
|
|
RewardRefID *uuid.UUID `json:"reward_ref_id,omitempty"`
|
|
Metadata *entities.Metadata `json:"metadata,omitempty"`
|
|
}
|