feat(boxing): support v2.5 dual modes

This commit is contained in:
Misaka_Company
2026-05-14 12:29:33 +08:00
parent 077a1ab1b9
commit 28871eb21f
3 changed files with 161 additions and 118 deletions

View File

@@ -11,9 +11,8 @@ import 'package:pad_scanner/widgets/status_bar.dart';
// === 装箱模式 === // === 装箱模式 ===
enum BoxingMode { enum BoxingMode {
one2one, // 一码一 singleCode, // 单码装
one2many, // 一码多箱 multiCode, // 多码凑
many2one, // 多码一箱
} }
// === 页面阶段 === // === 页面阶段 ===
@@ -75,7 +74,7 @@ class _BoxingPageState extends State<BoxingPage> {
final _feedbackService = FeedbackService(); final _feedbackService = FeedbackService();
// 模式 // 模式
BoxingMode _mode = BoxingMode.one2one; BoxingMode _mode = BoxingMode.singleCode;
// 阶段 // 阶段
_Phase _phase = _Phase.waiting; _Phase _phase = _Phase.waiting;
@@ -97,7 +96,7 @@ class _BoxingPageState extends State<BoxingPage> {
final _boxNoFocusNode = FocusNode(); final _boxNoFocusNode = FocusNode();
final _quantityFocusNode = FocusNode(); final _quantityFocusNode = FocusNode();
// 多码箱:本次操作已确认装入当前箱的明细 // 多码箱:本次操作已确认装入当前箱的明细
final _manyToOnePackedItems = <_ManyToOnePackedItem>[]; final _manyToOnePackedItems = <_ManyToOnePackedItem>[];
int? _editingPackedItemId; int? _editingPackedItemId;
String _editingPackedQuantity = ''; String _editingPackedQuantity = '';
@@ -106,7 +105,7 @@ class _BoxingPageState extends State<BoxingPage> {
// 提交中 // 提交中
bool _isSubmitting = false; bool _isSubmitting = false;
// 一码多箱:上次提交的箱号(用于继续添加预填) // 单码装箱:上次提交的箱号(用于继续添加预填)
int? _lastBoxNo; int? _lastBoxNo;
CurrentZongpaiBoxData? _editingBox; CurrentZongpaiBoxData? _editingBox;
@@ -138,7 +137,7 @@ class _BoxingPageState extends State<BoxingPage> {
} }
void _onBoxNoTextChanged() { void _onBoxNoTextChanged() {
if (!mounted || _mode != BoxingMode.many2one) return; if (!mounted || _mode != BoxingMode.multiCode) return;
setState(() { setState(() {
_editingPackedItemId = null; _editingPackedItemId = null;
_editingPackedQuantity = ''; _editingPackedQuantity = '';
@@ -150,12 +149,10 @@ class _BoxingPageState extends State<BoxingPage> {
String get _modeLabel { String get _modeLabel {
switch (_mode) { switch (_mode) {
case BoxingMode.one2one: case BoxingMode.singleCode:
return '一码一'; return '单码装';
case BoxingMode.one2many: case BoxingMode.multiCode:
return '一码多箱'; return '码凑';
case BoxingMode.many2one:
return '多码一箱';
} }
} }
@@ -163,12 +160,10 @@ class _BoxingPageState extends State<BoxingPage> {
_feedbackService.trigger(FeedbackEvent.modeSwitch); _feedbackService.trigger(FeedbackEvent.modeSwitch);
setState(() { setState(() {
switch (_mode) { switch (_mode) {
case BoxingMode.one2one: case BoxingMode.singleCode:
_mode = BoxingMode.one2many; _mode = BoxingMode.multiCode;
case BoxingMode.one2many: case BoxingMode.multiCode:
_mode = BoxingMode.many2one; _mode = BoxingMode.singleCode;
case BoxingMode.many2one:
_mode = BoxingMode.one2one;
} }
_resetState(); _resetState();
}); });
@@ -273,18 +268,31 @@ class _BoxingPageState extends State<BoxingPage> {
void _applyAutoFill() { void _applyAutoFill() {
switch (_mode) { switch (_mode) {
case BoxingMode.one2one: case BoxingMode.singleCode:
_boxNoController.text = (_maxBoxNo + 1).toString(); final remaining = _remainingQuantity;
_quantityController.text = (_erpQuantity ?? 0).toString(); if (remaining <= 0) {
case BoxingMode.one2many: _boxNoController.clear();
_quantityController.clear();
_statusOverrideText = '该总排号已全部装箱完毕';
_statusOverrideDot = StatusDotColor.red;
return;
}
if (_lastBoxNo != null) { if (_lastBoxNo != null) {
_boxNoController.text = (_lastBoxNo! + 1).toString(); _boxNoController.text = (_lastBoxNo! + 1).toString();
_quantityController.clear();
} else { } else {
_boxNoController.text = (_maxBoxNo + 1).toString(); _boxNoController.text = (_maxBoxNo + 1).toString();
_quantityController.clear();
} }
case BoxingMode.many2one: _quantityController.text = remaining.toString();
_quantityController.selection = TextSelection(
baseOffset: 0,
extentOffset: _quantityController.text.length,
);
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted && _mode == BoxingMode.singleCode) {
_quantityFocusNode.requestFocus();
}
});
case BoxingMode.multiCode:
if (_boxNoController.text.trim().isEmpty) { if (_boxNoController.text.trim().isEmpty) {
_boxNoController.text = (_maxBoxNo + 1).toString(); _boxNoController.text = (_maxBoxNo + 1).toString();
} }
@@ -294,41 +302,60 @@ class _BoxingPageState extends State<BoxingPage> {
extentOffset: _quantityController.text.length, extentOffset: _quantityController.text.length,
); );
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted && _mode == BoxingMode.many2one) { if (mounted && _mode == BoxingMode.multiCode) {
_quantityFocusNode.requestFocus(); _quantityFocusNode.requestFocus();
} }
}); });
} }
_checkDuplicateBoxNo(); _isDuplicateBoxNo = _boxNoIsDuplicate();
} }
// === 重复箱号检测 === // === 重复箱号检测 ===
void _checkDuplicateBoxNo() { bool _boxNoIsDuplicate() {
final boxNo = int.tryParse(_boxNoController.text); final boxNo = int.tryParse(_boxNoController.text);
if (boxNo == null) { if (boxNo == null) {
setState(() => _isDuplicateBoxNo = false); return false;
return;
} }
final exists = switch (_mode) { return switch (_mode) {
BoxingMode.many2one => false, BoxingMode.multiCode => false,
BoxingMode.one2one => _existingBoxes.any((b) => b.boxNo == boxNo), BoxingMode.singleCode => _currentZongpaiBoxes.any((b) {
BoxingMode.one2many => _existingBoxes.any((b) {
if (b.boxNo != boxNo) return false; if (b.boxNo != boxNo) return false;
return _editingBox == null || b.boxNo != _editingBox!.boxNo; return _editingBox == null || b.boxItemId != _editingBox!.boxItemId;
}), }),
}; };
setState(() => _isDuplicateBoxNo = exists); }
void _checkDuplicateBoxNo() {
setState(() => _isDuplicateBoxNo = _boxNoIsDuplicate());
} }
// === 提交 === // === 提交 ===
int get _packedQuantity {
return _currentZongpaiBoxes.fold<int>(
0,
(sum, item) => sum + item.quantity,
);
}
int get _remainingQuantity {
final total = _erpQuantity ?? 0;
return total - _packedQuantity;
}
bool get _quantityTooHigh {
final qty = int.tryParse(_quantityController.text);
return qty != null && qty > _remainingQuantity;
}
bool get _canSubmit { bool get _canSubmit {
if (_isSubmitting || _phase != _Phase.scanned) return false; if (_isSubmitting || _phase != _Phase.scanned) return false;
if (_zongpaiNo == null) return false; if (_zongpaiNo == null) return false;
final boxNo = int.tryParse(_boxNoController.text); final boxNo = int.tryParse(_boxNoController.text);
final qty = int.tryParse(_quantityController.text); final qty = int.tryParse(_quantityController.text);
if (boxNo == null || qty == null || qty <= 0) return false; if (boxNo == null || boxNo <= 0 || qty == null || qty <= 0) return false;
if (_quantityTooHigh) return false;
if (_isDuplicateBoxNo) return false; if (_isDuplicateBoxNo) return false;
return true; return true;
} }
@@ -386,11 +413,6 @@ class _BoxingPageState extends State<BoxingPage> {
setState(() => _isSubmitting = true); setState(() => _isSubmitting = true);
final modeStr = switch (_mode) {
BoxingMode.one2one => 'one-to-one',
BoxingMode.one2many => 'one-to-many',
BoxingMode.many2one => 'many-to-one',
};
final editing = _editingBox; final editing = _editingBox;
final result = editing == null final result = editing == null
? await _apiService.saveBoxRecord( ? await _apiService.saveBoxRecord(
@@ -398,14 +420,12 @@ class _BoxingPageState extends State<BoxingPage> {
zongpaiNo: _zongpaiNo!, zongpaiNo: _zongpaiNo!,
boxNo: boxNo, boxNo: boxNo,
quantity: quantity, quantity: quantity,
boxMode: modeStr,
) )
: await _apiService.updateBoxRecord( : await _apiService.updateBoxRecord(
baseUrl: baseUrl, baseUrl: baseUrl,
boxItemId: editing.boxItemId, boxItemId: editing.boxItemId,
boxNo: boxNo, boxNo: boxNo,
quantity: quantity, quantity: quantity,
boxMode: modeStr,
); );
if (!mounted) return; if (!mounted) return;
@@ -420,19 +440,11 @@ class _BoxingPageState extends State<BoxingPage> {
} }
} else if (result.isDuplicate) { } else if (result.isDuplicate) {
_feedbackService.trigger(FeedbackEvent.duplicateBoxNo); _feedbackService.trigger(FeedbackEvent.duplicateBoxNo);
if (result.errorCode == 'DUPLICATE_ZONGPAI_BIND') {
_showStatusOverride( _showStatusOverride(
'该总排号已绑定箱号 ${result.boxNo ?? "?"},请勿重复装箱', '该总排号箱号 ${result.boxNo ?? ""} 已存在,请重新输入',
StatusDotColor.red,
const Duration(seconds: 2),
);
} else {
_showStatusOverride(
'排产号 ${result.paichanNo ?? ""} 下箱号 ${result.boxNo ?? ""} 已存在',
StatusDotColor.amber, StatusDotColor.amber,
const Duration(seconds: 2), const Duration(seconds: 2),
); );
}
} else { } else {
final isNetwork = result.errorMessage == '网络异常,请检查网络连接'; final isNetwork = result.errorMessage == '网络异常,请检查网络连接';
_feedbackService.trigger( _feedbackService.trigger(
@@ -451,7 +463,7 @@ class _BoxingPageState extends State<BoxingPage> {
setState(() { setState(() {
_lastBoxNo = boxNo; _lastBoxNo = boxNo;
if (_mode == BoxingMode.one2many && boxItemId != null) { if (_mode == BoxingMode.singleCode && boxItemId != null) {
_currentZongpaiBoxes = List.from(_currentZongpaiBoxes) _currentZongpaiBoxes = List.from(_currentZongpaiBoxes)
..add( ..add(
CurrentZongpaiBoxData( CurrentZongpaiBoxData(
@@ -466,7 +478,9 @@ class _BoxingPageState extends State<BoxingPage> {
}); });
switch (_mode) { switch (_mode) {
case BoxingMode.one2one: case BoxingMode.singleCode:
final remaining = _remainingQuantity;
if (remaining <= 0) {
_showStatusOverride( _showStatusOverride(
'装箱成功', '装箱成功',
StatusDotColor.green, StatusDotColor.green,
@@ -475,21 +489,25 @@ class _BoxingPageState extends State<BoxingPage> {
Future.delayed(const Duration(milliseconds: 1500), () { Future.delayed(const Duration(milliseconds: 1500), () {
if (mounted) setState(() => _resetState()); if (mounted) setState(() => _resetState());
}); });
} else {
case BoxingMode.one2many:
setState(() { setState(() {
_phase = _Phase.scanned; _phase = _Phase.scanned;
_boxNoController.text = (boxNo + 1).toString(); _boxNoController.text = (boxNo + 1).toString();
_quantityController.clear(); _quantityController.text = remaining.toString();
_quantityController.selection = TextSelection(
baseOffset: 0,
extentOffset: _quantityController.text.length,
);
_isDuplicateBoxNo = false; _isDuplicateBoxNo = false;
}); });
_showStatusOverride( _showStatusOverride(
'装箱成功,可继续添加', '请继续完成剩余数量装箱',
StatusDotColor.green, StatusDotColor.green,
const Duration(milliseconds: 1500), const Duration(milliseconds: 1500),
); );
}
case BoxingMode.many2one: case BoxingMode.multiCode:
setState(() { setState(() {
if (boxItemId != null) { if (boxItemId != null) {
_manyToOnePackedItems.add( _manyToOnePackedItems.add(
@@ -538,7 +556,12 @@ class _BoxingPageState extends State<BoxingPage> {
_editingBox = null; _editingBox = null;
_lastBoxNo = _maxBoxNo; _lastBoxNo = _maxBoxNo;
_boxNoController.text = (_maxBoxNo + 1).toString(); _boxNoController.text = (_maxBoxNo + 1).toString();
final remaining = _remainingQuantity;
if (remaining > 0) {
_quantityController.text = remaining.toString();
} else {
_quantityController.clear(); _quantityController.clear();
}
_isDuplicateBoxNo = false; _isDuplicateBoxNo = false;
}); });
_showStatusOverride( _showStatusOverride(
@@ -699,7 +722,6 @@ class _BoxingPageState extends State<BoxingPage> {
boxItemId: item.boxItemId, boxItemId: item.boxItemId,
boxNo: boxNo, boxNo: boxNo,
quantity: quantity, quantity: quantity,
boxMode: 'many-to-one',
); );
if (!mounted) return; if (!mounted) return;
@@ -886,10 +908,15 @@ class _BoxingPageState extends State<BoxingPage> {
_statusText = '箱号 ${_boxNoController.text} 已存在,请重新输入'; _statusText = '箱号 ${_boxNoController.text} 已存在,请重新输入';
return; return;
} }
if (_quantityTooHigh && _phase == _Phase.scanned) {
_statusDot = StatusDotColor.red;
_statusText = '超出可装数量上限';
return;
}
switch (_phase) { switch (_phase) {
case _Phase.waiting: case _Phase.waiting:
_statusDot = StatusDotColor.blue; _statusDot = StatusDotColor.blue;
if (_mode == BoxingMode.many2one && if (_mode == BoxingMode.multiCode &&
_visibleManyToOnePackedItems.isNotEmpty) { _visibleManyToOnePackedItems.isNotEmpty) {
_statusText = '请继续扫码或完成本箱'; _statusText = '请继续扫码或完成本箱';
} else { } else {
@@ -897,18 +924,21 @@ class _BoxingPageState extends State<BoxingPage> {
} }
case _Phase.scanned: case _Phase.scanned:
_statusDot = StatusDotColor.blue; _statusDot = StatusDotColor.blue;
_statusText = _mode == BoxingMode.many2one if (_remainingQuantity <= 0) {
_statusDot = StatusDotColor.red;
_statusText = '该总排号已全部装箱完毕';
return;
}
_statusText = _mode == BoxingMode.multiCode
? '数量已填入,请确认或修改' ? '数量已填入,请确认或修改'
: '请确认信息并提交'; : '数量已填入,请确认或修改';
case _Phase.submitted: case _Phase.submitted:
_statusDot = StatusDotColor.green; _statusDot = StatusDotColor.green;
switch (_mode) { switch (_mode) {
case BoxingMode.one2many: case BoxingMode.singleCode:
_statusText = '装箱成功,可继续添加或返回'; _statusText = '装箱成功,可继续添加或返回';
case BoxingMode.many2one: case BoxingMode.multiCode:
_statusText = '请扫描下一个总排号'; _statusText = '请扫描下一个总排号';
case BoxingMode.one2one:
_statusText = '';
} }
} }
} }
@@ -920,7 +950,7 @@ class _BoxingPageState extends State<BoxingPage> {
final colorScheme = Theme.of(context).colorScheme; final colorScheme = Theme.of(context).colorScheme;
final isWaiting = _phase == _Phase.waiting && _zongpaiNo == null; final isWaiting = _phase == _Phase.waiting && _zongpaiNo == null;
final showActionButtons = final showActionButtons =
_phase == _Phase.scanned && _mode == BoxingMode.one2many; _phase == _Phase.scanned && _mode == BoxingMode.singleCode;
_updateBaseStatus(); _updateBaseStatus();
final effectiveDot = _statusOverrideDot ?? _statusDot; final effectiveDot = _statusOverrideDot ?? _statusDot;
@@ -937,7 +967,7 @@ class _BoxingPageState extends State<BoxingPage> {
icon: const Icon(Icons.swap_horiz, size: 18), icon: const Icon(Icons.swap_horiz, size: 18),
label: Text(_modeLabel, style: const TextStyle(fontSize: 13)), label: Text(_modeLabel, style: const TextStyle(fontSize: 13)),
style: TextButton.styleFrom( style: TextButton.styleFrom(
foregroundColor: _mode == BoxingMode.many2one foregroundColor: _mode == BoxingMode.multiCode
? Colors.orange ? Colors.orange
: colorScheme.primary, : colorScheme.primary,
), ),
@@ -960,7 +990,7 @@ class _BoxingPageState extends State<BoxingPage> {
), ),
Expanded( Expanded(
child: _mode == BoxingMode.many2one child: _mode == BoxingMode.multiCode
? _buildManyToOneBody(colorScheme) ? _buildManyToOneBody(colorScheme)
: SingleChildScrollView( : SingleChildScrollView(
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
@@ -971,7 +1001,7 @@ class _BoxingPageState extends State<BoxingPage> {
const SizedBox(height: 12), const SizedBox(height: 12),
_buildInfoArea(isWaiting, colorScheme), _buildInfoArea(isWaiting, colorScheme),
const Divider(height: 24), const Divider(height: 24),
if (_mode == BoxingMode.one2many && if (_mode == BoxingMode.singleCode &&
!isWaiting && !isWaiting &&
_currentZongpaiBoxes.isNotEmpty) ...[ _currentZongpaiBoxes.isNotEmpty) ...[
_buildAssignedBoxes(), _buildAssignedBoxes(),
@@ -983,7 +1013,7 @@ class _BoxingPageState extends State<BoxingPage> {
), ),
), ),
// 操作按钮(一码多箱 / 多码箱) // 操作按钮(单码装箱 / 多码箱)
if (showActionButtons) if (showActionButtons)
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
@@ -998,11 +1028,13 @@ class _BoxingPageState extends State<BoxingPage> {
child: const Text('返回'), child: const Text('返回'),
), ),
), ),
if (_mode == BoxingMode.one2many && _editingBox == null) ...[ if (_mode == BoxingMode.singleCode &&
_editingBox == null) ...[
const SizedBox(width: 12), const SizedBox(width: 12),
const Expanded(child: SizedBox.shrink()), const Expanded(child: SizedBox.shrink()),
], ],
if (_mode == BoxingMode.one2many && _editingBox != null) ...[ if (_mode == BoxingMode.singleCode &&
_editingBox != null) ...[
const SizedBox(width: 12), const SizedBox(width: 12),
Expanded( Expanded(
child: ElevatedButton( child: ElevatedButton(
@@ -1018,7 +1050,7 @@ class _BoxingPageState extends State<BoxingPage> {
), ),
), ),
if (_mode == BoxingMode.many2one) if (_mode == BoxingMode.multiCode)
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
child: OutlinedButton( child: OutlinedButton(
@@ -1399,9 +1431,16 @@ class _BoxingPageState extends State<BoxingPage> {
keyboardType: TextInputType.none, keyboardType: TextInputType.none,
inputFormatters: [FilteringTextInputFormatter.digitsOnly], inputFormatters: [FilteringTextInputFormatter.digitsOnly],
onChanged: (_) => setState(() {}), onChanged: (_) => setState(() {}),
decoration: const InputDecoration( decoration: InputDecoration(
contentPadding: EdgeInsets.symmetric(horizontal: 10), contentPadding: const EdgeInsets.symmetric(horizontal: 10),
border: OutlineInputBorder(), border: const OutlineInputBorder(),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: _quantityTooHigh
? Colors.red
: Colors.grey.shade400,
),
),
), ),
), ),
), ),
@@ -1681,9 +1720,16 @@ class _BoxingPageState extends State<BoxingPage> {
keyboardType: TextInputType.none, keyboardType: TextInputType.none,
inputFormatters: [FilteringTextInputFormatter.digitsOnly], inputFormatters: [FilteringTextInputFormatter.digitsOnly],
onChanged: (_) => setState(() {}), onChanged: (_) => setState(() {}),
decoration: const InputDecoration( decoration: InputDecoration(
contentPadding: EdgeInsets.symmetric(horizontal: 10), contentPadding: const EdgeInsets.symmetric(horizontal: 10),
border: OutlineInputBorder(), border: const OutlineInputBorder(),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: _quantityTooHigh
? Colors.red
: Colors.grey.shade400,
),
),
), ),
style: const TextStyle(fontSize: 16), style: const TextStyle(fontSize: 16),
), ),

View File

@@ -325,7 +325,6 @@ class ApiService {
required String zongpaiNo, required String zongpaiNo,
required int boxNo, required int boxNo,
required int quantity, required int quantity,
required String boxMode,
}) async { }) async {
final uri = Uri.parse('$baseUrl/CargoTrace/box'); final uri = Uri.parse('$baseUrl/CargoTrace/box');
try { try {
@@ -337,7 +336,6 @@ class ApiService {
'zongpai_no': zongpaiNo, 'zongpai_no': zongpaiNo,
'box_no': boxNo, 'box_no': boxNo,
'quantity': quantity, 'quantity': quantity,
'box_mode': boxMode,
}), }),
) )
.timeout(timeout); .timeout(timeout);
@@ -369,7 +367,6 @@ class ApiService {
required int boxItemId, required int boxItemId,
required int boxNo, required int boxNo,
required int quantity, required int quantity,
String boxMode = 'one-to-many',
}) async { }) async {
final uri = Uri.parse('$baseUrl/CargoTrace/box/$boxItemId'); final uri = Uri.parse('$baseUrl/CargoTrace/box/$boxItemId');
try { try {
@@ -377,11 +374,7 @@ class ApiService {
.patch( .patch(
uri, uri,
headers: {'Content-Type': 'application/json'}, headers: {'Content-Type': 'application/json'},
body: jsonEncode({ body: jsonEncode({'box_no': boxNo, 'quantity': quantity}),
'box_no': boxNo,
'quantity': quantity,
'box_mode': boxMode,
}),
) )
.timeout(timeout); .timeout(timeout);

View File

@@ -190,8 +190,11 @@ void main() {
expect(result.suggestedBoxNo, 4); expect(result.suggestedBoxNo, 4);
}); });
test('saveBoxRecord parses zongpai number from success response', () async { test('saveBoxRecord sends simplified request body', () async {
late Map<String, dynamic> body;
final mockClient = _MockClient((request) async { final mockClient = _MockClient((request) async {
final req = request as http.Request;
body = jsonDecode(req.body) as Map<String, dynamic>;
return http.Response( return http.Response(
jsonEncode({ jsonEncode({
'box_item_id': 102, 'box_item_id': 102,
@@ -212,15 +215,16 @@ void main() {
zongpaiNo: '26BW0012', zongpaiNo: '26BW0012',
boxNo: 4, boxNo: 4,
quantity: 34, quantity: 34,
boxMode: 'many-to-one',
); );
expect(result.success, isTrue); expect(result.success, isTrue);
expect(result.boxItemId, 102); expect(result.boxItemId, 102);
expect(result.zongpaiNo, '26BW0012'); expect(result.zongpaiNo, '26BW0012');
expect(body, {'zongpai_no': '26BW0012', 'box_no': 4, 'quantity': 34});
expect(body.containsKey('box_mode'), isFalse);
}); });
test('updateBoxRecord sends many-to-one box mode', () async { test('updateBoxRecord sends simplified request body', () async {
late Map<String, dynamic> body; late Map<String, dynamic> body;
final mockClient = _MockClient((request) async { final mockClient = _MockClient((request) async {
final req = request as http.Request; final req = request as http.Request;
@@ -245,11 +249,11 @@ void main() {
boxItemId: 102, boxItemId: 102,
boxNo: 4, boxNo: 4,
quantity: 20, quantity: 20,
boxMode: 'many-to-one',
); );
expect(result.success, isTrue); expect(result.success, isTrue);
expect(body['box_mode'], 'many-to-one'); expect(body, {'box_no': 4, 'quantity': 20});
expect(body.containsKey('box_mode'), isFalse);
}); });
test('deleteBoxRecord handles success and not found', () async { test('deleteBoxRecord handles success and not found', () async {