feat(android): add attachment API methods and result classes

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Misaka_Company
2026-08-12 14:51:41 +08:00
parent b383328aed
commit b7484915c6
2 changed files with 742 additions and 0 deletions

View File

@@ -299,6 +299,169 @@ class BoxDeleteResult {
BoxDeleteResult({required this.success, this.errorMessage});
}
// === 附件模块数据类 ===
class AttachmentCategoryData {
final int id;
final String name;
final int sortOrder;
AttachmentCategoryData({
required this.id,
required this.name,
required this.sortOrder,
});
factory AttachmentCategoryData.fromJson(Map<String, dynamic> json) {
return AttachmentCategoryData(
id: json['id'] as int,
name: json['name'] as String,
sortOrder: json['sort_order'] as int,
);
}
}
class AttachmentCategoryResult {
final bool success;
final String? errorMessage;
final List<AttachmentCategoryData> categories;
AttachmentCategoryResult({
required this.success,
this.errorMessage,
this.categories = const [],
});
}
class AttachmentStatusItemData {
final int categoryId;
final String name;
final int expectedQty;
final int boxedQty;
final String? locationCode;
final bool complete;
AttachmentStatusItemData({
required this.categoryId,
required this.name,
required this.expectedQty,
required this.boxedQty,
this.locationCode,
required this.complete,
});
factory AttachmentStatusItemData.fromJson(Map<String, dynamic> json) {
return AttachmentStatusItemData(
categoryId: json['category_id'] as int,
name: json['name'] as String,
expectedQty: json['expected_qty'] as int,
boxedQty: json['boxed_qty'] as int,
locationCode: json['location_code']?.toString(),
complete: json['complete'] as bool,
);
}
}
class AttachmentStatusResult {
final bool success;
final String? errorMessage;
final String determination;
final bool allComplete;
final List<AttachmentStatusItemData> items;
AttachmentStatusResult({
required this.success,
this.errorMessage,
this.determination = 'undetermined',
this.allComplete = false,
this.items = const [],
});
}
class AttachmentConfigItemData {
final int categoryId;
final int expectedQty;
const AttachmentConfigItemData({
required this.categoryId,
required this.expectedQty,
});
Map<String, dynamic> toJson() => {
'category_id': categoryId,
'expected_qty': expectedQty,
};
}
class AttachmentConfigResult {
final bool success;
final String? errorMessage;
final String? errorCode;
final String determination;
final bool allComplete;
final List<AttachmentStatusItemData> items;
AttachmentConfigResult({
required this.success,
this.errorMessage,
this.errorCode,
this.determination = 'undetermined',
this.allComplete = false,
this.items = const [],
});
}
class AttachmentLocationResult {
final bool success;
final bool isDuplicate;
final bool isAlreadyOffShelf;
final String? errorMessage;
final String? locationCode;
final String? previousLocation;
final Map<String, dynamic>? conflictInfo;
AttachmentLocationResult({
required this.success,
this.isDuplicate = false,
this.isAlreadyOffShelf = false,
this.errorMessage,
this.locationCode,
this.previousLocation,
this.conflictInfo,
});
}
class AttachmentBoxResult {
final bool success;
final bool isDuplicate;
final String? errorMessage;
final int? boxItemId;
final String? paichanNo;
final int? boxNo;
final String? zongpaiNo;
final int? categoryId;
final int? quantity;
AttachmentBoxResult({
required this.success,
this.isDuplicate = false,
this.errorMessage,
this.boxItemId,
this.paichanNo,
this.boxNo,
this.zongpaiNo,
this.categoryId,
this.quantity,
});
}
class AttachmentBoxDeleteResult {
final bool success;
final String? errorMessage;
AttachmentBoxDeleteResult({required this.success, this.errorMessage});
}
class ApiService {
final http.Client _client;
final Duration timeout;
@@ -534,4 +697,341 @@ class ApiService {
return false;
}
}
/// 查询附件类型列表 — GET /CargoTrace/attachment/categories
Future<AttachmentCategoryResult> fetchAttachmentCategories({
required String baseUrl,
}) async {
final uri = Uri.parse('$baseUrl/CargoTrace/attachment/categories');
try {
final response = await _client
.get(uri, headers: {'Content-Type': 'application/json'})
.timeout(timeout);
if (response.statusCode == 200) {
final list = (jsonDecode(response.body) as List)
.map(
(e) => AttachmentCategoryData.fromJson(e as Map<String, dynamic>),
)
.toList();
return AttachmentCategoryResult(success: true, categories: list);
}
return AttachmentCategoryResult(
success: false,
errorMessage: '加载失败 (${response.statusCode})',
);
} catch (e) {
return AttachmentCategoryResult(
success: false,
errorMessage: '网络异常,请检查网络连接',
);
}
}
/// 查询附件状态 — GET /CargoTrace/attachment/status
Future<AttachmentStatusResult> fetchAttachmentStatus({
required String baseUrl,
required String zongpaiNo,
}) async {
final uri = Uri.parse(
'$baseUrl/CargoTrace/attachment/status',
).replace(queryParameters: {'zongpai_no': zongpaiNo});
try {
final response = await _client
.get(uri, headers: {'Content-Type': 'application/json'})
.timeout(timeout);
if (response.statusCode == 200) {
final body = jsonDecode(response.body) as Map<String, dynamic>;
final items =
(body['items'] as List?)
?.map(
(e) => AttachmentStatusItemData.fromJson(
e as Map<String, dynamic>,
),
)
.toList() ??
[];
return AttachmentStatusResult(
success: true,
determination: body['determination']?.toString() ?? 'undetermined',
allComplete: body['all_complete'] as bool? ?? false,
items: items,
);
}
return AttachmentStatusResult(
success: false,
errorMessage: '查询失败 (${response.statusCode})',
);
} catch (e) {
return AttachmentStatusResult(
success: false,
errorMessage: '网络异常,请检查网络连接',
);
}
}
/// 定稿附件清单 — PUT /CargoTrace/attachment/config
Future<AttachmentConfigResult> putAttachmentConfig({
required String baseUrl,
required String zongpaiNo,
required String determination,
required List<AttachmentConfigItemData> items,
}) async {
final uri = Uri.parse('$baseUrl/CargoTrace/attachment/config');
try {
final response = await _client
.put(
uri,
headers: {'Content-Type': 'application/json'},
body: jsonEncode({
'zongpai_no': zongpaiNo,
'determination': determination,
'items': items.map((i) => i.toJson()).toList(),
}),
)
.timeout(timeout);
if (response.statusCode == 200) {
final body = jsonDecode(response.body) as Map<String, dynamic>;
final respItems =
(body['items'] as List?)
?.map(
(e) => AttachmentStatusItemData.fromJson(
e as Map<String, dynamic>,
),
)
.toList() ??
[];
return AttachmentConfigResult(
success: true,
determination: body['determination']?.toString() ?? 'has',
allComplete: body['all_complete'] as bool? ?? false,
items: respItems,
);
}
final body = jsonDecode(response.body) as Map<String, dynamic>;
return AttachmentConfigResult(
success: false,
errorMessage: body['message']?.toString() ?? '配置失败',
errorCode: body['error_code']?.toString(),
);
} catch (e) {
return AttachmentConfigResult(
success: false,
errorMessage: '网络异常,请检查网络连接',
);
}
}
/// 附件上架 — POST /CargoTrace/attachment/location
Future<AttachmentLocationResult> registerAttachmentLocation({
required String baseUrl,
required String zongpaiNo,
required int categoryId,
required String locationCode,
}) async {
final uri = Uri.parse('$baseUrl/CargoTrace/attachment/location');
try {
final response = await _client
.post(
uri,
headers: {'Content-Type': 'application/json'},
body: jsonEncode({
'zongpai_no': zongpaiNo,
'category_id': categoryId,
'location_code': locationCode,
}),
)
.timeout(timeout);
switch (response.statusCode) {
case 200:
final body = jsonDecode(response.body) as Map<String, dynamic>;
return AttachmentLocationResult(
success: true,
locationCode: body['location_code']?.toString(),
previousLocation: body['previous_location']?.toString(),
);
case 400:
final body = jsonDecode(response.body) as Map<String, dynamic>;
return AttachmentLocationResult(
success: false,
errorMessage: body['message']?.toString() ?? '请求参数错误',
);
case 409:
final body = jsonDecode(response.body) as Map<String, dynamic>;
final errorCode = body['error_code']?.toString();
if (errorCode == 'DUPLICATE_LOCATION') {
return AttachmentLocationResult(
success: false,
isDuplicate: true,
conflictInfo: body,
);
}
if (errorCode == 'ALREADY_OFF_SHELF') {
return AttachmentLocationResult(
success: false,
isAlreadyOffShelf: true,
errorMessage: body['message']?.toString(),
conflictInfo: body,
);
}
return AttachmentLocationResult(
success: false,
errorMessage: body['message']?.toString() ?? '提交失败',
);
default:
return AttachmentLocationResult(
success: false,
errorMessage: '提交失败 (${response.statusCode})',
);
}
} catch (e) {
return AttachmentLocationResult(
success: false,
errorMessage: '网络异常,请检查网络连接',
);
}
}
/// 附件装箱 — POST /CargoTrace/attachment/box
Future<AttachmentBoxResult> saveAttachmentBox({
required String baseUrl,
required String zongpaiNo,
required int categoryId,
required int boxNo,
required int quantity,
}) async {
final uri = Uri.parse('$baseUrl/CargoTrace/attachment/box');
try {
final response = await _client
.post(
uri,
headers: {'Content-Type': 'application/json'},
body: jsonEncode({
'zongpai_no': zongpaiNo,
'category_id': categoryId,
'box_no': boxNo,
'quantity': quantity,
}),
)
.timeout(timeout);
switch (response.statusCode) {
case 200:
final body = jsonDecode(response.body) as Map<String, dynamic>;
return AttachmentBoxResult(
success: true,
boxItemId: body['box_item_id'] as int?,
paichanNo: body['paichan_no']?.toString(),
boxNo: body['box_no'] as int?,
zongpaiNo: body['zongpai_no']?.toString(),
categoryId: body['category_id'] as int?,
quantity: body['quantity'] as int?,
);
case 400:
final body = jsonDecode(response.body) as Map<String, dynamic>;
return AttachmentBoxResult(
success: false,
errorMessage: body['message']?.toString() ?? '请求参数错误',
);
case 404:
return AttachmentBoxResult(
success: false,
errorMessage: '未找到该总排号或附件类型',
);
case 409:
final body = jsonDecode(response.body) as Map<String, dynamic>;
return AttachmentBoxResult(
success: false,
isDuplicate: true,
errorMessage: body['message']?.toString(),
paichanNo: body['paichan_no']?.toString(),
boxNo: body['box_no'] as int?,
);
default:
return AttachmentBoxResult(
success: false,
errorMessage: '提交失败 (${response.statusCode})',
);
}
} catch (e) {
return AttachmentBoxResult(success: false, errorMessage: '网络异常,请检查网络连接');
}
}
/// 修改附件装箱明细 — PATCH /CargoTrace/attachment/box/{itemId}
Future<AttachmentBoxResult> updateAttachmentBox({
required String baseUrl,
required int boxItemId,
required int boxNo,
required int quantity,
}) async {
final uri = Uri.parse('$baseUrl/CargoTrace/attachment/box/$boxItemId');
try {
final response = await _client
.patch(
uri,
headers: {'Content-Type': 'application/json'},
body: jsonEncode({'box_no': boxNo, 'quantity': quantity}),
)
.timeout(timeout);
switch (response.statusCode) {
case 200:
final body = jsonDecode(response.body) as Map<String, dynamic>;
return AttachmentBoxResult(
success: true,
boxItemId: body['box_item_id'] as int?,
paichanNo: body['paichan_no']?.toString(),
boxNo: body['box_no'] as int?,
zongpaiNo: body['zongpai_no']?.toString(),
categoryId: body['category_id'] as int?,
quantity: body['quantity'] as int?,
);
case 400:
final body = jsonDecode(response.body) as Map<String, dynamic>;
return AttachmentBoxResult(
success: false,
errorMessage: body['message']?.toString() ?? '请求参数错误',
);
case 404:
return AttachmentBoxResult(success: false, errorMessage: '指定装箱明细不存在');
default:
return AttachmentBoxResult(
success: false,
errorMessage: '修改失败 (${response.statusCode})',
);
}
} catch (e) {
return AttachmentBoxResult(success: false, errorMessage: '网络异常,请检查网络连接');
}
}
/// 删除附件装箱明细 — DELETE /CargoTrace/attachment/box/{itemId}
Future<AttachmentBoxDeleteResult> deleteAttachmentBox({
required String baseUrl,
required int boxItemId,
}) async {
final uri = Uri.parse('$baseUrl/CargoTrace/attachment/box/$boxItemId');
try {
final response = await _client
.delete(uri, headers: {'Content-Type': 'application/json'})
.timeout(timeout);
switch (response.statusCode) {
case 200:
return AttachmentBoxDeleteResult(success: true);
case 404:
return AttachmentBoxDeleteResult(
success: false,
errorMessage: '指定装箱明细不存在',
);
default:
return AttachmentBoxDeleteResult(
success: false,
errorMessage: '删除失败 (${response.statusCode})',
);
}
} catch (e) {
return AttachmentBoxDeleteResult(
success: false,
errorMessage: '网络异常,请检查网络连接',
);
}
}
}