马良AI写作初始化仓库
This commit is contained in:
429
AINoval/lib/models/admin/subscription_models.dart
Normal file
429
AINoval/lib/models/admin/subscription_models.dart
Normal file
@@ -0,0 +1,429 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import '../../utils/date_time_parser.dart';
|
||||
|
||||
part 'subscription_models.g.dart';
|
||||
|
||||
/// 订阅计划模型
|
||||
@JsonSerializable()
|
||||
class SubscriptionPlan extends Equatable {
|
||||
final String? id;
|
||||
final String planName;
|
||||
final String? description;
|
||||
final double price;
|
||||
final String currency;
|
||||
final BillingCycle billingCycle;
|
||||
final String? roleId;
|
||||
final int? creditsGranted;
|
||||
final bool active;
|
||||
final bool recommended;
|
||||
final int priority;
|
||||
final Map<String, dynamic>? features;
|
||||
final int trialDays;
|
||||
final int maxUsers;
|
||||
final DateTime? createdAt;
|
||||
final DateTime? updatedAt;
|
||||
|
||||
const SubscriptionPlan({
|
||||
this.id,
|
||||
required this.planName,
|
||||
this.description,
|
||||
required this.price,
|
||||
required this.currency,
|
||||
required this.billingCycle,
|
||||
this.roleId,
|
||||
this.creditsGranted,
|
||||
this.active = true,
|
||||
this.recommended = false,
|
||||
this.priority = 0,
|
||||
this.features,
|
||||
this.trialDays = 0,
|
||||
this.maxUsers = -1,
|
||||
this.createdAt,
|
||||
this.updatedAt,
|
||||
});
|
||||
|
||||
factory SubscriptionPlan.fromJson(Map<String, dynamic> json) {
|
||||
// 兼容后端字段可能为空或类型不一致(如 BigDecimal 序列化为字符串)
|
||||
final dynamic priceRaw = json['price'];
|
||||
final double parsedPrice = priceRaw is num
|
||||
? priceRaw.toDouble()
|
||||
: (priceRaw is String ? double.tryParse(priceRaw) ?? 0.0 : 0.0);
|
||||
|
||||
final dynamic priorityRaw = json['priority'];
|
||||
final int parsedPriority = priorityRaw is num
|
||||
? priorityRaw.toInt()
|
||||
: (priorityRaw is String ? int.tryParse(priorityRaw) ?? 0 : 0);
|
||||
|
||||
final dynamic creditsRaw = json['creditsGranted'];
|
||||
final int? parsedCredits = creditsRaw == null
|
||||
? null
|
||||
: (creditsRaw is num
|
||||
? creditsRaw.toInt()
|
||||
: (creditsRaw is String ? int.tryParse(creditsRaw) : null));
|
||||
|
||||
final dynamic activeRaw = json['active'];
|
||||
final bool parsedActive = activeRaw is bool
|
||||
? activeRaw
|
||||
: (activeRaw is String ? activeRaw.toLowerCase() == 'true' : true);
|
||||
|
||||
final dynamic recommendedRaw = json['recommended'];
|
||||
final bool parsedRecommended = recommendedRaw is bool
|
||||
? recommendedRaw
|
||||
: (recommendedRaw is String ? recommendedRaw.toLowerCase() == 'true' : false);
|
||||
|
||||
final featuresRaw = json['features'];
|
||||
final Map<String, dynamic>? parsedFeatures =
|
||||
featuresRaw is Map<String, dynamic> ? featuresRaw : null;
|
||||
|
||||
return SubscriptionPlan(
|
||||
id: json['id'] as String?,
|
||||
planName: (json['planName'] as String?) ?? '未命名套餐',
|
||||
description: json['description'] as String?,
|
||||
price: parsedPrice,
|
||||
currency: (json['currency'] as String?) ?? 'CNY',
|
||||
billingCycle: _parseBillingCycle(json['billingCycle']),
|
||||
roleId: json['roleId'] as String?,
|
||||
creditsGranted: parsedCredits,
|
||||
active: parsedActive,
|
||||
recommended: parsedRecommended,
|
||||
priority: parsedPriority,
|
||||
features: parsedFeatures,
|
||||
trialDays: ((json['trialDays'] is String)
|
||||
? int.tryParse(json['trialDays'])
|
||||
: (json['trialDays'] as num?))
|
||||
?.toInt() ?? 0,
|
||||
maxUsers: ((json['maxUsers'] is String)
|
||||
? int.tryParse(json['maxUsers'])
|
||||
: (json['maxUsers'] as num?))
|
||||
?.toInt() ?? -1,
|
||||
createdAt: json['createdAt'] != null ? parseBackendDateTime(json['createdAt']) : null,
|
||||
updatedAt: json['updatedAt'] != null ? parseBackendDateTime(json['updatedAt']) : null,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => _$SubscriptionPlanToJson(this);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
id,
|
||||
planName,
|
||||
description,
|
||||
price,
|
||||
currency,
|
||||
billingCycle,
|
||||
roleId,
|
||||
creditsGranted,
|
||||
active,
|
||||
recommended,
|
||||
priority,
|
||||
features,
|
||||
trialDays,
|
||||
maxUsers,
|
||||
createdAt,
|
||||
updatedAt,
|
||||
];
|
||||
|
||||
/// 获取月度等价价格
|
||||
double get monthlyEquivalentPrice {
|
||||
switch (billingCycle) {
|
||||
case BillingCycle.monthly:
|
||||
return price;
|
||||
case BillingCycle.quarterly:
|
||||
return price / 3;
|
||||
case BillingCycle.yearly:
|
||||
return price / 12;
|
||||
case BillingCycle.lifetime:
|
||||
return price / 120; // 假设10年使用期
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取计费周期显示文本
|
||||
String get billingCycleText {
|
||||
switch (billingCycle) {
|
||||
case BillingCycle.monthly:
|
||||
return '月付';
|
||||
case BillingCycle.quarterly:
|
||||
return '季付';
|
||||
case BillingCycle.yearly:
|
||||
return '年付';
|
||||
case BillingCycle.lifetime:
|
||||
return '终身';
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取格式化价格
|
||||
String get formattedPrice {
|
||||
return '$currency ${price.toStringAsFixed(2)}';
|
||||
}
|
||||
|
||||
/// 解析BillingCycle枚举
|
||||
static BillingCycle _parseBillingCycle(dynamic value) {
|
||||
if (value == null) return BillingCycle.monthly;
|
||||
|
||||
final stringValue = value.toString().toUpperCase();
|
||||
switch (stringValue) {
|
||||
case 'MONTHLY':
|
||||
return BillingCycle.monthly;
|
||||
case 'QUARTERLY':
|
||||
return BillingCycle.quarterly;
|
||||
case 'YEARLY':
|
||||
return BillingCycle.yearly;
|
||||
case 'LIFETIME':
|
||||
return BillingCycle.lifetime;
|
||||
default:
|
||||
return BillingCycle.monthly;
|
||||
}
|
||||
}
|
||||
|
||||
/// 创建副本
|
||||
SubscriptionPlan copyWith({
|
||||
String? id,
|
||||
String? planName,
|
||||
String? description,
|
||||
double? price,
|
||||
String? currency,
|
||||
BillingCycle? billingCycle,
|
||||
String? roleId,
|
||||
int? creditsGranted,
|
||||
bool? active,
|
||||
bool? recommended,
|
||||
int? priority,
|
||||
Map<String, dynamic>? features,
|
||||
int? trialDays,
|
||||
int? maxUsers,
|
||||
DateTime? createdAt,
|
||||
DateTime? updatedAt,
|
||||
}) {
|
||||
return SubscriptionPlan(
|
||||
id: id ?? this.id,
|
||||
planName: planName ?? this.planName,
|
||||
description: description ?? this.description,
|
||||
price: price ?? this.price,
|
||||
currency: currency ?? this.currency,
|
||||
billingCycle: billingCycle ?? this.billingCycle,
|
||||
roleId: roleId ?? this.roleId,
|
||||
creditsGranted: creditsGranted ?? this.creditsGranted,
|
||||
active: active ?? this.active,
|
||||
recommended: recommended ?? this.recommended,
|
||||
priority: priority ?? this.priority,
|
||||
features: features ?? this.features,
|
||||
trialDays: trialDays ?? this.trialDays,
|
||||
maxUsers: maxUsers ?? this.maxUsers,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
updatedAt: updatedAt ?? this.updatedAt,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 计费周期枚举
|
||||
enum BillingCycle {
|
||||
@JsonValue('MONTHLY')
|
||||
monthly,
|
||||
@JsonValue('QUARTERLY')
|
||||
quarterly,
|
||||
@JsonValue('YEARLY')
|
||||
yearly,
|
||||
@JsonValue('LIFETIME')
|
||||
lifetime,
|
||||
}
|
||||
|
||||
/// 用户订阅模型
|
||||
@JsonSerializable()
|
||||
class UserSubscription extends Equatable {
|
||||
final String? id;
|
||||
final String userId;
|
||||
final String planId;
|
||||
final DateTime? startDate;
|
||||
final DateTime? endDate;
|
||||
final SubscriptionStatus status;
|
||||
final bool autoRenewal;
|
||||
final String? paymentMethod;
|
||||
final String? transactionId;
|
||||
final int creditsUsed;
|
||||
final int totalCredits;
|
||||
final DateTime? canceledAt;
|
||||
final String? cancelReason;
|
||||
final DateTime? trialEndDate;
|
||||
final bool isTrial;
|
||||
final DateTime? createdAt;
|
||||
final DateTime? updatedAt;
|
||||
|
||||
const UserSubscription({
|
||||
this.id,
|
||||
required this.userId,
|
||||
required this.planId,
|
||||
this.startDate,
|
||||
this.endDate,
|
||||
required this.status,
|
||||
this.autoRenewal = false,
|
||||
this.paymentMethod,
|
||||
this.transactionId,
|
||||
this.creditsUsed = 0,
|
||||
this.totalCredits = 0,
|
||||
this.canceledAt,
|
||||
this.cancelReason,
|
||||
this.trialEndDate,
|
||||
this.isTrial = false,
|
||||
this.createdAt,
|
||||
this.updatedAt,
|
||||
});
|
||||
|
||||
factory UserSubscription.fromJson(Map<String, dynamic> json) {
|
||||
return UserSubscription(
|
||||
id: json['id'] as String?,
|
||||
userId: json['userId'] as String,
|
||||
planId: json['planId'] as String,
|
||||
startDate: json['startDate'] != null ? parseBackendDateTime(json['startDate']) : null,
|
||||
endDate: json['endDate'] != null ? parseBackendDateTime(json['endDate']) : null,
|
||||
status: _parseSubscriptionStatus(json['status']),
|
||||
autoRenewal: json['autoRenewal'] as bool? ?? false,
|
||||
paymentMethod: json['paymentMethod'] as String?,
|
||||
transactionId: json['transactionId'] as String?,
|
||||
creditsUsed: (json['creditsUsed'] as num?)?.toInt() ?? 0,
|
||||
totalCredits: (json['totalCredits'] as num?)?.toInt() ?? 0,
|
||||
canceledAt: json['canceledAt'] != null ? parseBackendDateTime(json['canceledAt']) : null,
|
||||
cancelReason: json['cancelReason'] as String?,
|
||||
trialEndDate: json['trialEndDate'] != null ? parseBackendDateTime(json['trialEndDate']) : null,
|
||||
isTrial: json['isTrial'] as bool? ?? false,
|
||||
createdAt: json['createdAt'] != null ? parseBackendDateTime(json['createdAt']) : null,
|
||||
updatedAt: json['updatedAt'] != null ? parseBackendDateTime(json['updatedAt']) : null,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => _$UserSubscriptionToJson(this);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
id,
|
||||
userId,
|
||||
planId,
|
||||
startDate,
|
||||
endDate,
|
||||
status,
|
||||
autoRenewal,
|
||||
paymentMethod,
|
||||
transactionId,
|
||||
creditsUsed,
|
||||
totalCredits,
|
||||
canceledAt,
|
||||
cancelReason,
|
||||
trialEndDate,
|
||||
isTrial,
|
||||
createdAt,
|
||||
updatedAt,
|
||||
];
|
||||
|
||||
/// 获取剩余积分
|
||||
int get remainingCredits => (totalCredits - creditsUsed).clamp(0, totalCredits);
|
||||
|
||||
/// 检查订阅是否有效
|
||||
bool get isValid {
|
||||
final now = DateTime.now();
|
||||
return (status == SubscriptionStatus.active || status == SubscriptionStatus.trial) &&
|
||||
(endDate == null || endDate!.isAfter(now));
|
||||
}
|
||||
|
||||
/// 检查是否即将过期(7天内)
|
||||
bool get isExpiringSoon {
|
||||
if (endDate == null) return false;
|
||||
final now = DateTime.now();
|
||||
final sevenDaysLater = now.add(const Duration(days: 7));
|
||||
return endDate!.isBefore(sevenDaysLater) && endDate!.isAfter(now);
|
||||
}
|
||||
|
||||
/// 解析SubscriptionStatus枚举
|
||||
static SubscriptionStatus _parseSubscriptionStatus(dynamic value) {
|
||||
if (value == null) return SubscriptionStatus.active;
|
||||
|
||||
final stringValue = value.toString().toUpperCase();
|
||||
switch (stringValue) {
|
||||
case 'ACTIVE':
|
||||
return SubscriptionStatus.active;
|
||||
case 'TRIAL':
|
||||
return SubscriptionStatus.trial;
|
||||
case 'CANCELED':
|
||||
return SubscriptionStatus.canceled;
|
||||
case 'EXPIRED':
|
||||
return SubscriptionStatus.expired;
|
||||
case 'SUSPENDED':
|
||||
return SubscriptionStatus.suspended;
|
||||
case 'REFUNDED':
|
||||
return SubscriptionStatus.refunded;
|
||||
default:
|
||||
return SubscriptionStatus.active;
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取状态显示文本
|
||||
String get statusText {
|
||||
switch (status) {
|
||||
case SubscriptionStatus.active:
|
||||
return '活跃';
|
||||
case SubscriptionStatus.trial:
|
||||
return '试用期';
|
||||
case SubscriptionStatus.canceled:
|
||||
return '已取消';
|
||||
case SubscriptionStatus.expired:
|
||||
return '已过期';
|
||||
case SubscriptionStatus.suspended:
|
||||
return '暂停';
|
||||
case SubscriptionStatus.refunded:
|
||||
return '已退款';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 订阅状态枚举
|
||||
enum SubscriptionStatus {
|
||||
@JsonValue('ACTIVE')
|
||||
active,
|
||||
@JsonValue('TRIAL')
|
||||
trial,
|
||||
@JsonValue('CANCELED')
|
||||
canceled,
|
||||
@JsonValue('EXPIRED')
|
||||
expired,
|
||||
@JsonValue('SUSPENDED')
|
||||
suspended,
|
||||
@JsonValue('REFUNDED')
|
||||
refunded,
|
||||
}
|
||||
|
||||
/// 订阅统计信息
|
||||
@JsonSerializable()
|
||||
class SubscriptionStatistics extends Equatable {
|
||||
final int totalPlans;
|
||||
final int activePlans;
|
||||
final int totalSubscriptions;
|
||||
final int activeSubscriptions;
|
||||
final int trialSubscriptions;
|
||||
final double monthlyRevenue;
|
||||
final double yearlyRevenue;
|
||||
|
||||
const SubscriptionStatistics({
|
||||
required this.totalPlans,
|
||||
required this.activePlans,
|
||||
required this.totalSubscriptions,
|
||||
required this.activeSubscriptions,
|
||||
required this.trialSubscriptions,
|
||||
required this.monthlyRevenue,
|
||||
required this.yearlyRevenue,
|
||||
});
|
||||
|
||||
factory SubscriptionStatistics.fromJson(Map<String, dynamic> json) =>
|
||||
_$SubscriptionStatisticsFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$SubscriptionStatisticsToJson(this);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
totalPlans,
|
||||
activePlans,
|
||||
totalSubscriptions,
|
||||
activeSubscriptions,
|
||||
trialSubscriptions,
|
||||
monthlyRevenue,
|
||||
yearlyRevenue,
|
||||
];
|
||||
}
|
||||
Reference in New Issue
Block a user