From 9aa5d86fdec60d99d7a0ba49a094c6812187b419 Mon Sep 17 00:00:00 2001 From: Misaka Date: Sun, 24 May 2026 17:46:14 +0800 Subject: [PATCH] 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 --- lib/services/api_service.dart | 557 ++++++++++++++++++++++++++++++++++ 1 file changed, 557 insertions(+) diff --git a/lib/services/api_service.dart b/lib/services/api_service.dart index 27dbde0..3d4f8d5 100644 --- a/lib/services/api_service.dart +++ b/lib/services/api_service.dart @@ -195,6 +195,7 @@ class BoxInfoResult { final List 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 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 zongpaiNos; + WorkOrderGroup({required this.workOrderNo, required this.zongpaiNos}); + factory WorkOrderGroup.fromJson(Map 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 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 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 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 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 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 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; + final workOrders = + (body['work_orders'] as List?) + ?.map( + (w) => WorkOrderGroup.fromJson(w as Map), + ) + .toList() ?? + []; + return WorkOrderQueryResult( + success: true, + paichanNo: body['paichan_no'] as String?, + workOrders: workOrders, + ); + case 400: + final body = jsonDecode(response.body) as Map; + 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 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; + final items = + (body['items'] as List?) + ?.map( + (i) => AccessoryRecord.fromJson(i as Map), + ) + .toList() ?? + []; + return AccessoryListResult(success: true, items: items); + case 400: + final body = jsonDecode(response.body) as Map; + 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 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 = { + '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; + return AccessoryCreateResult( + success: true, + record: AccessoryRecord.fromJson(body), + ); + case 400: + final body = jsonDecode(response.body) as Map; + 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; + 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 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 = {}; + 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; + return AccessoryUpdateResult( + success: true, + record: AccessoryRecord.fromJson(body), + ); + case 400: + final body = jsonDecode(response.body) as Map; + 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 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 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; + final types = + (body['types'] as List?) + ?.map( + (t) => + AccessoryTypeInfo.fromJson(t as Map), + ) + .toList() ?? + []; + return AccessoryTypeListResult(success: true, types: types); + case 400: + final body = jsonDecode(response.body) as Map; + 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 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; + return AccessoryTypeCreateResult( + success: true, + type: AccessoryTypeInfo.fromJson(body), + ); + case 400: + final body = jsonDecode(response.body) as Map; + return AccessoryTypeCreateResult( + success: false, + errorMessage: body['message']?.toString() ?? '请求参数错误', + errorCode: body['error_code']?.toString(), + ); + case 409: + final body = jsonDecode(response.body) as Map; + 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 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; + 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 testConnection(String baseUrl) async { try {