Files
CloudHome-Ops/lib/models/org_node.dart

274 lines
8.5 KiB
Dart
Raw Permalink Normal View History

/// 组织节点模型。
///
/// 对应后端 /sys/org/* 接口的组织实体,支持树形结构。
/// type 取值:0=平台 1=渠道 2=代理商 3=门店 4=设备分组。
class OrgNode {
const OrgNode({
required this.id,
required this.name,
required this.type,
required this.parentId,
this.children = const [],
required this.address,
required this.contactPhone,
required this.activationCode,
this.province = '',
this.city = '',
this.themeId,
DateTime? createdAt,
this.expireTime,
int? groupLimit,
this.orderNo = 0,
this.status = 'ACTIVE',
this.mpath = '',
}) : createdAt = createdAt,
groupLimit = groupLimit;
/// 组织节点 ID。
final String id;
/// 组织名称。
final String name;
/// 类型(0=平台 1=渠道 2=代理商 3=门店 4=设备分组)。
final int type;
/// 父级 ID。
final String parentId;
/// 子节点列表(树形结构)。
final List<OrgNode> children;
/// 地址。
final String address;
/// 联系电话。
final String contactPhone;
/// 省(新接口 /sys/org/tree 返回 province)。
final String province;
/// 市(新接口 /sys/org/tree 返回 city)。
final String city;
/// 主题 ID(新接口 /sys/org/tree 返回 themeId)。
final int? themeId;
/// 激活码。
final String activationCode;
/// 创建时间(组织树接口返回 createdAt,ISO8601 字符串)。
///
/// 门店列表需按建店时间倒序排列,故从组织树节点透传该字段;
/// 老记录可能缺省(null),此时排序回退到 orgId 数值倒序。
final DateTime? createdAt;
/// 到期时间(组织树/门店接口可能返回 expireTime/expire_time/expiredTime)。
///
/// 门店预览列表需展示到期状态(已过期 / 无到期时间),故从组织树节点透传该字段。
final DateTime? expireTime;
/// 设备总数上限(组织树接口返回 groupLimit)。
///
/// 「建店时设置的设备上限」需回显并随门店保存,故从组织树节点透传。
final int? groupLimit;
/// 排序号(组织接口返回 orderNo,渠道商管理页按此排序展示)。
final int orderNo;
/// 状态:'ACTIVE'=启用,'DISABLED'=禁用。
///
/// 后端可能返回字符串(ACTIVE/DISABLED)或整型(0=正常/1=禁用,
/// 与 [AppVersion.status] 约定一致),统一规整为字符串便于 UI 判断。
final String status;
/// 组织编码(mpath 路径编码,渠道商管理页「组织编码」列展示)。
///
/// 后端未返回时回退为空串,UI 层再降级显示节点 id。
final String mpath;
/// 后端组织类型字符串到数字的映射。
/// 旧接口用 int:0=平台 1=渠道 2=代理商 3=门店 4=设备分组。
/// 新接口 /sys/org/tree 用字符串 type:CHANNEL / SERVICE_PROVIDER / STORE / GROUP。
/// 兼容 `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'));
}
/// 解析可空整型(设备上限 groupLimit 等)。
static int? _parseInt(dynamic value) {
if (value == null) return null;
return int.tryParse(value.toString());
}
static int _parseType(dynamic value) {
if (value == null) return 0;
if (value is int) return value;
final s = value.toString().toUpperCase();
switch (s) {
case 'CHANNEL':
case '渠道':
return 1;
case 'SERVICE_PROVIDER':
case '代理商':
return 2;
case 'STORE':
case '门店':
return 3;
case 'GROUP':
case '设备分组':
return 4;
default:
return int.tryParse(s) ?? 0;
}
}
/// 解析组织状态,统一规整为 'ACTIVE' / 'DISABLED'。
///
/// 兼容后端两种形态:字符串(ACTIVE/DISABLED、启用/禁用)与整型
/// (与 [AppVersion.status] 一致:0=正常 1=禁用)。
static String _parseStatus(dynamic value) {
if (value == null) return 'ACTIVE';
if (value is int) return value == 0 ? 'ACTIVE' : 'DISABLED';
final s = value.toString().toUpperCase();
if (s == 'DISABLED' || s == '禁用') return 'DISABLED';
return 'ACTIVE';
}
factory OrgNode.fromJson(Map<String, dynamic> json) {
final childrenRaw = json['list'] ?? json['children'];
return OrgNode(
// 对齐 Web 端 normalizeOrg:id 兼容 orgId / org_id,parentId 兼容 parent_id,
// 避免后端偶发返回 orgId/parent_id 时节点 id 为空导致展开/编辑/删除失效。
id: json['id']?.toString() ??
json['orgId']?.toString() ??
json['org_id']?.toString() ??
'',
name: json['name']?.toString() ?? '',
type: _parseType(json['type']),
parentId: json['parentId']?.toString() ??
json['parent_id']?.toString() ??
'',
children: childrenRaw is List
? childrenRaw
.whereType<Map<String, dynamic>>()
.map(OrgNode.fromJson)
.toList()
: const <OrgNode>[],
address: json['address']?.toString() ?? '',
contactPhone: json['contactPhone']?.toString() ?? json['phone']?.toString() ?? '',
activationCode: json['activationCode']?.toString() ?? '',
createdAt: _parseDateTime(
json['createdAt'] ??
json['createTime'] ??
json['created_time'] ??
json['created_at'],
),
expireTime: _parseDateTime(
json['expireTime'] ?? json['expire_time'] ?? json['expiredTime'],
),
groupLimit: _parseInt(
json['groupLimit'] ?? json['deviceLimit'] ?? json['device_limit'],
),
province: json['province']?.toString() ?? '',
city: json['city']?.toString() ?? '',
themeId: json['themeId'] is int
? json['themeId'] as int
: int.tryParse(json['themeId']?.toString() ?? ''),
orderNo: _parseInt(json['orderNo'] ?? json['order_no']) ?? 0,
status: _parseStatus(json['status']),
mpath: json['mpath']?.toString() ??
json['orgCode']?.toString() ??
json['org_code']?.toString() ??
'',
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'name': name,
'type': type,
'parentId': parentId,
'children': children.map((e) => e.toJson()).toList(),
'address': address,
'contactPhone': contactPhone,
'activationCode': activationCode,
'province': province,
'city': city,
'themeId': themeId,
'orderNo': orderNo,
'status': status,
'mpath': mpath,
'expireTime': expireTime?.toIso8601String(),
};
}
/// 不可变拷贝(主要用于渠道商管理页给节点挂载子节点树)。
OrgNode copyWith({
String? id,
String? name,
int? type,
String? parentId,
List<OrgNode>? children,
String? address,
String? contactPhone,
String? activationCode,
String? province,
String? city,
int? themeId,
DateTime? createdAt,
DateTime? expireTime,
int? groupLimit,
int? orderNo,
String? status,
String? mpath,
}) {
return OrgNode(
id: id ?? this.id,
name: name ?? this.name,
type: type ?? this.type,
parentId: parentId ?? this.parentId,
children: children ?? this.children,
address: address ?? this.address,
contactPhone: contactPhone ?? this.contactPhone,
activationCode: activationCode ?? this.activationCode,
province: province ?? this.province,
city: city ?? this.city,
themeId: themeId ?? this.themeId,
createdAt: createdAt ?? this.createdAt,
expireTime: expireTime ?? this.expireTime,
groupLimit: groupLimit ?? this.groupLimit,
orderNo: orderNo ?? this.orderNo,
status: status ?? this.status,
mpath: mpath ?? this.mpath,
);
}
/// 类型展示文案(渠道商管理页徽章用)。
String get typeLabel {
switch (type) {
case 1:
return '渠道';
case 2:
return '代理';
case 3:
return '门店';
case 4:
return '设备分组';
default:
return '平台';
}
}
/// 是否已禁用。
bool get isDisabled => status.toUpperCase() == 'DISABLED';
/// 「组织编码」列展示值:优先 mpath,缺省时降级为节点 id。
String get orgCodeLabel => mpath.isNotEmpty ? mpath : id;
}