/// 门店配置(到期时间 / 禁用状态等)。 /// /// 对应后端 `GET /device/storeConfig/info/{orgId}`,与 Web 后台「门店配置」弹窗 /// 同源(参考 screen-front `src/api/device.ts` 的 `getStoreConfig`)。 /// /// 重要:组织树 `/sys/org/tree` 与通用门店列表接口(`myStores` / `listByLoginOrg`) /// 通常**不返回** `expireTime`;移动端门店的到期时间必须由此端点补全, /// 否则会恒为「无到期时间」。 class StoreConfig { const StoreConfig({ this.expireTime, this.mediaVolume, this.disabled = false, }); /// 到期时间(null 表示不限/未设置)。 final DateTime? expireTime; /// 媒体区域音量(0-100,null 表示未设置)。 final int? mediaVolume; /// 是否禁用(后端用 bool 或 '1'/0)。 final bool disabled; factory StoreConfig.fromJson(Map json) { final rawDisabled = json['disabled']; final disabled = rawDisabled == true || (rawDisabled is num && rawDisabled != 0) || (rawDisabled is String && rawDisabled == '1'); return StoreConfig( expireTime: _parseDateTime( json['expireTime'] ?? json['expire_time'] ?? json['expiredTime'], ), mediaVolume: _parseInt(json['mediaVolume'] ?? json['media_volume']), disabled: disabled, ); } /// 空配置(接口异常/无数据时的占位)。 static StoreConfig empty() => const StoreConfig(); /// 兼容 `yyyy-MM-dd HH:mm:ss` 与 ISO 8601 两种时间格式。 static DateTime? _parseDateTime(dynamic value) { if (value == null) return null; final text = value.toString().trim(); if (text.isEmpty) return null; return DateTime.tryParse(text) ?? DateTime.tryParse(text.replaceFirst(' ', 'T')); } /// 兼容 int / num / String 的整数解析(null 安全)。 static int? _parseInt(dynamic value) { if (value == null) return null; if (value is int) return value; final text = value.toString().trim(); if (text.isEmpty) return null; return int.tryParse(text); } }