feat(android): add attachment API methods and result classes
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -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: '网络异常,请检查网络连接',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -372,6 +372,248 @@ void main() {
|
||||
expect(missing.errorMessage, '指定装箱明细不存在');
|
||||
});
|
||||
});
|
||||
|
||||
group('ApiService attachment APIs', () {
|
||||
test('fetchAttachmentCategories parses list', () async {
|
||||
final mockClient = _MockClient((request) async {
|
||||
return http.Response(
|
||||
jsonEncode([
|
||||
{'id': 1, 'name': '检验证书', 'sort_order': 1},
|
||||
{'id': 2, 'name': '增发', 'sort_order': 2},
|
||||
]),
|
||||
200,
|
||||
headers: {'content-type': 'application/json; charset=utf-8'},
|
||||
);
|
||||
});
|
||||
final svc = ApiService(client: mockClient);
|
||||
final result = await svc.fetchAttachmentCategories(
|
||||
baseUrl: 'http://localhost',
|
||||
);
|
||||
expect(result.success, isTrue);
|
||||
expect(result.categories.length, 2);
|
||||
expect(result.categories.first.name, '检验证书');
|
||||
});
|
||||
|
||||
test('fetchAttachmentCategories handles network failure', () async {
|
||||
final mockClient = _MockClient((request) async {
|
||||
throw Exception('Connection refused');
|
||||
});
|
||||
final svc = ApiService(client: mockClient);
|
||||
final result = await svc.fetchAttachmentCategories(
|
||||
baseUrl: 'http://localhost',
|
||||
);
|
||||
expect(result.success, isFalse);
|
||||
expect(result.errorMessage, '网络异常,请检查网络连接');
|
||||
});
|
||||
|
||||
test('fetchAttachmentStatus parses undetermined zongpai', () async {
|
||||
final mockClient = _MockClient((request) async {
|
||||
return http.Response(
|
||||
jsonEncode({
|
||||
'zongpai_no': '26BW0011',
|
||||
'determination': 'undetermined',
|
||||
'all_complete': false,
|
||||
'items': [],
|
||||
}),
|
||||
200,
|
||||
headers: {'content-type': 'application/json; charset=utf-8'},
|
||||
);
|
||||
});
|
||||
final svc = ApiService(client: mockClient);
|
||||
final result = await svc.fetchAttachmentStatus(
|
||||
baseUrl: 'http://localhost',
|
||||
zongpaiNo: '26BW0011',
|
||||
);
|
||||
expect(result.success, isTrue);
|
||||
expect(result.determination, 'undetermined');
|
||||
expect(result.items, isEmpty);
|
||||
});
|
||||
|
||||
test('putAttachmentConfig sends full payload', () async {
|
||||
late Map<String, dynamic> body;
|
||||
final mockClient = _MockClient((request) async {
|
||||
final req = request as http.Request;
|
||||
body = jsonDecode(req.body) as Map<String, dynamic>;
|
||||
return http.Response(
|
||||
jsonEncode({
|
||||
'zongpai_no': '26BW0011',
|
||||
'determination': 'has',
|
||||
'all_complete': false,
|
||||
'items': [
|
||||
{
|
||||
'category_id': 1,
|
||||
'name': '检验证书',
|
||||
'expected_qty': 80,
|
||||
'boxed_qty': 0,
|
||||
'location_code': null,
|
||||
'complete': false,
|
||||
},
|
||||
],
|
||||
}),
|
||||
200,
|
||||
headers: {'content-type': 'application/json; charset=utf-8'},
|
||||
);
|
||||
});
|
||||
final svc = ApiService(client: mockClient);
|
||||
final result = await svc.putAttachmentConfig(
|
||||
baseUrl: 'http://localhost',
|
||||
zongpaiNo: '26BW0011',
|
||||
determination: 'has',
|
||||
items: [AttachmentConfigItemData(categoryId: 1, expectedQty: 80)],
|
||||
);
|
||||
expect(result.success, isTrue);
|
||||
expect(result.items.single.complete, isFalse);
|
||||
expect(body['items'], [
|
||||
{'category_id': 1, 'expected_qty': 80},
|
||||
]);
|
||||
});
|
||||
|
||||
test('registerAttachmentLocation returns 200 with data', () async {
|
||||
final mockClient = _MockClient((request) async {
|
||||
return http.Response(
|
||||
jsonEncode({
|
||||
'zongpai_no': '26BW0011',
|
||||
'category_id': 1,
|
||||
'location_code': 'A01-01-01',
|
||||
'created_at': '2026-08-12T10:00:00',
|
||||
'previous_location': null,
|
||||
}),
|
||||
200,
|
||||
headers: {'content-type': 'application/json; charset=utf-8'},
|
||||
);
|
||||
});
|
||||
final svc = ApiService(client: mockClient);
|
||||
final result = await svc.registerAttachmentLocation(
|
||||
baseUrl: 'http://localhost',
|
||||
zongpaiNo: '26BW0011',
|
||||
categoryId: 1,
|
||||
locationCode: 'A01-01-01',
|
||||
);
|
||||
expect(result.success, isTrue);
|
||||
expect(result.locationCode, 'A01-01-01');
|
||||
});
|
||||
|
||||
test('saveAttachmentBox sends request with category_id', () async {
|
||||
late Map<String, dynamic> body;
|
||||
final mockClient = _MockClient((request) async {
|
||||
final req = request as http.Request;
|
||||
body = jsonDecode(req.body) as Map<String, dynamic>;
|
||||
return http.Response(
|
||||
jsonEncode({
|
||||
'box_item_id': 1,
|
||||
'paichan_no': 'W00009',
|
||||
'box_no': 10,
|
||||
'zongpai_no': '26BW0011',
|
||||
'category_id': 1,
|
||||
'quantity': 30,
|
||||
'created_at': '2026-08-12T10:00:00',
|
||||
}),
|
||||
200,
|
||||
headers: {'content-type': 'application/json; charset=utf-8'},
|
||||
);
|
||||
});
|
||||
final svc = ApiService(client: mockClient);
|
||||
final result = await svc.saveAttachmentBox(
|
||||
baseUrl: 'http://localhost',
|
||||
zongpaiNo: '26BW0011',
|
||||
categoryId: 1,
|
||||
boxNo: 10,
|
||||
quantity: 30,
|
||||
);
|
||||
expect(result.success, isTrue);
|
||||
expect(result.boxItemId, 1);
|
||||
expect(body['category_id'], 1);
|
||||
});
|
||||
|
||||
test('saveAttachmentBox returns duplicate on 409', () async {
|
||||
final mockClient = _MockClient((request) async {
|
||||
return http.Response(
|
||||
jsonEncode({
|
||||
'error_code': 'DUPLICATE_BOX_NO',
|
||||
'message': '排产号 W00009 下箱号 10 已存在',
|
||||
'paichan_no': 'W00009',
|
||||
'box_no': 10,
|
||||
}),
|
||||
409,
|
||||
headers: {'content-type': 'application/json; charset=utf-8'},
|
||||
);
|
||||
});
|
||||
final svc = ApiService(client: mockClient);
|
||||
final result = await svc.saveAttachmentBox(
|
||||
baseUrl: 'http://localhost',
|
||||
zongpaiNo: '26BW0011',
|
||||
categoryId: 1,
|
||||
boxNo: 10,
|
||||
quantity: 30,
|
||||
);
|
||||
expect(result.success, isFalse);
|
||||
expect(result.isDuplicate, isTrue);
|
||||
});
|
||||
|
||||
test('updateAttachmentBox sends PATCH with box_no and quantity', () async {
|
||||
late Map<String, dynamic> body;
|
||||
final mockClient = _MockClient((request) async {
|
||||
final req = request as http.Request;
|
||||
body = jsonDecode(req.body) as Map<String, dynamic>;
|
||||
return http.Response(
|
||||
jsonEncode({
|
||||
'box_item_id': 1,
|
||||
'paichan_no': 'W00009',
|
||||
'box_no': 11,
|
||||
'zongpai_no': '26BW0011',
|
||||
'category_id': 1,
|
||||
'quantity': 20,
|
||||
'updated_at': '2026-08-12T10:00:00',
|
||||
}),
|
||||
200,
|
||||
headers: {'content-type': 'application/json; charset=utf-8'},
|
||||
);
|
||||
});
|
||||
final svc = ApiService(client: mockClient);
|
||||
final result = await svc.updateAttachmentBox(
|
||||
baseUrl: 'http://localhost',
|
||||
boxItemId: 1,
|
||||
boxNo: 11,
|
||||
quantity: 20,
|
||||
);
|
||||
expect(result.success, isTrue);
|
||||
expect(body, {'box_no': 11, 'quantity': 20});
|
||||
});
|
||||
|
||||
test('deleteAttachmentBox handles success and not found', () async {
|
||||
var calls = 0;
|
||||
final mockClient = _MockClient((request) async {
|
||||
calls += 1;
|
||||
if (calls == 1) {
|
||||
return http.Response(
|
||||
jsonEncode({'box_item_id': 1, 'deleted': true}),
|
||||
200,
|
||||
headers: {'content-type': 'application/json; charset=utf-8'},
|
||||
);
|
||||
}
|
||||
return http.Response(
|
||||
jsonEncode({
|
||||
'error_code': 'BOX_ITEM_NOT_FOUND',
|
||||
'message': '指定装箱明细不存在',
|
||||
}),
|
||||
404,
|
||||
headers: {'content-type': 'application/json; charset=utf-8'},
|
||||
);
|
||||
});
|
||||
final svc = ApiService(client: mockClient);
|
||||
final ok = await svc.deleteAttachmentBox(
|
||||
baseUrl: 'http://localhost',
|
||||
boxItemId: 1,
|
||||
);
|
||||
final missing = await svc.deleteAttachmentBox(
|
||||
baseUrl: 'http://localhost',
|
||||
boxItemId: 999,
|
||||
);
|
||||
expect(ok.success, isTrue);
|
||||
expect(missing.success, isFalse);
|
||||
expect(missing.errorMessage, '指定装箱明细不存在');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class _MockClient extends http.BaseClient {
|
||||
|
||||
Reference in New Issue
Block a user