feat: add accessory API methods and data classes to ApiService
Add accessory-related data classes (WorkOrderGroup, AccessoryTypeInfo, AccessoryRecord), result classes, and 8 API methods for the accessory module. Also add pendingAccessories field to BoxInfoResult. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -195,6 +195,7 @@ class BoxInfoResult {
|
||||
final List<BoxDetailData> existingBoxes;
|
||||
final int maxBoxNo;
|
||||
final int suggestedBoxNo;
|
||||
final int pendingAccessories;
|
||||
|
||||
BoxInfoResult({
|
||||
required this.success,
|
||||
@@ -207,6 +208,7 @@ class BoxInfoResult {
|
||||
this.existingBoxes = const [],
|
||||
this.maxBoxNo = 0,
|
||||
this.suggestedBoxNo = 1,
|
||||
this.pendingAccessories = 0,
|
||||
});
|
||||
|
||||
factory BoxInfoResult.ok(Map<String, dynamic> json) {
|
||||
@@ -232,6 +234,7 @@ class BoxInfoResult {
|
||||
existingBoxes: boxes,
|
||||
maxBoxNo: json['max_box_no'] as int? ?? 0,
|
||||
suggestedBoxNo: json['suggested_box_no'] as int? ?? 1,
|
||||
pendingAccessories: json['pending_accessories'] as int? ?? 0,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -299,6 +302,163 @@ class BoxDeleteResult {
|
||||
BoxDeleteResult({required this.success, this.errorMessage});
|
||||
}
|
||||
|
||||
// === 附件模块数据类 ===
|
||||
|
||||
/// 工令号分组(工令号 → 总排号列表)
|
||||
class WorkOrderGroup {
|
||||
final String workOrderNo;
|
||||
final List<String> zongpaiNos;
|
||||
WorkOrderGroup({required this.workOrderNo, required this.zongpaiNos});
|
||||
factory WorkOrderGroup.fromJson(Map<String, dynamic> json) => WorkOrderGroup(
|
||||
workOrderNo: json['work_order_no'] as String,
|
||||
zongpaiNos: (json['zongpai_nos'] as List).map((e) => e as String).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
/// 附件类型预设
|
||||
class AccessoryTypeInfo {
|
||||
final int id;
|
||||
final String name;
|
||||
final int sortOrder;
|
||||
AccessoryTypeInfo({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.sortOrder,
|
||||
});
|
||||
factory AccessoryTypeInfo.fromJson(Map<String, dynamic> json) =>
|
||||
AccessoryTypeInfo(
|
||||
id: json['id'] as int,
|
||||
name: json['name'] as String,
|
||||
sortOrder: json['sort_order'] as int,
|
||||
);
|
||||
}
|
||||
|
||||
/// 附件记录
|
||||
class AccessoryRecord {
|
||||
final int id;
|
||||
final String paichanNo;
|
||||
final String zongpaiNo;
|
||||
final String accessoryType;
|
||||
final int quantity;
|
||||
final String? locationCode;
|
||||
final bool isBoxed;
|
||||
final String createdAt;
|
||||
AccessoryRecord({
|
||||
required this.id,
|
||||
required this.paichanNo,
|
||||
required this.zongpaiNo,
|
||||
required this.accessoryType,
|
||||
required this.quantity,
|
||||
this.locationCode,
|
||||
required this.isBoxed,
|
||||
required this.createdAt,
|
||||
});
|
||||
factory AccessoryRecord.fromJson(Map<String, dynamic> json) =>
|
||||
AccessoryRecord(
|
||||
id: json['id'] as int,
|
||||
paichanNo: json['paichan_no'] as String,
|
||||
zongpaiNo: json['zongpai_no'] as String,
|
||||
accessoryType: json['accessory_type'] as String,
|
||||
quantity: json['quantity'] as int,
|
||||
locationCode: json['location_code'] as String?,
|
||||
isBoxed: json['is_boxed'] as bool,
|
||||
createdAt: json['created_at'] as String,
|
||||
);
|
||||
}
|
||||
|
||||
// === 附件模块结果类 ===
|
||||
|
||||
class WorkOrderQueryResult {
|
||||
final bool success;
|
||||
final String? paichanNo;
|
||||
final List<WorkOrderGroup> workOrders;
|
||||
final String? errorMessage;
|
||||
final String? errorCode;
|
||||
WorkOrderQueryResult({
|
||||
required this.success,
|
||||
this.paichanNo,
|
||||
this.workOrders = const [],
|
||||
this.errorMessage,
|
||||
this.errorCode,
|
||||
});
|
||||
}
|
||||
|
||||
class AccessoryListResult {
|
||||
final bool success;
|
||||
final List<AccessoryRecord> items;
|
||||
final String? errorMessage;
|
||||
final String? errorCode;
|
||||
AccessoryListResult({
|
||||
required this.success,
|
||||
this.items = const [],
|
||||
this.errorMessage,
|
||||
this.errorCode,
|
||||
});
|
||||
}
|
||||
|
||||
class AccessoryCreateResult {
|
||||
final bool success;
|
||||
final AccessoryRecord? record;
|
||||
final String? errorMessage;
|
||||
final String? errorCode;
|
||||
AccessoryCreateResult({
|
||||
required this.success,
|
||||
this.record,
|
||||
this.errorMessage,
|
||||
this.errorCode,
|
||||
});
|
||||
}
|
||||
|
||||
class AccessoryUpdateResult {
|
||||
final bool success;
|
||||
final AccessoryRecord? record;
|
||||
final String? errorMessage;
|
||||
final String? errorCode;
|
||||
AccessoryUpdateResult({
|
||||
required this.success,
|
||||
this.record,
|
||||
this.errorMessage,
|
||||
this.errorCode,
|
||||
});
|
||||
}
|
||||
|
||||
class AccessoryDeleteResult {
|
||||
final bool success;
|
||||
final String? errorMessage;
|
||||
final String? errorCode;
|
||||
AccessoryDeleteResult({
|
||||
required this.success,
|
||||
this.errorMessage,
|
||||
this.errorCode,
|
||||
});
|
||||
}
|
||||
|
||||
class AccessoryTypeListResult {
|
||||
final bool success;
|
||||
final List<AccessoryTypeInfo> types;
|
||||
final String? errorMessage;
|
||||
final String? errorCode;
|
||||
AccessoryTypeListResult({
|
||||
required this.success,
|
||||
this.types = const [],
|
||||
this.errorMessage,
|
||||
this.errorCode,
|
||||
});
|
||||
}
|
||||
|
||||
class AccessoryTypeCreateResult {
|
||||
final bool success;
|
||||
final AccessoryTypeInfo? type;
|
||||
final String? errorMessage;
|
||||
final String? errorCode;
|
||||
AccessoryTypeCreateResult({
|
||||
required this.success,
|
||||
this.type,
|
||||
this.errorMessage,
|
||||
this.errorCode,
|
||||
});
|
||||
}
|
||||
|
||||
class ApiService {
|
||||
final http.Client _client;
|
||||
final Duration timeout;
|
||||
@@ -524,6 +684,403 @@ class ApiService {
|
||||
}
|
||||
}
|
||||
|
||||
// === 附件模块 API 方法 ===
|
||||
|
||||
/// 工令号查询 — GET /CargoTrace/accessory/work-orders
|
||||
Future<WorkOrderQueryResult> queryWorkOrders({
|
||||
required String baseUrl,
|
||||
required String paichanNo,
|
||||
}) async {
|
||||
final uri = Uri.parse(
|
||||
'$baseUrl/CargoTrace/accessory/work-orders',
|
||||
).replace(queryParameters: {'paichan_no': paichanNo});
|
||||
try {
|
||||
final response = await _client
|
||||
.get(uri, headers: {'Content-Type': 'application/json'})
|
||||
.timeout(timeout);
|
||||
|
||||
switch (response.statusCode) {
|
||||
case 200:
|
||||
final body = jsonDecode(response.body) as Map<String, dynamic>;
|
||||
final workOrders =
|
||||
(body['work_orders'] as List<dynamic>?)
|
||||
?.map(
|
||||
(w) => WorkOrderGroup.fromJson(w as Map<String, dynamic>),
|
||||
)
|
||||
.toList() ??
|
||||
[];
|
||||
return WorkOrderQueryResult(
|
||||
success: true,
|
||||
paichanNo: body['paichan_no'] as String?,
|
||||
workOrders: workOrders,
|
||||
);
|
||||
case 400:
|
||||
final body = jsonDecode(response.body) as Map<String, dynamic>;
|
||||
return WorkOrderQueryResult(
|
||||
success: false,
|
||||
errorMessage: body['message']?.toString() ?? '请求参数错误',
|
||||
errorCode: body['error_code']?.toString(),
|
||||
);
|
||||
case 404:
|
||||
return WorkOrderQueryResult(
|
||||
success: false,
|
||||
errorMessage: '未找到该排产号对应的工令号信息',
|
||||
errorCode: 'NOT_FOUND',
|
||||
);
|
||||
default:
|
||||
return WorkOrderQueryResult(
|
||||
success: false,
|
||||
errorMessage: '查询失败 (${response.statusCode})',
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
return WorkOrderQueryResult(success: false, errorMessage: '网络异常,请检查网络连接');
|
||||
}
|
||||
}
|
||||
|
||||
/// 附件列表查询 — GET /CargoTrace/accessory
|
||||
Future<AccessoryListResult> listAccessories({
|
||||
required String baseUrl,
|
||||
required String paichanNo,
|
||||
}) async {
|
||||
final uri = Uri.parse(
|
||||
'$baseUrl/CargoTrace/accessory',
|
||||
).replace(queryParameters: {'paichan_no': paichanNo});
|
||||
try {
|
||||
final response = await _client
|
||||
.get(uri, headers: {'Content-Type': 'application/json'})
|
||||
.timeout(timeout);
|
||||
|
||||
switch (response.statusCode) {
|
||||
case 200:
|
||||
final body = jsonDecode(response.body) as Map<String, dynamic>;
|
||||
final items =
|
||||
(body['items'] as List<dynamic>?)
|
||||
?.map(
|
||||
(i) => AccessoryRecord.fromJson(i as Map<String, dynamic>),
|
||||
)
|
||||
.toList() ??
|
||||
[];
|
||||
return AccessoryListResult(success: true, items: items);
|
||||
case 400:
|
||||
final body = jsonDecode(response.body) as Map<String, dynamic>;
|
||||
return AccessoryListResult(
|
||||
success: false,
|
||||
errorMessage: body['message']?.toString() ?? '请求参数错误',
|
||||
errorCode: body['error_code']?.toString(),
|
||||
);
|
||||
case 404:
|
||||
return AccessoryListResult(
|
||||
success: false,
|
||||
errorMessage: '未找到该排产号信息',
|
||||
errorCode: 'NOT_FOUND',
|
||||
);
|
||||
default:
|
||||
return AccessoryListResult(
|
||||
success: false,
|
||||
errorMessage: '查询失败 (${response.statusCode})',
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
return AccessoryListResult(success: false, errorMessage: '网络异常,请检查网络连接');
|
||||
}
|
||||
}
|
||||
|
||||
/// 附件创建(上架) — POST /CargoTrace/accessory
|
||||
Future<AccessoryCreateResult> createAccessory({
|
||||
required String baseUrl,
|
||||
required String paichanNo,
|
||||
required String zongpaiNo,
|
||||
required String accessoryType,
|
||||
required int quantity,
|
||||
String? locationCode,
|
||||
}) async {
|
||||
final uri = Uri.parse('$baseUrl/CargoTrace/accessory');
|
||||
try {
|
||||
final bodyMap = <String, dynamic>{
|
||||
'paichan_no': paichanNo,
|
||||
'zongpai_no': zongpaiNo,
|
||||
'accessory_type': accessoryType,
|
||||
'quantity': quantity,
|
||||
};
|
||||
if (locationCode != null) {
|
||||
bodyMap['location_code'] = locationCode;
|
||||
}
|
||||
final response = await _client
|
||||
.post(
|
||||
uri,
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: jsonEncode(bodyMap),
|
||||
)
|
||||
.timeout(timeout);
|
||||
|
||||
switch (response.statusCode) {
|
||||
case 200:
|
||||
final body = jsonDecode(response.body) as Map<String, dynamic>;
|
||||
return AccessoryCreateResult(
|
||||
success: true,
|
||||
record: AccessoryRecord.fromJson(body),
|
||||
);
|
||||
case 400:
|
||||
final body = jsonDecode(response.body) as Map<String, dynamic>;
|
||||
return AccessoryCreateResult(
|
||||
success: false,
|
||||
errorMessage: body['message']?.toString() ?? '请求参数错误',
|
||||
errorCode: body['error_code']?.toString(),
|
||||
);
|
||||
case 404:
|
||||
return AccessoryCreateResult(
|
||||
success: false,
|
||||
errorMessage: '未找到该排产号对应的工令号信息',
|
||||
errorCode: 'NOT_FOUND',
|
||||
);
|
||||
case 409:
|
||||
final body = jsonDecode(response.body) as Map<String, dynamic>;
|
||||
return AccessoryCreateResult(
|
||||
success: false,
|
||||
errorMessage: body['message']?.toString() ?? '冲突',
|
||||
errorCode: body['error_code']?.toString(),
|
||||
);
|
||||
default:
|
||||
return AccessoryCreateResult(
|
||||
success: false,
|
||||
errorMessage: '创建失败 (${response.statusCode})',
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
return AccessoryCreateResult(
|
||||
success: false,
|
||||
errorMessage: '网络异常,请检查网络连接',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 附件更新 — PATCH /CargoTrace/accessory/{id}
|
||||
Future<AccessoryUpdateResult> updateAccessory({
|
||||
required String baseUrl,
|
||||
required int id,
|
||||
String? accessoryType,
|
||||
int? quantity,
|
||||
String? locationCode,
|
||||
}) async {
|
||||
final uri = Uri.parse('$baseUrl/CargoTrace/accessory/$id');
|
||||
try {
|
||||
final bodyMap = <String, dynamic>{};
|
||||
if (accessoryType != null) bodyMap['accessory_type'] = accessoryType;
|
||||
if (quantity != null) bodyMap['quantity'] = quantity;
|
||||
if (locationCode != null) bodyMap['location_code'] = locationCode;
|
||||
|
||||
final response = await _client
|
||||
.patch(
|
||||
uri,
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: jsonEncode(bodyMap),
|
||||
)
|
||||
.timeout(timeout);
|
||||
|
||||
switch (response.statusCode) {
|
||||
case 200:
|
||||
final body = jsonDecode(response.body) as Map<String, dynamic>;
|
||||
return AccessoryUpdateResult(
|
||||
success: true,
|
||||
record: AccessoryRecord.fromJson(body),
|
||||
);
|
||||
case 400:
|
||||
final body = jsonDecode(response.body) as Map<String, dynamic>;
|
||||
return AccessoryUpdateResult(
|
||||
success: false,
|
||||
errorMessage: body['message']?.toString() ?? '请求参数错误',
|
||||
errorCode: body['error_code']?.toString(),
|
||||
);
|
||||
case 404:
|
||||
return AccessoryUpdateResult(
|
||||
success: false,
|
||||
errorMessage: '指定附件记录不存在',
|
||||
errorCode: 'NOT_FOUND',
|
||||
);
|
||||
default:
|
||||
return AccessoryUpdateResult(
|
||||
success: false,
|
||||
errorMessage: '更新失败 (${response.statusCode})',
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
return AccessoryUpdateResult(
|
||||
success: false,
|
||||
errorMessage: '网络异常,请检查网络连接',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 附件删除 — DELETE /CargoTrace/accessory/{id}
|
||||
Future<AccessoryDeleteResult> deleteAccessory({
|
||||
required String baseUrl,
|
||||
required int id,
|
||||
}) async {
|
||||
final uri = Uri.parse('$baseUrl/CargoTrace/accessory/$id');
|
||||
try {
|
||||
final response = await _client
|
||||
.delete(uri, headers: {'Content-Type': 'application/json'})
|
||||
.timeout(timeout);
|
||||
|
||||
switch (response.statusCode) {
|
||||
case 200:
|
||||
return AccessoryDeleteResult(success: true);
|
||||
case 404:
|
||||
return AccessoryDeleteResult(
|
||||
success: false,
|
||||
errorMessage: '指定附件记录不存在',
|
||||
errorCode: 'NOT_FOUND',
|
||||
);
|
||||
default:
|
||||
return AccessoryDeleteResult(
|
||||
success: false,
|
||||
errorMessage: '删除失败 (${response.statusCode})',
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
return AccessoryDeleteResult(
|
||||
success: false,
|
||||
errorMessage: '网络异常,请检查网络连接',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 附件类型列表 — GET /CargoTrace/accessory-type
|
||||
Future<AccessoryTypeListResult> listAccessoryTypes({
|
||||
required String baseUrl,
|
||||
}) async {
|
||||
final uri = Uri.parse('$baseUrl/CargoTrace/accessory-type');
|
||||
try {
|
||||
final response = await _client
|
||||
.get(uri, headers: {'Content-Type': 'application/json'})
|
||||
.timeout(timeout);
|
||||
|
||||
switch (response.statusCode) {
|
||||
case 200:
|
||||
final body = jsonDecode(response.body) as Map<String, dynamic>;
|
||||
final types =
|
||||
(body['types'] as List<dynamic>?)
|
||||
?.map(
|
||||
(t) =>
|
||||
AccessoryTypeInfo.fromJson(t as Map<String, dynamic>),
|
||||
)
|
||||
.toList() ??
|
||||
[];
|
||||
return AccessoryTypeListResult(success: true, types: types);
|
||||
case 400:
|
||||
final body = jsonDecode(response.body) as Map<String, dynamic>;
|
||||
return AccessoryTypeListResult(
|
||||
success: false,
|
||||
errorMessage: body['message']?.toString() ?? '请求参数错误',
|
||||
errorCode: body['error_code']?.toString(),
|
||||
);
|
||||
default:
|
||||
return AccessoryTypeListResult(
|
||||
success: false,
|
||||
errorMessage: '查询失败 (${response.statusCode})',
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
return AccessoryTypeListResult(
|
||||
success: false,
|
||||
errorMessage: '网络异常,请检查网络连接',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 附件类型创建 — POST /CargoTrace/accessory-type
|
||||
Future<AccessoryTypeCreateResult> createAccessoryType({
|
||||
required String baseUrl,
|
||||
required String name,
|
||||
int sortOrder = 0,
|
||||
}) async {
|
||||
final uri = Uri.parse('$baseUrl/CargoTrace/accessory-type');
|
||||
try {
|
||||
final response = await _client
|
||||
.post(
|
||||
uri,
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: jsonEncode({'name': name, 'sort_order': sortOrder}),
|
||||
)
|
||||
.timeout(timeout);
|
||||
|
||||
switch (response.statusCode) {
|
||||
case 200:
|
||||
final body = jsonDecode(response.body) as Map<String, dynamic>;
|
||||
return AccessoryTypeCreateResult(
|
||||
success: true,
|
||||
type: AccessoryTypeInfo.fromJson(body),
|
||||
);
|
||||
case 400:
|
||||
final body = jsonDecode(response.body) as Map<String, dynamic>;
|
||||
return AccessoryTypeCreateResult(
|
||||
success: false,
|
||||
errorMessage: body['message']?.toString() ?? '请求参数错误',
|
||||
errorCode: body['error_code']?.toString(),
|
||||
);
|
||||
case 409:
|
||||
final body = jsonDecode(response.body) as Map<String, dynamic>;
|
||||
return AccessoryTypeCreateResult(
|
||||
success: false,
|
||||
errorMessage: body['message']?.toString() ?? '附件类型已存在',
|
||||
errorCode: body['error_code']?.toString(),
|
||||
);
|
||||
default:
|
||||
return AccessoryTypeCreateResult(
|
||||
success: false,
|
||||
errorMessage: '创建失败 (${response.statusCode})',
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
return AccessoryTypeCreateResult(
|
||||
success: false,
|
||||
errorMessage: '网络异常,请检查网络连接',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 附件类型删除 — DELETE /CargoTrace/accessory-type/{id}
|
||||
Future<AccessoryDeleteResult> deleteAccessoryType({
|
||||
required String baseUrl,
|
||||
required int id,
|
||||
}) async {
|
||||
final uri = Uri.parse('$baseUrl/CargoTrace/accessory-type/$id');
|
||||
try {
|
||||
final response = await _client
|
||||
.delete(uri, headers: {'Content-Type': 'application/json'})
|
||||
.timeout(timeout);
|
||||
|
||||
switch (response.statusCode) {
|
||||
case 200:
|
||||
return AccessoryDeleteResult(success: true);
|
||||
case 404:
|
||||
return AccessoryDeleteResult(
|
||||
success: false,
|
||||
errorMessage: '指定附件类型不存在',
|
||||
errorCode: 'NOT_FOUND',
|
||||
);
|
||||
case 409:
|
||||
final body = jsonDecode(response.body) as Map<String, dynamic>;
|
||||
return AccessoryDeleteResult(
|
||||
success: false,
|
||||
errorMessage: body['message']?.toString() ?? '该类型下存在附件记录,无法删除',
|
||||
errorCode: body['error_code']?.toString(),
|
||||
);
|
||||
default:
|
||||
return AccessoryDeleteResult(
|
||||
success: false,
|
||||
errorMessage: '删除失败 (${response.statusCode})',
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
return AccessoryDeleteResult(
|
||||
success: false,
|
||||
errorMessage: '网络异常,请检查网络连接',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Test connectivity by making a HEAD request to the base URL.
|
||||
Future<bool> testConnection(String baseUrl) async {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user