class ReferralInfo { final String referralCode; final String shareUrl; final int directCount; final int activeCount; final int pendingCreditCents; final int totalEarnedCents; final int totalAppliedCents; const ReferralInfo({ required this.referralCode, required this.shareUrl, required this.directCount, required this.activeCount, required this.pendingCreditCents, required this.totalEarnedCents, required this.totalAppliedCents, }); factory ReferralInfo.fromJson(Map json) => ReferralInfo( referralCode: json['referralCode'] as String? ?? '', shareUrl: json['shareUrl'] as String? ?? '', directCount: json['directCount'] as int? ?? 0, activeCount: json['activeCount'] as int? ?? 0, pendingCreditCents: json['pendingCreditCents'] as int? ?? 0, totalEarnedCents: json['totalEarnedCents'] as int? ?? 0, totalAppliedCents: json['totalAppliedCents'] as int? ?? 0, ); String get pendingCreditFormatted => '\$${(pendingCreditCents / 100).toStringAsFixed(2)}'; String get totalEarnedFormatted => '\$${(totalEarnedCents / 100).toStringAsFixed(2)}'; } class ReferralItem { final String id; final String referredTenantId; final String referralCode; final String status; // PENDING | ACTIVE | REWARDED | EXPIRED final int level; final DateTime registeredAt; final DateTime? activatedAt; const ReferralItem({ required this.id, required this.referredTenantId, required this.referralCode, required this.status, required this.level, required this.registeredAt, this.activatedAt, }); factory ReferralItem.fromJson(Map json) => ReferralItem( id: json['id'] as String, referredTenantId: json['referredTenantId'] as String? ?? '', referralCode: json['referralCode'] as String? ?? '', status: json['status'] as String? ?? 'PENDING', level: json['level'] as int? ?? 1, registeredAt: DateTime.parse(json['registeredAt'] as String), activatedAt: json['activatedAt'] != null ? DateTime.parse(json['activatedAt'] as String) : null, ); bool get isActive => status == 'ACTIVE' || status == 'REWARDED'; } // ── User-level personal circle models ───────────────────────────────────── class UserReferralInfo { final String code; final String shareUrl; final int circleSize; final int activeCount; final int pointsBalance; final int totalEarned; final int totalSpent; const UserReferralInfo({ required this.code, required this.shareUrl, required this.circleSize, required this.activeCount, required this.pointsBalance, required this.totalEarned, required this.totalSpent, }); factory UserReferralInfo.fromJson(Map json) => UserReferralInfo( code: json['code'] as String? ?? '', shareUrl: json['shareUrl'] as String? ?? '', circleSize: json['circleSize'] as int? ?? 0, activeCount: json['activeCount'] as int? ?? 0, pointsBalance: json['pointsBalance'] as int? ?? 0, totalEarned: json['totalEarned'] as int? ?? 0, totalSpent: json['totalSpent'] as int? ?? 0, ); } class CircleMember { final String relationshipId; final String referredUserId; final int level; final String status; final DateTime joinedAt; final DateTime? activatedAt; const CircleMember({ required this.relationshipId, required this.referredUserId, required this.level, required this.status, required this.joinedAt, this.activatedAt, }); factory CircleMember.fromJson(Map json) => CircleMember( relationshipId: json['relationshipId'] as String, referredUserId: json['referredUserId'] as String, level: json['level'] as int? ?? 1, status: json['status'] as String? ?? 'PENDING', joinedAt: DateTime.parse(json['joinedAt'] as String), activatedAt: json['activatedAt'] != null ? DateTime.parse(json['activatedAt'] as String) : null, ); bool get isActive => status == 'ACTIVE' || status == 'REWARDED'; } class PointTransaction { final String id; final int delta; final String type; final String? note; final DateTime createdAt; const PointTransaction({ required this.id, required this.delta, required this.type, this.note, required this.createdAt, }); factory PointTransaction.fromJson(Map json) => PointTransaction( id: json['id'] as String, delta: json['delta'] as int? ?? 0, type: json['type'] as String? ?? '', note: json['note'] as String?, createdAt: DateTime.parse(json['createdAt'] as String), ); bool get isEarned => delta > 0; String get typeLabel { switch (type) { case 'REFERRAL_FIRST_PAYMENT': return '推荐首次订阅奖励'; case 'REFERRAL_RECURRING': return '推荐续订奖励'; case 'REFERRAL_L2': return '二级推荐奖励'; case 'REFERRAL_WELCOME': return '加入欢迎积分'; case 'REDEMPTION_QUOTA': return '兑换使用配额'; case 'REDEMPTION_UNLOCK': return '兑换智能体解锁'; case 'ADMIN_GRANT': return '平台赠送'; case 'EXPIRY': return '积分过期'; default: return type; } } } class RewardItem { final String id; final int amountCents; final String amountFormatted; final String rewardType; final String triggerType; final String status; // PENDING | APPLIED | EXPIRED final String? sourceInvoiceId; final int? recurringMonth; final DateTime createdAt; final DateTime? appliedAt; const RewardItem({ required this.id, required this.amountCents, required this.amountFormatted, required this.rewardType, required this.triggerType, required this.status, this.sourceInvoiceId, this.recurringMonth, required this.createdAt, this.appliedAt, }); factory RewardItem.fromJson(Map json) => RewardItem( id: json['id'] as String, amountCents: json['amountCents'] as int? ?? 0, amountFormatted: json['amountFormatted'] as String? ?? '\$0.00', rewardType: json['rewardType'] as String? ?? 'CREDIT', triggerType: json['triggerType'] as String? ?? 'FIRST_PAYMENT', status: json['status'] as String? ?? 'PENDING', sourceInvoiceId: json['sourceInvoiceId'] as String?, recurringMonth: json['recurringMonth'] as int?, createdAt: DateTime.parse(json['createdAt'] as String), appliedAt: json['appliedAt'] != null ? DateTime.parse(json['appliedAt'] as String) : null, ); String get triggerLabel => triggerType == 'FIRST_PAYMENT' ? '首次付款奖励' : '续订奖励(第${recurringMonth ?? 1}月)'; }