1041 lines
31 KiB
Dart
1041 lines
31 KiB
Dart
import 'dart:convert';
|
|
import 'package:http/http.dart' as http;
|
|
|
|
/// Result of a registration API call.
|
|
class RegistrationResult {
|
|
final bool success;
|
|
final bool isDuplicate;
|
|
final bool isAlreadyOffShelf;
|
|
final bool isOffShelfSuccess;
|
|
final String? errorMessage;
|
|
final Map<String, dynamic>? duplicateInfo;
|
|
final Map<String, dynamic>? conflictInfo;
|
|
|
|
RegistrationResult({
|
|
required this.success,
|
|
this.isDuplicate = false,
|
|
this.isAlreadyOffShelf = false,
|
|
this.isOffShelfSuccess = false,
|
|
this.errorMessage,
|
|
this.duplicateInfo,
|
|
this.conflictInfo,
|
|
});
|
|
|
|
factory RegistrationResult.ok() => RegistrationResult(success: true);
|
|
|
|
factory RegistrationResult.offShelfSuccess() =>
|
|
RegistrationResult(success: true, isOffShelfSuccess: true);
|
|
|
|
factory RegistrationResult.duplicate(Map<String, dynamic> info) =>
|
|
RegistrationResult(
|
|
success: false,
|
|
isDuplicate: true,
|
|
duplicateInfo: info,
|
|
conflictInfo: info,
|
|
);
|
|
|
|
factory RegistrationResult.alreadyOffShelf(Map<String, dynamic> info) =>
|
|
RegistrationResult(
|
|
success: false,
|
|
isAlreadyOffShelf: true,
|
|
errorMessage: info['message']?.toString() ?? '该总排号已下架至转运区域,不可重新上架',
|
|
conflictInfo: info,
|
|
);
|
|
|
|
factory RegistrationResult.error(String message) =>
|
|
RegistrationResult(success: false, errorMessage: message);
|
|
}
|
|
|
|
class PaichaOverviewItem {
|
|
final String zongpaiNo;
|
|
final String? workOrderNo;
|
|
final int quantity;
|
|
final String? locationCode;
|
|
final String status;
|
|
|
|
PaichaOverviewItem({
|
|
required this.zongpaiNo,
|
|
this.workOrderNo,
|
|
required this.quantity,
|
|
this.locationCode,
|
|
required this.status,
|
|
});
|
|
|
|
factory PaichaOverviewItem.fromJson(Map<String, dynamic> json) {
|
|
return PaichaOverviewItem(
|
|
zongpaiNo: json['zongpai_no'] as String,
|
|
workOrderNo: json['work_order_no']?.toString(),
|
|
quantity: json['quantity'] as int? ?? 0,
|
|
locationCode: json['location_code']?.toString(),
|
|
status: json['status']?.toString() ?? 'not_shelved',
|
|
);
|
|
}
|
|
}
|
|
|
|
class PaichaOverviewResult {
|
|
final bool success;
|
|
final bool notFound;
|
|
final String? errorMessage;
|
|
final String? paichaNo;
|
|
final int totalCount;
|
|
final List<PaichaOverviewItem> items;
|
|
|
|
PaichaOverviewResult({
|
|
required this.success,
|
|
this.notFound = false,
|
|
this.errorMessage,
|
|
this.paichaNo,
|
|
this.totalCount = 0,
|
|
this.items = const [],
|
|
});
|
|
|
|
factory PaichaOverviewResult.ok(Map<String, dynamic> json) {
|
|
final items =
|
|
(json['items'] as List<dynamic>?)
|
|
?.map(
|
|
(item) =>
|
|
PaichaOverviewItem.fromJson(item as Map<String, dynamic>),
|
|
)
|
|
.toList() ??
|
|
[];
|
|
return PaichaOverviewResult(
|
|
success: true,
|
|
paichaNo: json['paicha_no'] as String?,
|
|
totalCount: json['total_count'] as int? ?? items.length,
|
|
items: items,
|
|
);
|
|
}
|
|
|
|
factory PaichaOverviewResult.notFoundResult() => PaichaOverviewResult(
|
|
success: false,
|
|
notFound: true,
|
|
errorMessage: '暂无排产信息',
|
|
);
|
|
|
|
factory PaichaOverviewResult.error(String message) =>
|
|
PaichaOverviewResult(success: false, errorMessage: message);
|
|
}
|
|
|
|
// === 装箱模块数据类 ===
|
|
|
|
/// 箱号内单个总排号明细
|
|
class BoxItemData {
|
|
final int? boxItemId;
|
|
final String zongpaiNo;
|
|
final String? workOrderNo;
|
|
final int quantity;
|
|
final int? totalQuantity;
|
|
|
|
BoxItemData({
|
|
this.boxItemId,
|
|
required this.zongpaiNo,
|
|
this.workOrderNo,
|
|
required this.quantity,
|
|
this.totalQuantity,
|
|
});
|
|
|
|
factory BoxItemData.fromJson(Map<String, dynamic> json) {
|
|
return BoxItemData(
|
|
boxItemId: json['box_item_id'] as int?,
|
|
zongpaiNo: json['zongpai_no'] as String,
|
|
workOrderNo: json['work_order_no']?.toString(),
|
|
quantity: json['quantity'] as int,
|
|
totalQuantity: json['total_quantity'] as int?,
|
|
);
|
|
}
|
|
}
|
|
|
|
/// 箱号明细
|
|
class BoxDetailData {
|
|
final int boxNo;
|
|
final List<BoxItemData> items;
|
|
|
|
BoxDetailData({required this.boxNo, required this.items});
|
|
|
|
factory BoxDetailData.fromJson(Map<String, dynamic> json) {
|
|
final items =
|
|
(json['items'] as List<dynamic>?)
|
|
?.map((i) => BoxItemData.fromJson(i as Map<String, dynamic>))
|
|
.toList() ??
|
|
[];
|
|
return BoxDetailData(boxNo: json['box_no'] as int, items: items);
|
|
}
|
|
}
|
|
|
|
/// 当前总排号已分配的装箱明细
|
|
class CurrentZongpaiBoxData {
|
|
final int boxItemId;
|
|
final int boxNo;
|
|
final int quantity;
|
|
|
|
CurrentZongpaiBoxData({
|
|
required this.boxItemId,
|
|
required this.boxNo,
|
|
required this.quantity,
|
|
});
|
|
|
|
factory CurrentZongpaiBoxData.fromJson(Map<String, dynamic> json) {
|
|
return CurrentZongpaiBoxData(
|
|
boxItemId: json['box_item_id'] as int,
|
|
boxNo: json['box_no'] as int,
|
|
quantity: json['quantity'] as int,
|
|
);
|
|
}
|
|
}
|
|
|
|
/// 装箱信息查询结果
|
|
class BoxInfoResult {
|
|
final bool success;
|
|
final String? errorMessage;
|
|
final String? zongpaiNo;
|
|
final String? paichanNo;
|
|
final String? workOrderNo;
|
|
final int? quantity;
|
|
final List<CurrentZongpaiBoxData> currentZongpaiBoxes;
|
|
final List<BoxDetailData> existingBoxes;
|
|
final int maxBoxNo;
|
|
final int suggestedBoxNo;
|
|
final Map<String, dynamic>? attachmentSummary;
|
|
|
|
BoxInfoResult({
|
|
required this.success,
|
|
this.errorMessage,
|
|
this.zongpaiNo,
|
|
this.paichanNo,
|
|
this.workOrderNo,
|
|
this.quantity,
|
|
this.currentZongpaiBoxes = const [],
|
|
this.existingBoxes = const [],
|
|
this.maxBoxNo = 0,
|
|
this.suggestedBoxNo = 1,
|
|
this.attachmentSummary,
|
|
});
|
|
|
|
factory BoxInfoResult.ok(Map<String, dynamic> json) {
|
|
final boxes =
|
|
(json['existing_boxes'] as List<dynamic>?)
|
|
?.map((b) => BoxDetailData.fromJson(b as Map<String, dynamic>))
|
|
.toList() ??
|
|
[];
|
|
final currentBoxes =
|
|
(json['current_zongpai_boxes'] as List<dynamic>?)
|
|
?.map(
|
|
(b) => CurrentZongpaiBoxData.fromJson(b as Map<String, dynamic>),
|
|
)
|
|
.toList() ??
|
|
[];
|
|
return BoxInfoResult(
|
|
success: true,
|
|
zongpaiNo: json['zongpai_no'] as String?,
|
|
paichanNo: json['paichan_no'] as String?,
|
|
workOrderNo: json['work_order_no']?.toString(),
|
|
quantity: json['quantity'] as int?,
|
|
currentZongpaiBoxes: currentBoxes,
|
|
existingBoxes: boxes,
|
|
maxBoxNo: json['max_box_no'] as int? ?? 0,
|
|
suggestedBoxNo: json['suggested_box_no'] as int? ?? 1,
|
|
attachmentSummary: json['attachment_summary'] as Map<String, dynamic>?,
|
|
);
|
|
}
|
|
|
|
factory BoxInfoResult.error(String message) {
|
|
return BoxInfoResult(success: false, errorMessage: message);
|
|
}
|
|
}
|
|
|
|
/// 装箱保存结果
|
|
class BoxSaveResult {
|
|
final bool success;
|
|
final bool isDuplicate;
|
|
final String? errorMessage;
|
|
final String? errorCode;
|
|
final int? boxItemId;
|
|
final String? paichanNo;
|
|
final String? zongpaiNo;
|
|
final int? boxNo;
|
|
final int? quantity;
|
|
|
|
BoxSaveResult({
|
|
required this.success,
|
|
this.isDuplicate = false,
|
|
this.errorMessage,
|
|
this.errorCode,
|
|
this.boxItemId,
|
|
this.paichanNo,
|
|
this.zongpaiNo,
|
|
this.boxNo,
|
|
this.quantity,
|
|
});
|
|
|
|
factory BoxSaveResult.ok(Map<String, dynamic> json) {
|
|
return BoxSaveResult(
|
|
success: true,
|
|
boxItemId: json['box_item_id'] as int?,
|
|
paichanNo: json['paichan_no'] as String?,
|
|
zongpaiNo: json['zongpai_no'] as String?,
|
|
boxNo: json['box_no'] as int?,
|
|
quantity: json['quantity'] as int?,
|
|
);
|
|
}
|
|
|
|
factory BoxSaveResult.duplicate(Map<String, dynamic> json) {
|
|
return BoxSaveResult(
|
|
success: false,
|
|
isDuplicate: true,
|
|
errorCode: json['error_code']?.toString(),
|
|
errorMessage: json['message']?.toString(),
|
|
paichanNo: json['paichan_no'] as String?,
|
|
zongpaiNo: json['zongpai_no'] as String?,
|
|
boxNo: json['box_no'] as int?,
|
|
);
|
|
}
|
|
|
|
factory BoxSaveResult.error(String message) {
|
|
return BoxSaveResult(success: false, errorMessage: message);
|
|
}
|
|
}
|
|
|
|
class BoxDeleteResult {
|
|
final bool success;
|
|
final String? errorMessage;
|
|
|
|
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;
|
|
|
|
ApiService({http.Client? client, this.timeout = const Duration(seconds: 5)})
|
|
: _client = client ?? http.Client();
|
|
|
|
/// Submit a shelf registration (上架登记).
|
|
Future<RegistrationResult> registerLocation({
|
|
required String baseUrl,
|
|
required String zongpaiNo,
|
|
required String locationCode,
|
|
}) async {
|
|
final uri = Uri.parse('$baseUrl/CargoTrace/location');
|
|
try {
|
|
final response = await _client
|
|
.post(
|
|
uri,
|
|
headers: {'Content-Type': 'application/json'},
|
|
body: jsonEncode({
|
|
'zongpai_no': zongpaiNo,
|
|
'location_code': locationCode,
|
|
}),
|
|
)
|
|
.timeout(timeout);
|
|
|
|
switch (response.statusCode) {
|
|
case 200:
|
|
final body = jsonDecode(response.body) as Map<String, dynamic>;
|
|
if (body['previous_location'] != null) {
|
|
return RegistrationResult.offShelfSuccess();
|
|
}
|
|
return RegistrationResult.ok();
|
|
case 400:
|
|
final body = jsonDecode(response.body) as Map<String, dynamic>;
|
|
final msg = body['message']?.toString() ?? '请求参数错误';
|
|
return RegistrationResult.error(msg);
|
|
case 409:
|
|
final body = jsonDecode(response.body) as Map<String, dynamic>;
|
|
final errorCode = body['error_code']?.toString();
|
|
if (errorCode == 'DUPLICATE_LOCATION') {
|
|
return RegistrationResult.duplicate(body);
|
|
}
|
|
if (errorCode == 'ALREADY_OFF_SHELF') {
|
|
return RegistrationResult.alreadyOffShelf(body);
|
|
}
|
|
final msg = body['message']?.toString() ?? '提交失败';
|
|
return RegistrationResult.error(msg);
|
|
default:
|
|
final body = jsonDecode(response.body) as Map<String, dynamic>;
|
|
final msg =
|
|
body['message'] ??
|
|
body['error'] ??
|
|
'Unknown error (${response.statusCode})';
|
|
return RegistrationResult.error(msg.toString());
|
|
}
|
|
} catch (e) {
|
|
return RegistrationResult.error('网络异常,请检查网络连接');
|
|
}
|
|
}
|
|
|
|
Future<PaichaOverviewResult> fetchPaichaOverview({
|
|
required String baseUrl,
|
|
required String zongpaiNo,
|
|
}) async {
|
|
final uri = Uri.parse(
|
|
'$baseUrl/CargoTrace/location/paicha-overview',
|
|
).replace(queryParameters: {'zongpai_no': zongpaiNo});
|
|
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>;
|
|
return PaichaOverviewResult.ok(body);
|
|
case 400:
|
|
return PaichaOverviewResult.error('无效的总排号格式');
|
|
case 404:
|
|
return PaichaOverviewResult.notFoundResult();
|
|
default:
|
|
return PaichaOverviewResult.error('加载失败,点击重试');
|
|
}
|
|
} catch (e) {
|
|
return PaichaOverviewResult.error('加载失败,点击重试');
|
|
}
|
|
}
|
|
|
|
/// 查询装箱信息 — GET /CargoTrace/box/info
|
|
Future<BoxInfoResult> fetchBoxInfo({
|
|
required String baseUrl,
|
|
required String zongpaiNo,
|
|
}) async {
|
|
final uri = Uri.parse(
|
|
'$baseUrl/CargoTrace/box/info',
|
|
).replace(queryParameters: {'zongpai_no': zongpaiNo});
|
|
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>;
|
|
return BoxInfoResult.ok(body);
|
|
case 400:
|
|
return BoxInfoResult.error('无效的总排号格式');
|
|
case 404:
|
|
return BoxInfoResult.error('未找到该总排号对应的排产号信息');
|
|
default:
|
|
return BoxInfoResult.error('查询失败 (${response.statusCode})');
|
|
}
|
|
} catch (e) {
|
|
return BoxInfoResult.error('网络异常,请检查网络连接');
|
|
}
|
|
}
|
|
|
|
/// 保存装箱记录 — POST /CargoTrace/box
|
|
Future<BoxSaveResult> saveBoxRecord({
|
|
required String baseUrl,
|
|
required String zongpaiNo,
|
|
required int boxNo,
|
|
required int quantity,
|
|
}) async {
|
|
final uri = Uri.parse('$baseUrl/CargoTrace/box');
|
|
try {
|
|
final response = await _client
|
|
.post(
|
|
uri,
|
|
headers: {'Content-Type': 'application/json'},
|
|
body: jsonEncode({
|
|
'zongpai_no': zongpaiNo,
|
|
'box_no': boxNo,
|
|
'quantity': quantity,
|
|
}),
|
|
)
|
|
.timeout(timeout);
|
|
|
|
switch (response.statusCode) {
|
|
case 200:
|
|
final body = jsonDecode(response.body) as Map<String, dynamic>;
|
|
return BoxSaveResult.ok(body);
|
|
case 400:
|
|
final body = jsonDecode(response.body) as Map<String, dynamic>;
|
|
final msg = body['message']?.toString() ?? '请求参数错误';
|
|
return BoxSaveResult.error(msg);
|
|
case 409:
|
|
final body = jsonDecode(response.body) as Map<String, dynamic>;
|
|
return BoxSaveResult.duplicate(body);
|
|
case 404:
|
|
return BoxSaveResult.error('未找到该总排号对应的排产号信息');
|
|
default:
|
|
return BoxSaveResult.error('提交失败 (${response.statusCode})');
|
|
}
|
|
} catch (e) {
|
|
return BoxSaveResult.error('网络异常,请检查网络连接');
|
|
}
|
|
}
|
|
|
|
/// 更新装箱明细 — PATCH /CargoTrace/box/{boxItemId}
|
|
Future<BoxSaveResult> updateBoxRecord({
|
|
required String baseUrl,
|
|
required int boxItemId,
|
|
required int boxNo,
|
|
required int quantity,
|
|
}) async {
|
|
final uri = Uri.parse('$baseUrl/CargoTrace/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 BoxSaveResult.ok(body);
|
|
case 400:
|
|
final body = jsonDecode(response.body) as Map<String, dynamic>;
|
|
final msg = body['message']?.toString() ?? '请求参数错误';
|
|
return BoxSaveResult.error(msg);
|
|
case 409:
|
|
final body = jsonDecode(response.body) as Map<String, dynamic>;
|
|
return BoxSaveResult.duplicate(body);
|
|
case 404:
|
|
return BoxSaveResult.error('指定装箱明细不存在');
|
|
default:
|
|
return BoxSaveResult.error('修改失败 (${response.statusCode})');
|
|
}
|
|
} catch (e) {
|
|
return BoxSaveResult.error('网络异常,请检查网络连接');
|
|
}
|
|
}
|
|
|
|
/// 删除装箱明细 — DELETE /CargoTrace/box/{boxItemId}
|
|
Future<BoxDeleteResult> deleteBoxRecord({
|
|
required String baseUrl,
|
|
required int boxItemId,
|
|
}) async {
|
|
final uri = Uri.parse('$baseUrl/CargoTrace/box/$boxItemId');
|
|
try {
|
|
final response = await _client
|
|
.delete(uri, headers: {'Content-Type': 'application/json'})
|
|
.timeout(timeout);
|
|
|
|
switch (response.statusCode) {
|
|
case 200:
|
|
return BoxDeleteResult(success: true);
|
|
case 404:
|
|
return BoxDeleteResult(success: false, errorMessage: '指定装箱明细不存在');
|
|
default:
|
|
return BoxDeleteResult(
|
|
success: false,
|
|
errorMessage: '删除失败 (${response.statusCode})',
|
|
);
|
|
}
|
|
} catch (e) {
|
|
return BoxDeleteResult(success: false, errorMessage: '网络异常,请检查网络连接');
|
|
}
|
|
}
|
|
|
|
/// Test connectivity by making a HEAD request to the base URL.
|
|
Future<bool> testConnection(String baseUrl) async {
|
|
try {
|
|
final uri = Uri.parse(baseUrl);
|
|
final response = await _client.head(uri).timeout(timeout);
|
|
return response.statusCode < 500;
|
|
} catch (_) {
|
|
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: '网络异常,请检查网络连接',
|
|
);
|
|
}
|
|
}
|
|
}
|