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? duplicateInfo; final Map? 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 info) => RegistrationResult( success: false, isDuplicate: true, duplicateInfo: info, conflictInfo: info, ); factory RegistrationResult.alreadyOffShelf(Map info) => RegistrationResult( success: false, isAlreadyOffShelf: true, errorMessage: info['message']?.toString() ?? '该总排号已下架至转运区域,不可重新上架', conflictInfo: info, ); factory RegistrationResult.error(String message) => RegistrationResult(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 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 items; BoxDetailData({required this.boxNo, required this.items}); factory BoxDetailData.fromJson(Map json) { final items = (json['items'] as List?) ?.map((i) => BoxItemData.fromJson(i as Map)) .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 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 currentZongpaiBoxes; final List existingBoxes; final int maxBoxNo; final int suggestedBoxNo; 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, }); factory BoxInfoResult.ok(Map json) { final boxes = (json['existing_boxes'] as List?) ?.map((b) => BoxDetailData.fromJson(b as Map)) .toList() ?? []; final currentBoxes = (json['current_zongpai_boxes'] as List?) ?.map( (b) => CurrentZongpaiBoxData.fromJson(b as Map), ) .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, ); } 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 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 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 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 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; if (body['previous_location'] != null) { return RegistrationResult.offShelfSuccess(); } return RegistrationResult.ok(); case 400: final body = jsonDecode(response.body) as Map; final msg = body['message']?.toString() ?? '请求参数错误'; return RegistrationResult.error(msg); case 409: final body = jsonDecode(response.body) as Map; 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; final msg = body['message'] ?? body['error'] ?? 'Unknown error (${response.statusCode})'; return RegistrationResult.error(msg.toString()); } } catch (e) { return RegistrationResult.error('网络异常,请检查网络连接'); } } /// 查询装箱信息 — GET /CargoTrace/box/info Future 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; 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 saveBoxRecord({ required String baseUrl, required String zongpaiNo, required int boxNo, required int quantity, required String boxMode, }) 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, 'box_mode': boxMode, }), ) .timeout(timeout); switch (response.statusCode) { case 200: final body = jsonDecode(response.body) as Map; return BoxSaveResult.ok(body); case 400: final body = jsonDecode(response.body) as Map; final msg = body['message']?.toString() ?? '请求参数错误'; return BoxSaveResult.error(msg); case 409: final body = jsonDecode(response.body) as Map; 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 updateBoxRecord({ required String baseUrl, required int boxItemId, required int boxNo, required int quantity, String boxMode = 'one-to-many', }) 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, 'box_mode': boxMode, }), ) .timeout(timeout); switch (response.statusCode) { case 200: final body = jsonDecode(response.body) as Map; return BoxSaveResult.ok(body); case 400: final body = jsonDecode(response.body) as Map; final msg = body['message']?.toString() ?? '请求参数错误'; return BoxSaveResult.error(msg); case 409: final body = jsonDecode(response.body) as Map; 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 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 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; } } }