/// 券数据模型 — 对应后端 GET /api/v1/coupons 返回结构 class CouponModel { final String id; final String issuerId; final String name; final String? description; final String? imageUrl; final double faceValue; final double currentPrice; final double? issuePrice; final int totalSupply; final int remainingSupply; final String category; final String status; final String couponType; final DateTime expiryDate; final bool isTransferable; final int resaleCount; final int maxResaleCount; // Joined issuer fields final String? brandName; final String? brandLogoUrl; final String? creditRating; const CouponModel({ required this.id, required this.issuerId, required this.name, this.description, this.imageUrl, required this.faceValue, required this.currentPrice, this.issuePrice, required this.totalSupply, required this.remainingSupply, required this.category, required this.status, required this.couponType, required this.expiryDate, required this.isTransferable, this.resaleCount = 0, this.maxResaleCount = 3, this.brandName, this.brandLogoUrl, this.creditRating, }); factory CouponModel.fromJson(Map json) { final issuer = json['issuer'] as Map?; return CouponModel( id: json['id'] ?? '', issuerId: json['issuerId'] ?? '', name: json['name'] ?? '', description: json['description'], imageUrl: json['imageUrl'], faceValue: _toDouble(json['faceValue']), currentPrice: _toDouble(json['currentPrice']), issuePrice: json['issuePrice'] != null ? _toDouble(json['issuePrice']) : null, totalSupply: json['totalSupply'] ?? 0, remainingSupply: json['remainingSupply'] ?? 0, category: json['category'] ?? '', status: json['status'] ?? '', couponType: json['couponType'] ?? 'utility', expiryDate: DateTime.tryParse(json['expiryDate']?.toString() ?? '') ?? DateTime.now(), isTransferable: json['isTransferable'] ?? true, resaleCount: json['resaleCount'] ?? 0, maxResaleCount: json['maxResaleCount'] ?? 3, brandName: issuer?['companyName'], brandLogoUrl: issuer?['logoUrl'], creditRating: issuer?['creditRating'], ); } /// 折扣率 (0~1) double get discount => faceValue > 0 ? 1 - currentPrice / faceValue : 0; /// 是否即将过期(7天内) bool get isExpiringSoon => expiryDate.difference(DateTime.now()).inDays <= 7; /// 是否已过期 bool get isExpired => expiryDate.isBefore(DateTime.now()); static double _toDouble(dynamic value) { if (value == null) return 0; if (value is num) return value.toDouble(); return double.tryParse(value.toString()) ?? 0; } }