diff --git a/lib/pages/boxing/parts/boxing_box_mutation_part.dart b/lib/pages/boxing/parts/boxing_box_mutation_part.dart new file mode 100644 index 0000000..e0107a0 --- /dev/null +++ b/lib/pages/boxing/parts/boxing_box_mutation_part.dart @@ -0,0 +1,59 @@ +part of '../../boxing_page.dart'; + +extension _BoxingBoxMutationPart on _BoxingPageState { + void _replaceExistingBoxItem( + CurrentZongpaiBoxData editing, + int boxNo, + int quantity, + ) { + final result = box_mutations.replaceExistingBoxItem( + existingBoxes: _existingBoxes, + editing: editing, + boxNo: boxNo, + quantity: quantity, + zongpaiNo: _zongpaiNo!, + workOrderNo: _workOrderNo, + totalQuantity: _erpQuantity, + ); + _existingBoxes = result.boxes; + _maxBoxNo = result.maxBoxNo; + } + + void _addExistingBoxItem(int boxNo, int quantity, int? boxItemId) { + final result = box_mutations.addExistingBoxItem( + existingBoxes: _existingBoxes, + boxNo: boxNo, + quantity: quantity, + boxItemId: boxItemId, + zongpaiNo: _zongpaiNo!, + workOrderNo: _workOrderNo, + totalQuantity: _erpQuantity, + ); + _existingBoxes = result.boxes; + _maxBoxNo = result.maxBoxNo; + } + + void _replaceExistingPackedItem( + ManyToOnePackedItem item, + int boxNo, + int quantity, + ) { + final result = box_mutations.replaceExistingPackedItem( + existingBoxes: _existingBoxes, + item: item, + boxNo: boxNo, + quantity: quantity, + ); + _existingBoxes = result.boxes; + _maxBoxNo = result.maxBoxNo; + } + + void _removeExistingBoxItem(int boxItemId) { + final result = box_mutations.removeExistingBoxItem( + existingBoxes: _existingBoxes, + boxItemId: boxItemId, + ); + _existingBoxes = result.boxes; + _maxBoxNo = result.maxBoxNo; + } +} diff --git a/lib/pages/boxing/parts/boxing_multi_ops_part.dart b/lib/pages/boxing/parts/boxing_multi_ops_part.dart new file mode 100644 index 0000000..58ffe32 --- /dev/null +++ b/lib/pages/boxing/parts/boxing_multi_ops_part.dart @@ -0,0 +1,112 @@ +// ignore_for_file: invalid_use_of_protected_member, unnecessary_this + +part of '../../boxing_page.dart'; + +extension _BoxingMultiOpsPart on _BoxingPageState { + void _finishManyToOneBox() { + setState(() { + _manyToOnePackedItems.clear(); + _editingPackedItemId = null; + _editingPackedQuantity = ''; + _deletingPackedItemId = null; + _zongpaiNo = null; + _phase = BoxingPhase.waiting; + _quantityController.clear(); + _boxNoController.text = (_maxBoxNo + 1).toString(); + _statusOverrideText = null; + _statusOverrideDot = null; + }); + } + + void _startEditPackedItem(ManyToOnePackedItem item) { + setState(() { + _editingPackedItemId = item.boxItemId; + _editingPackedQuantity = item.quantity.toString(); + _deletingPackedItemId = null; + }); + } + + void _cancelEditPackedItem() { + setState(() { + _editingPackedItemId = null; + _editingPackedQuantity = ''; + }); + } + + Future _savePackedItem(ManyToOnePackedItem item) async { + final quantity = int.tryParse(_editingPackedQuantity); + final boxNo = int.tryParse(_boxNoController.text.trim()); + if (quantity == null || quantity <= 0 || boxNo == null || boxNo <= 0) { + this._showStatusOverride( + '请输入有效箱号和数量', + StatusDotColor.red, + const Duration(seconds: 2), + ); + return; + } + + setState(() => _isSubmitting = true); + final action = await _apiActions.updateBoxRecord( + boxItemId: item.boxItemId, + boxNo: boxNo, + quantity: quantity, + ); + if (!mounted) return; + + setState(() => _isSubmitting = false); + if (action.success) { + setState(() { + final index = _manyToOnePackedItems.indexWhere( + (packed) => packed.boxItemId == item.boxItemId, + ); + if (index >= 0) { + _manyToOnePackedItems[index] = _manyToOnePackedItems[index].copyWith( + boxNo: boxNo, + quantity: quantity, + ); + } + this._replaceExistingPackedItem(item, boxNo, quantity); + _editingPackedItemId = null; + _editingPackedQuantity = ''; + }); + this._showStatusOverride( + '修改成功', + StatusDotColor.green, + const Duration(milliseconds: 1500), + ); + } else { + this._showActionError(action, fallback: '修改失败'); + } + } + + Future _deletePackedItem(ManyToOnePackedItem item) async { + setState(() => _isSubmitting = true); + final action = await _apiActions.deleteBoxRecord(boxItemId: item.boxItemId); + if (!mounted) return; + + setState(() { + _isSubmitting = false; + if (action.success) { + _manyToOnePackedItems.removeWhere( + (packed) => packed.boxItemId == item.boxItemId, + ); + this._removeExistingBoxItem(item.boxItemId); + if (_editingPackedItemId == item.boxItemId) { + _editingPackedItemId = null; + _editingPackedQuantity = ''; + } + _deletingPackedItemId = null; + } + }); + + if (action.success) { + this._showStatusOverride( + '删除成功', + StatusDotColor.green, + const Duration(milliseconds: 1500), + ); + } else { + this._showActionError(action, fallback: '删除失败'); + } + } +} diff --git a/lib/pages/boxing/parts/boxing_scan_part.dart b/lib/pages/boxing/parts/boxing_scan_part.dart new file mode 100644 index 0000000..602e22a --- /dev/null +++ b/lib/pages/boxing/parts/boxing_scan_part.dart @@ -0,0 +1,271 @@ +// ignore_for_file: invalid_use_of_protected_member, unnecessary_this + +part of '../../boxing_page.dart'; + +extension _BoxingScanPart on _BoxingPageState { + void _onScan(ScanResult result) { + if (_isAutoProcessing) { + return; + } + + if (_crossPaichaPending) { + _feedbackService.trigger(FeedbackEvent.alreadyCompleted); + return; + } + + final parsed = CodeParser.parse(result.barcode); + + if (parsed.type != CodeType.zongpaiNo) { + _feedbackService.trigger(FeedbackEvent.scanInvalid); + this._showStatusOverride( + '无效码,请重新扫描', + StatusDotColor.red, + const Duration(seconds: 2), + ); + return; + } + + final zongpai = parsed.value; + + // 三种模式都允许后扫入的总排号覆盖当前扫码区;未确认数据不会入库。 + this._queryBoxInfo(zongpai); + } + + Future _queryBoxInfo(String zongpai) async { + final action = await _apiActions.fetchBoxInfo(zongpai); + + if (!mounted) return false; + + if (!action.success) { + _feedbackService.trigger(switch (action.errorKind) { + BoxingActionErrorKind.network => FeedbackEvent.networkError, + BoxingActionErrorKind.missingApiUrl => FeedbackEvent.submitFailure, + _ => FeedbackEvent.scanInvalid, + }); + this._showStatusOverride( + action.errorMessage ?? '查询失败', + this._dotForActionError(action.errorKind), + const Duration(seconds: 2), + ); + return false; + } + + final result = action.data!; + // 判断该总牌号是否已全部装箱完毕 + final packedQuantity = result.currentZongpaiBoxes.fold( + 0, + (sum, item) => sum + item.quantity, + ); + final totalQuantity = result.quantity ?? 0; + final alreadyCompleted = + totalQuantity > 0 && packedQuantity >= totalQuantity; + + _feedbackService.trigger( + alreadyCompleted + ? FeedbackEvent.alreadyCompleted + : FeedbackEvent.scanValid, + ); + + setState(() { + _zongpaiNo = zongpai; + _paichanNo = result.paichanNo; + _workOrderNo = result.workOrderNo; + _erpQuantity = result.quantity; + _currentZongpaiBoxes = result.currentZongpaiBoxes; + _existingBoxes = result.existingBoxes; + _maxBoxNo = result.maxBoxNo; + _phase = BoxingPhase.scanned; + _isDuplicateBoxNo = false; + _editingBox = null; + _editingAssignedItemId = null; + _editingAssignedQuantity = ''; + _deletingAssignedItemId = null; + _completedJumpBoxNo = null; + _statusOverrideText = null; + if (_mode == BoxingMode.singleCode) { + _paichanSwitchNotice = null; + } + + // 根据模式自动填充 + this._applyAutoFill(); + }); + return true; + } + + Future _processAutoScanCodes(List codes) async { + if (!mounted) return; + setState(() { + _isAutoProcessing = true; + _autoWarnings.clear(); + _statusOverrideText = '正在同步转运数据...'; + _statusOverrideDot = StatusDotColor.orange; + }); + + if (_mode == BoxingMode.singleCode) { + final code = codes.first; + final ok = await this._queryBoxInfo(code); + if (!ok && mounted) { + setState(() => _autoWarnings.add('总排号 $code 未找到排产号信息,已忽略')); + } + } else { + for (final code in codes) { + if (!mounted) return; + final queried = await this._queryBoxInfo(code); + if (!queried) { + if (mounted) { + setState(() => _autoWarnings.add('总排号 $code 未找到排产号信息,已忽略')); + } + continue; + } + if (!_canSubmit) { + if (mounted) { + setState(() => _autoWarnings.add('总排号 $code 暂不能装箱,已忽略')); + } + continue; + } + final saved = await this._submit(); + if (!saved && mounted) { + setState(() => _autoWarnings.add('总排号 $code 自动装箱失败,请手动处理')); + } + } + } + + if (!mounted) return; + setState(() { + _isAutoProcessing = false; + _statusOverrideText = null; + _statusOverrideDot = null; + }); + _focusQuantityInput(); + _requestFocus(); + } + + void _applyAutoFill() { + switch (_mode) { + case BoxingMode.singleCode: + final remaining = _remainingQuantity; + if (remaining <= 0) { + _boxNoController.clear(); + _quantityController.clear(); + _statusOverrideText = '该总排号已全部装箱完毕'; + _statusOverrideDot = StatusDotColor.red; + return; + } + _boxNoController.text = (_maxBoxNo + 1).toString(); + _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: + final decision = decideMultiCodePaichanContext( + lastPaichanNo: _lastScannedPaichanNo, + currentPaichanNo: _paichanNo, + currentBoxNoText: _boxNoController.text, + maxBoxNo: _maxBoxNo, + ); + if (decision.isSwitched) { + _crossPaichaPending = true; + _feedbackService.trigger(FeedbackEvent.alreadyCompleted); + this._showCrossPaichaDialog(); + return; + } else { + _paichanSwitchNotice = null; + } + final boxNoToApply = decision.boxNoToApply; + if (boxNoToApply != null) { + _boxNoController.text = boxNoToApply.toString(); + } + _lastScannedPaichanNo = _paichanNo; + final remaining = _remainingQuantity; + if (remaining <= 0) { + _quantityController.clear(); + if (_manyToOnePackedItems.isEmpty && + _currentZongpaiBoxes.isNotEmpty) { + _completedJumpBoxNo = _currentZongpaiBoxes.first.boxNo; + } + _statusOverrideText = '该总排号已全部装箱完毕'; + _statusOverrideDot = StatusDotColor.red; + return; + } + _quantityController.text = remaining.toString(); + _quantityController.selection = TextSelection( + baseOffset: 0, + extentOffset: _quantityController.text.length, + ); + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted && _mode == BoxingMode.multiCode) { + _quantityFocusNode.requestFocus(); + } + }); + } + _isDuplicateBoxNo = _boxNoIsDuplicate(); + } + + Future _showCrossPaichaDialog() async { + final currentPaicha = _lastScannedPaichanNo ?? '--'; + final newPaicha = _paichanNo ?? '--'; + final confirmed = await showCrossPaichanDialog( + context: context, + currentPaicha: currentPaicha, + newPaicha: newPaicha, + ); + if (!mounted) return; + confirmed ? this._confirmPaichanSwitch() : this._cancelPaichanSwitch(); + } + + void _confirmPaichanSwitch() { + setState(() { + _crossPaichaPending = false; + _manyToOnePackedItems.clear(); + _editingPackedItemId = null; + _editingPackedQuantity = ''; + _deletingPackedItemId = null; + _paichanSwitchNotice = '排产号已切换:${_paichanNo ?? "--"}'; + _boxNoController.text = (_maxBoxNo + 1).toString(); + _lastScannedPaichanNo = _paichanNo; + }); + _feedbackService.trigger(FeedbackEvent.paichanSwitch); + + // Continue with remaining quantity flow + final remaining = _remainingQuantity; + if (remaining <= 0) { + _quantityController.clear(); + if (_manyToOnePackedItems.isEmpty && _currentZongpaiBoxes.isNotEmpty) { + _completedJumpBoxNo = _currentZongpaiBoxes.first.boxNo; + } + setState(() { + _statusOverrideText = '该总排号已全部装箱完毕'; + _statusOverrideDot = StatusDotColor.red; + }); + return; + } + setState(() { + _quantityController.text = remaining.toString(); + _quantityController.selection = TextSelection( + baseOffset: 0, + extentOffset: _quantityController.text.length, + ); + _isDuplicateBoxNo = _boxNoIsDuplicate(); + }); + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted && _mode == BoxingMode.multiCode) { + _quantityFocusNode.requestFocus(); + } + }); + } + + void _cancelPaichanSwitch() { + setState(() { + _crossPaichaPending = false; + _zongpaiNo = null; + _paichanNo = _lastScannedPaichanNo; + _phase = BoxingPhase.waiting; + }); + } +} diff --git a/lib/pages/boxing/parts/boxing_single_ops_part.dart b/lib/pages/boxing/parts/boxing_single_ops_part.dart new file mode 100644 index 0000000..3397d01 --- /dev/null +++ b/lib/pages/boxing/parts/boxing_single_ops_part.dart @@ -0,0 +1,133 @@ +// ignore_for_file: invalid_use_of_protected_member, unnecessary_this + +part of '../../boxing_page.dart'; + +extension _BoxingSingleOpsPart on _BoxingPageState { + void _startEditAssigned(CurrentZongpaiBoxData item) { + setState(() { + _editingAssignedItemId = item.boxItemId; + _editingAssignedQuantity = item.quantity.toString(); + _deletingAssignedItemId = null; + _statusOverrideText = null; + _statusOverrideDot = null; + }); + } + + void _cancelEditAssigned() { + setState(() { + _editingAssignedItemId = null; + _editingAssignedQuantity = ''; + }); + } + + int _maxAssignedQuantity(CurrentZongpaiBoxData item) { + return boxing_calculations.maxAssignedQuantity( + totalQuantity: _erpQuantity, + packedQuantity: _packedQuantity, + itemQuantity: item.quantity, + ); + } + + void _refreshSingleCodeNewInput({bool focusQuantity = true}) { + _boxNoController.text = (_maxBoxNo + 1).toString(); + final remaining = _remainingQuantity; + if (remaining > 0) { + _quantityController.text = remaining.toString(); + _quantityController.selection = TextSelection( + baseOffset: 0, + extentOffset: _quantityController.text.length, + ); + if (focusQuantity) { + _focusQuantityInput(); + } + } else { + _quantityController.clear(); + } + _isDuplicateBoxNo = false; + } + + Future _saveAssignedItem(CurrentZongpaiBoxData item) async { + final quantity = int.tryParse(_editingAssignedQuantity); + if (quantity == null || quantity <= 0) { + this._showStatusOverride( + '请输入有效数量', + StatusDotColor.red, + const Duration(seconds: 2), + ); + return; + } + if (quantity > this._maxAssignedQuantity(item)) { + this._showStatusOverride( + '超出可装数量上限', + StatusDotColor.red, + const Duration(seconds: 2), + ); + return; + } + + setState(() => _isSubmitting = true); + final action = await _apiActions.updateBoxRecord( + boxItemId: item.boxItemId, + boxNo: item.boxNo, + quantity: quantity, + ); + if (!mounted) return; + + setState(() => _isSubmitting = false); + if (action.success) { + setState(() { + _currentZongpaiBoxes = _currentZongpaiBoxes.map((box) { + if (box.boxItemId != item.boxItemId) return box; + return CurrentZongpaiBoxData( + boxItemId: box.boxItemId, + boxNo: box.boxNo, + quantity: quantity, + ); + }).toList(); + this._replaceExistingBoxItem(item, item.boxNo, quantity); + _editingAssignedItemId = null; + _editingAssignedQuantity = ''; + this._refreshSingleCodeNewInput(); + }); + this._showStatusOverride( + '修改成功', + StatusDotColor.green, + const Duration(milliseconds: 1500), + ); + } else { + this._showActionError(action, fallback: '修改失败'); + } + } + + Future _deleteAssignedItem(CurrentZongpaiBoxData item) async { + setState(() => _isSubmitting = true); + final action = await _apiActions.deleteBoxRecord(boxItemId: item.boxItemId); + if (!mounted) return; + + setState(() { + _isSubmitting = false; + if (action.success) { + _currentZongpaiBoxes = _currentZongpaiBoxes + .where((box) => box.boxItemId != item.boxItemId) + .toList(); + this._removeExistingBoxItem(item.boxItemId); + if (_editingAssignedItemId == item.boxItemId) { + _editingAssignedItemId = null; + _editingAssignedQuantity = ''; + } + _deletingAssignedItemId = null; + this._refreshSingleCodeNewInput(); + } + }); + + if (action.success) { + this._showStatusOverride( + '删除成功', + StatusDotColor.green, + const Duration(milliseconds: 1500), + ); + } else { + this._showActionError(action, fallback: '删除失败'); + } + } +} diff --git a/lib/pages/boxing/parts/boxing_status_part.dart b/lib/pages/boxing/parts/boxing_status_part.dart new file mode 100644 index 0000000..adbb051 --- /dev/null +++ b/lib/pages/boxing/parts/boxing_status_part.dart @@ -0,0 +1,86 @@ +// ignore_for_file: invalid_use_of_protected_member, unnecessary_this + +part of '../../boxing_page.dart'; + +extension _BoxingStatusPart on _BoxingPageState { + StatusDotColor _dotForActionError(BoxingActionErrorKind? kind) { + return switch (kind) { + BoxingActionErrorKind.network => StatusDotColor.yellow, + BoxingActionErrorKind.duplicate => StatusDotColor.amber, + _ => StatusDotColor.red, + }; + } + + void _showActionError( + BoxingActionResult action, { + required String fallback, + }) { + _feedbackService.trigger(switch (action.errorKind) { + BoxingActionErrorKind.network => FeedbackEvent.networkError, + BoxingActionErrorKind.duplicate => FeedbackEvent.duplicateBoxNo, + _ => FeedbackEvent.submitFailure, + }); + this._showStatusOverride( + action.errorMessage ?? fallback, + this._dotForActionError(action.errorKind), + const Duration(seconds: 2), + ); + } + + void _showStatusOverride(String text, StatusDotColor dot, Duration duration) { + setState(() { + _statusOverrideText = text; + _statusOverrideDot = dot; + }); + Future.delayed(duration, () { + if (mounted) { + setState(() { + _statusOverrideText = null; + _statusOverrideDot = null; + }); + } + }); + } + + void _jumpToCompletedBox() { + final boxNo = _completedJumpBoxNo; + if (boxNo == null) return; + setState(() { + _boxNoController.text = boxNo.toString(); + _completedJumpBoxNo = null; + _statusOverrideText = null; + _statusOverrideDot = null; + }); + } + + void _openDetail() { + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => BoxingDetailPage( + paichanNo: _paichanNo ?? '', + existingBoxes: _existingBoxes, + ), + ), + ); + } + + void _updateBaseStatus() { + final status = boxingStatusFor( + isAutoProcessing: _isAutoProcessing, + isSubmitting: _isSubmitting, + isDuplicateBoxNo: _isDuplicateBoxNo, + quantityTooHigh: _quantityTooHigh, + isEditingAssigned: _editingAssignedItemId != null, + isDeletingAssigned: _deletingAssignedItemId != null, + hasPaichanSwitchNotice: _paichanSwitchNotice != null, + phase: _phase, + isMultiCode: _mode == BoxingMode.multiCode, + hasVisibleManyToOneItems: _visibleManyToOnePackedItems.isNotEmpty, + remainingQuantity: _remainingQuantity, + boxNoText: _boxNoController.text, + ); + _statusDot = status.dot; + _statusText = status.text; + } +} diff --git a/lib/pages/boxing/parts/boxing_submit_part.dart b/lib/pages/boxing/parts/boxing_submit_part.dart new file mode 100644 index 0000000..0e541af --- /dev/null +++ b/lib/pages/boxing/parts/boxing_submit_part.dart @@ -0,0 +1,161 @@ +// ignore_for_file: invalid_use_of_protected_member, unnecessary_this + +part of '../../boxing_page.dart'; + +extension _BoxingSubmitPart on _BoxingPageState { + Future _submit() async { + if (!_canSubmit) return false; + + final boxNo = int.parse(_boxNoController.text); + final quantity = int.parse(_quantityController.text); + + setState(() => _isSubmitting = true); + + final editing = _editingBox; + final action = editing == null + ? await _apiActions.saveBoxRecord( + zongpaiNo: _zongpaiNo!, + boxNo: boxNo, + quantity: quantity, + ) + : await _apiActions.updateBoxRecord( + boxItemId: editing.boxItemId, + boxNo: boxNo, + quantity: quantity, + ); + + if (!mounted) return false; + + setState(() => _isSubmitting = false); + + if (action.success) { + if (editing == null) { + this._onSubmitSuccess(boxNo, quantity, action.data!.boxItemId); + } else { + this._onUpdateSuccess(editing, boxNo, quantity); + } + return true; + } else if (action.errorKind == BoxingActionErrorKind.duplicate) { + _feedbackService.trigger(FeedbackEvent.duplicateBoxNo); + this._showStatusOverride( + '该总排号在箱号 ${action.boxNo ?? ""} 已存在,请重新输入', + StatusDotColor.amber, + const Duration(seconds: 2), + ); + return false; + } else { + this._showActionError(action, fallback: '提交失败'); + return false; + } + } + + void _onSubmitSuccess(int boxNo, int quantity, int? boxItemId) { + _feedbackService.trigger(FeedbackEvent.submitSuccess); + + setState(() { + if (_mode == BoxingMode.singleCode && boxItemId != null) { + _currentZongpaiBoxes = List.from(_currentZongpaiBoxes) + ..add( + CurrentZongpaiBoxData( + boxItemId: boxItemId, + boxNo: boxNo, + quantity: quantity, + ), + ); + } + this._addExistingBoxItem(boxNo, quantity, boxItemId); + _maxBoxNo = _maxBoxNo > boxNo ? _maxBoxNo : boxNo; + }); + + switch (_mode) { + case BoxingMode.singleCode: + final remaining = _remainingQuantity; + if (remaining <= 0) { + this._showStatusOverride( + '装箱成功', + StatusDotColor.green, + const Duration(milliseconds: 1500), + ); + } else { + setState(() { + _phase = BoxingPhase.scanned; + _boxNoController.text = (_maxBoxNo + 1).toString(); + _quantityController.text = remaining.toString(); + _quantityController.selection = TextSelection( + baseOffset: 0, + extentOffset: _quantityController.text.length, + ); + _isDuplicateBoxNo = false; + }); + _focusQuantityInput(); + this._showStatusOverride( + '请继续完成剩余数量装箱', + StatusDotColor.green, + const Duration(milliseconds: 1500), + ); + } + + case BoxingMode.multiCode: + _lastScannedPaichanNo = _paichanNo; + setState(() { + if (boxItemId != null) { + _manyToOnePackedItems.add( + ManyToOnePackedItem( + boxItemId: boxItemId, + zongpaiNo: _zongpaiNo!, + paichanNo: _paichanNo, + workOrderNo: _workOrderNo, + boxNo: boxNo, + quantity: quantity, + totalQuantity: _erpQuantity, + ), + ); + } + _phase = BoxingPhase.waiting; + _zongpaiNo = null; + _editingPackedItemId = null; + _editingPackedQuantity = ''; + _deletingPackedItemId = null; + }); + this._showStatusOverride( + '装箱成功,请继续扫码', + StatusDotColor.green, + const Duration(milliseconds: 1500), + ); + } + } + + void _onUpdateSuccess( + CurrentZongpaiBoxData editing, + int boxNo, + int quantity, + ) { + _feedbackService.trigger(FeedbackEvent.submitSuccess); + + setState(() { + _currentZongpaiBoxes = _currentZongpaiBoxes.map((item) { + if (item.boxItemId != editing.boxItemId) return item; + return CurrentZongpaiBoxData( + boxItemId: item.boxItemId, + boxNo: boxNo, + quantity: quantity, + ); + }).toList(); + this._replaceExistingBoxItem(editing, boxNo, quantity); + _editingBox = null; + _boxNoController.text = (_maxBoxNo + 1).toString(); + final remaining = _remainingQuantity; + if (remaining > 0) { + _quantityController.text = remaining.toString(); + } else { + _quantityController.clear(); + } + _isDuplicateBoxNo = false; + }); + this._showStatusOverride( + '修改成功', + StatusDotColor.green, + const Duration(milliseconds: 1500), + ); + } +} diff --git a/lib/pages/boxing_page.dart b/lib/pages/boxing_page.dart index 73f1f8c..173260b 100644 --- a/lib/pages/boxing_page.dart +++ b/lib/pages/boxing_page.dart @@ -1,3 +1,5 @@ +// ignore_for_file: unnecessary_this + import 'dart:async'; import 'package:flutter/material.dart'; @@ -25,6 +27,13 @@ import 'package:pad_scanner/widgets/status_bar.dart'; export 'package:pad_scanner/pages/boxing/boxing_models.dart' show BoxingMode, BoxingPageArguments; +part 'boxing/parts/boxing_box_mutation_part.dart'; +part 'boxing/parts/boxing_multi_ops_part.dart'; +part 'boxing/parts/boxing_scan_part.dart'; +part 'boxing/parts/boxing_single_ops_part.dart'; +part 'boxing/parts/boxing_status_part.dart'; +part 'boxing/parts/boxing_submit_part.dart'; + // === 主页面 === class BoxingPage extends StatefulWidget { @@ -108,10 +117,12 @@ class _BoxingPageState extends State with WidgetsBindingObserver { _mode = arguments.initialMode; } _boxNoController.addListener(_onBoxNoTextChanged); - _scanSubscription = _scannerService.scanResults.listen(_onScan); + _scanSubscription = _scannerService.scanResults.listen( + (result) => this._onScan(result), + ); if (arguments != null && arguments.autoScanCodes.isNotEmpty) { WidgetsBinding.instance.addPostFrameCallback((_) { - _processAutoScanCodes(arguments.autoScanCodes); + this._processAutoScanCodes(arguments.autoScanCodes); }); } WidgetsBinding.instance.addPostFrameCallback((_) => _requestFocus()); @@ -152,11 +163,11 @@ class _BoxingPageState extends State with WidgetsBindingObserver { if (_isFinishBoxKey(event)) { final hasItems = _visibleManyToOnePackedItems.isNotEmpty; if (hasItems && _mode == BoxingMode.multiCode) { - _finishManyToOneBox(); + this._finishManyToOneBox(); } } if (_completedJumpBoxNo != null && _isJumpToBoxKey(event)) { - _jumpToCompletedBox(); + this._jumpToCompletedBox(); } } @@ -235,276 +246,6 @@ class _BoxingPageState extends State with WidgetsBindingObserver { _statusOverrideDot = null; } - // === 扫码处理 === - - void _onScan(ScanResult result) { - if (_isAutoProcessing) { - return; - } - - if (_crossPaichaPending) { - _feedbackService.trigger(FeedbackEvent.alreadyCompleted); - return; - } - - final parsed = CodeParser.parse(result.barcode); - - if (parsed.type != CodeType.zongpaiNo) { - _feedbackService.trigger(FeedbackEvent.scanInvalid); - _showStatusOverride( - '无效码,请重新扫描', - StatusDotColor.red, - const Duration(seconds: 2), - ); - return; - } - - final zongpai = parsed.value; - - // 三种模式都允许后扫入的总排号覆盖当前扫码区;未确认数据不会入库。 - _queryBoxInfo(zongpai); - } - - Future _queryBoxInfo(String zongpai) async { - final action = await _apiActions.fetchBoxInfo(zongpai); - - if (!mounted) return false; - - if (!action.success) { - _feedbackService.trigger(switch (action.errorKind) { - BoxingActionErrorKind.network => FeedbackEvent.networkError, - BoxingActionErrorKind.missingApiUrl => FeedbackEvent.submitFailure, - _ => FeedbackEvent.scanInvalid, - }); - _showStatusOverride( - action.errorMessage ?? '查询失败', - _dotForActionError(action.errorKind), - const Duration(seconds: 2), - ); - return false; - } - - final result = action.data!; - // 判断该总牌号是否已全部装箱完毕 - final packedQuantity = result.currentZongpaiBoxes.fold( - 0, - (sum, item) => sum + item.quantity, - ); - final totalQuantity = result.quantity ?? 0; - final alreadyCompleted = - totalQuantity > 0 && packedQuantity >= totalQuantity; - - _feedbackService.trigger( - alreadyCompleted - ? FeedbackEvent.alreadyCompleted - : FeedbackEvent.scanValid, - ); - - setState(() { - _zongpaiNo = zongpai; - _paichanNo = result.paichanNo; - _workOrderNo = result.workOrderNo; - _erpQuantity = result.quantity; - _currentZongpaiBoxes = result.currentZongpaiBoxes; - _existingBoxes = result.existingBoxes; - _maxBoxNo = result.maxBoxNo; - _phase = BoxingPhase.scanned; - _isDuplicateBoxNo = false; - _editingBox = null; - _editingAssignedItemId = null; - _editingAssignedQuantity = ''; - _deletingAssignedItemId = null; - _completedJumpBoxNo = null; - _statusOverrideText = null; - if (_mode == BoxingMode.singleCode) { - _paichanSwitchNotice = null; - } - - // 根据模式自动填充 - _applyAutoFill(); - }); - return true; - } - - Future _processAutoScanCodes(List codes) async { - if (!mounted) return; - setState(() { - _isAutoProcessing = true; - _autoWarnings.clear(); - _statusOverrideText = '正在同步转运数据...'; - _statusOverrideDot = StatusDotColor.orange; - }); - - if (_mode == BoxingMode.singleCode) { - final code = codes.first; - final ok = await _queryBoxInfo(code); - if (!ok && mounted) { - setState(() => _autoWarnings.add('总排号 $code 未找到排产号信息,已忽略')); - } - } else { - for (final code in codes) { - if (!mounted) return; - final queried = await _queryBoxInfo(code); - if (!queried) { - if (mounted) { - setState(() => _autoWarnings.add('总排号 $code 未找到排产号信息,已忽略')); - } - continue; - } - if (!_canSubmit) { - if (mounted) { - setState(() => _autoWarnings.add('总排号 $code 暂不能装箱,已忽略')); - } - continue; - } - final saved = await _submit(); - if (!saved && mounted) { - setState(() => _autoWarnings.add('总排号 $code 自动装箱失败,请手动处理')); - } - } - } - - if (!mounted) return; - setState(() { - _isAutoProcessing = false; - _statusOverrideText = null; - _statusOverrideDot = null; - }); - _focusQuantityInput(); - _requestFocus(); - } - - void _applyAutoFill() { - switch (_mode) { - case BoxingMode.singleCode: - final remaining = _remainingQuantity; - if (remaining <= 0) { - _boxNoController.clear(); - _quantityController.clear(); - _statusOverrideText = '该总排号已全部装箱完毕'; - _statusOverrideDot = StatusDotColor.red; - return; - } - _boxNoController.text = (_maxBoxNo + 1).toString(); - _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: - final decision = decideMultiCodePaichanContext( - lastPaichanNo: _lastScannedPaichanNo, - currentPaichanNo: _paichanNo, - currentBoxNoText: _boxNoController.text, - maxBoxNo: _maxBoxNo, - ); - if (decision.isSwitched) { - _crossPaichaPending = true; - _feedbackService.trigger(FeedbackEvent.alreadyCompleted); - _showCrossPaichaDialog(); - return; - } else { - _paichanSwitchNotice = null; - } - final boxNoToApply = decision.boxNoToApply; - if (boxNoToApply != null) { - _boxNoController.text = boxNoToApply.toString(); - } - _lastScannedPaichanNo = _paichanNo; - final remaining = _remainingQuantity; - if (remaining <= 0) { - _quantityController.clear(); - if (_manyToOnePackedItems.isEmpty && - _currentZongpaiBoxes.isNotEmpty) { - _completedJumpBoxNo = _currentZongpaiBoxes.first.boxNo; - } - _statusOverrideText = '该总排号已全部装箱完毕'; - _statusOverrideDot = StatusDotColor.red; - return; - } - _quantityController.text = remaining.toString(); - _quantityController.selection = TextSelection( - baseOffset: 0, - extentOffset: _quantityController.text.length, - ); - WidgetsBinding.instance.addPostFrameCallback((_) { - if (mounted && _mode == BoxingMode.multiCode) { - _quantityFocusNode.requestFocus(); - } - }); - } - _isDuplicateBoxNo = _boxNoIsDuplicate(); - } - - // === 跨排产号确认对话框 === - - Future _showCrossPaichaDialog() async { - final currentPaicha = _lastScannedPaichanNo ?? '--'; - final newPaicha = _paichanNo ?? '--'; - final confirmed = await showCrossPaichanDialog( - context: context, - currentPaicha: currentPaicha, - newPaicha: newPaicha, - ); - if (!mounted) return; - confirmed ? _confirmPaichanSwitch() : _cancelPaichanSwitch(); - } - - void _confirmPaichanSwitch() { - setState(() { - _crossPaichaPending = false; - _manyToOnePackedItems.clear(); - _editingPackedItemId = null; - _editingPackedQuantity = ''; - _deletingPackedItemId = null; - _paichanSwitchNotice = '排产号已切换:${_paichanNo ?? "--"}'; - _boxNoController.text = (_maxBoxNo + 1).toString(); - _lastScannedPaichanNo = _paichanNo; - }); - _feedbackService.trigger(FeedbackEvent.paichanSwitch); - - // Continue with remaining quantity flow - final remaining = _remainingQuantity; - if (remaining <= 0) { - _quantityController.clear(); - if (_manyToOnePackedItems.isEmpty && _currentZongpaiBoxes.isNotEmpty) { - _completedJumpBoxNo = _currentZongpaiBoxes.first.boxNo; - } - setState(() { - _statusOverrideText = '该总排号已全部装箱完毕'; - _statusOverrideDot = StatusDotColor.red; - }); - return; - } - setState(() { - _quantityController.text = remaining.toString(); - _quantityController.selection = TextSelection( - baseOffset: 0, - extentOffset: _quantityController.text.length, - ); - _isDuplicateBoxNo = _boxNoIsDuplicate(); - }); - WidgetsBinding.instance.addPostFrameCallback((_) { - if (mounted && _mode == BoxingMode.multiCode) { - _quantityFocusNode.requestFocus(); - } - }); - } - - void _cancelPaichanSwitch() { - setState(() { - _crossPaichaPending = false; - _zongpaiNo = null; - _paichanNo = _lastScannedPaichanNo; - _phase = BoxingPhase.waiting; - }); - } - // === 重复箱号检测 === bool _boxNoIsDuplicate() { @@ -557,7 +298,7 @@ class _BoxingPageState extends State with WidgetsBindingObserver { void _submitFromKeyboard() { if (_canSubmit) { - _submit(); + this._submit(); } } @@ -585,545 +326,11 @@ class _BoxingPageState extends State with WidgetsBindingObserver { ); } - Future _submit() async { - if (!_canSubmit) return false; - - final boxNo = int.parse(_boxNoController.text); - final quantity = int.parse(_quantityController.text); - - setState(() => _isSubmitting = true); - - final editing = _editingBox; - final action = editing == null - ? await _apiActions.saveBoxRecord( - zongpaiNo: _zongpaiNo!, - boxNo: boxNo, - quantity: quantity, - ) - : await _apiActions.updateBoxRecord( - boxItemId: editing.boxItemId, - boxNo: boxNo, - quantity: quantity, - ); - - if (!mounted) return false; - - setState(() => _isSubmitting = false); - - if (action.success) { - if (editing == null) { - _onSubmitSuccess(boxNo, quantity, action.data!.boxItemId); - } else { - _onUpdateSuccess(editing, boxNo, quantity); - } - return true; - } else if (action.errorKind == BoxingActionErrorKind.duplicate) { - _feedbackService.trigger(FeedbackEvent.duplicateBoxNo); - _showStatusOverride( - '该总排号在箱号 ${action.boxNo ?? ""} 已存在,请重新输入', - StatusDotColor.amber, - const Duration(seconds: 2), - ); - return false; - } else { - _showActionError(action, fallback: '提交失败'); - return false; - } - } - - void _onSubmitSuccess(int boxNo, int quantity, int? boxItemId) { - _feedbackService.trigger(FeedbackEvent.submitSuccess); - - setState(() { - if (_mode == BoxingMode.singleCode && boxItemId != null) { - _currentZongpaiBoxes = List.from(_currentZongpaiBoxes) - ..add( - CurrentZongpaiBoxData( - boxItemId: boxItemId, - boxNo: boxNo, - quantity: quantity, - ), - ); - } - _addExistingBoxItem(boxNo, quantity, boxItemId); - _maxBoxNo = _maxBoxNo > boxNo ? _maxBoxNo : boxNo; - }); - - switch (_mode) { - case BoxingMode.singleCode: - final remaining = _remainingQuantity; - if (remaining <= 0) { - _showStatusOverride( - '装箱成功', - StatusDotColor.green, - const Duration(milliseconds: 1500), - ); - } else { - setState(() { - _phase = BoxingPhase.scanned; - _boxNoController.text = (_maxBoxNo + 1).toString(); - _quantityController.text = remaining.toString(); - _quantityController.selection = TextSelection( - baseOffset: 0, - extentOffset: _quantityController.text.length, - ); - _isDuplicateBoxNo = false; - }); - _focusQuantityInput(); - _showStatusOverride( - '请继续完成剩余数量装箱', - StatusDotColor.green, - const Duration(milliseconds: 1500), - ); - } - - case BoxingMode.multiCode: - _lastScannedPaichanNo = _paichanNo; - setState(() { - if (boxItemId != null) { - _manyToOnePackedItems.add( - ManyToOnePackedItem( - boxItemId: boxItemId, - zongpaiNo: _zongpaiNo!, - paichanNo: _paichanNo, - workOrderNo: _workOrderNo, - boxNo: boxNo, - quantity: quantity, - totalQuantity: _erpQuantity, - ), - ); - } - _phase = BoxingPhase.waiting; - _zongpaiNo = null; - _editingPackedItemId = null; - _editingPackedQuantity = ''; - _deletingPackedItemId = null; - }); - _showStatusOverride( - '装箱成功,请继续扫码', - StatusDotColor.green, - const Duration(milliseconds: 1500), - ); - } - } - - void _onUpdateSuccess( - CurrentZongpaiBoxData editing, - int boxNo, - int quantity, - ) { - _feedbackService.trigger(FeedbackEvent.submitSuccess); - - setState(() { - _currentZongpaiBoxes = _currentZongpaiBoxes.map((item) { - if (item.boxItemId != editing.boxItemId) return item; - return CurrentZongpaiBoxData( - boxItemId: item.boxItemId, - boxNo: boxNo, - quantity: quantity, - ); - }).toList(); - _replaceExistingBoxItem(editing, boxNo, quantity); - _editingBox = null; - _boxNoController.text = (_maxBoxNo + 1).toString(); - final remaining = _remainingQuantity; - if (remaining > 0) { - _quantityController.text = remaining.toString(); - } else { - _quantityController.clear(); - } - _isDuplicateBoxNo = false; - }); - _showStatusOverride( - '修改成功', - StatusDotColor.green, - const Duration(milliseconds: 1500), - ); - } - - void _replaceExistingBoxItem( - CurrentZongpaiBoxData editing, - int boxNo, - int quantity, - ) { - final result = box_mutations.replaceExistingBoxItem( - existingBoxes: _existingBoxes, - editing: editing, - boxNo: boxNo, - quantity: quantity, - zongpaiNo: _zongpaiNo!, - workOrderNo: _workOrderNo, - totalQuantity: _erpQuantity, - ); - _existingBoxes = result.boxes; - _maxBoxNo = result.maxBoxNo; - } - - void _addExistingBoxItem(int boxNo, int quantity, int? boxItemId) { - final result = box_mutations.addExistingBoxItem( - existingBoxes: _existingBoxes, - boxNo: boxNo, - quantity: quantity, - boxItemId: boxItemId, - zongpaiNo: _zongpaiNo!, - workOrderNo: _workOrderNo, - totalQuantity: _erpQuantity, - ); - _existingBoxes = result.boxes; - _maxBoxNo = result.maxBoxNo; - } - - void _finishManyToOneBox() { - setState(() { - _manyToOnePackedItems.clear(); - _editingPackedItemId = null; - _editingPackedQuantity = ''; - _deletingPackedItemId = null; - _zongpaiNo = null; - _phase = BoxingPhase.waiting; - _quantityController.clear(); - _boxNoController.text = (_maxBoxNo + 1).toString(); - _statusOverrideText = null; - _statusOverrideDot = null; - }); - } - - void _startEditAssigned(CurrentZongpaiBoxData item) { - setState(() { - _editingAssignedItemId = item.boxItemId; - _editingAssignedQuantity = item.quantity.toString(); - _deletingAssignedItemId = null; - _statusOverrideText = null; - _statusOverrideDot = null; - }); - } - - void _cancelEditAssigned() { - setState(() { - _editingAssignedItemId = null; - _editingAssignedQuantity = ''; - }); - } - - int _maxAssignedQuantity(CurrentZongpaiBoxData item) { - return boxing_calculations.maxAssignedQuantity( - totalQuantity: _erpQuantity, - packedQuantity: _packedQuantity, - itemQuantity: item.quantity, - ); - } - - void _refreshSingleCodeNewInput({bool focusQuantity = true}) { - _boxNoController.text = (_maxBoxNo + 1).toString(); - final remaining = _remainingQuantity; - if (remaining > 0) { - _quantityController.text = remaining.toString(); - _quantityController.selection = TextSelection( - baseOffset: 0, - extentOffset: _quantityController.text.length, - ); - if (focusQuantity) { - _focusQuantityInput(); - } - } else { - _quantityController.clear(); - } - _isDuplicateBoxNo = false; - } - - Future _saveAssignedItem(CurrentZongpaiBoxData item) async { - final quantity = int.tryParse(_editingAssignedQuantity); - if (quantity == null || quantity <= 0) { - _showStatusOverride( - '请输入有效数量', - StatusDotColor.red, - const Duration(seconds: 2), - ); - return; - } - if (quantity > _maxAssignedQuantity(item)) { - _showStatusOverride( - '超出可装数量上限', - StatusDotColor.red, - const Duration(seconds: 2), - ); - return; - } - - setState(() => _isSubmitting = true); - final action = await _apiActions.updateBoxRecord( - boxItemId: item.boxItemId, - boxNo: item.boxNo, - quantity: quantity, - ); - if (!mounted) return; - - setState(() => _isSubmitting = false); - if (action.success) { - setState(() { - _currentZongpaiBoxes = _currentZongpaiBoxes.map((box) { - if (box.boxItemId != item.boxItemId) return box; - return CurrentZongpaiBoxData( - boxItemId: box.boxItemId, - boxNo: box.boxNo, - quantity: quantity, - ); - }).toList(); - _replaceExistingBoxItem(item, item.boxNo, quantity); - _editingAssignedItemId = null; - _editingAssignedQuantity = ''; - _refreshSingleCodeNewInput(); - }); - _showStatusOverride( - '修改成功', - StatusDotColor.green, - const Duration(milliseconds: 1500), - ); - } else { - _showActionError(action, fallback: '修改失败'); - } - } - - Future _deleteAssignedItem(CurrentZongpaiBoxData item) async { - setState(() => _isSubmitting = true); - final action = await _apiActions.deleteBoxRecord(boxItemId: item.boxItemId); - if (!mounted) return; - - setState(() { - _isSubmitting = false; - if (action.success) { - _currentZongpaiBoxes = _currentZongpaiBoxes - .where((box) => box.boxItemId != item.boxItemId) - .toList(); - _removeExistingBoxItem(item.boxItemId); - if (_editingAssignedItemId == item.boxItemId) { - _editingAssignedItemId = null; - _editingAssignedQuantity = ''; - } - _deletingAssignedItemId = null; - _refreshSingleCodeNewInput(); - } - }); - - if (action.success) { - _showStatusOverride( - '删除成功', - StatusDotColor.green, - const Duration(milliseconds: 1500), - ); - } else { - _showActionError(action, fallback: '删除失败'); - } - } - - void _startEditPackedItem(ManyToOnePackedItem item) { - setState(() { - _editingPackedItemId = item.boxItemId; - _editingPackedQuantity = item.quantity.toString(); - _deletingPackedItemId = null; - }); - } - - void _cancelEditPackedItem() { - setState(() { - _editingPackedItemId = null; - _editingPackedQuantity = ''; - }); - } - - Future _savePackedItem(ManyToOnePackedItem item) async { - final quantity = int.tryParse(_editingPackedQuantity); - final boxNo = int.tryParse(_boxNoController.text.trim()); - if (quantity == null || quantity <= 0 || boxNo == null || boxNo <= 0) { - _showStatusOverride( - '请输入有效箱号和数量', - StatusDotColor.red, - const Duration(seconds: 2), - ); - return; - } - - setState(() => _isSubmitting = true); - final action = await _apiActions.updateBoxRecord( - boxItemId: item.boxItemId, - boxNo: boxNo, - quantity: quantity, - ); - if (!mounted) return; - - setState(() => _isSubmitting = false); - if (action.success) { - setState(() { - final index = _manyToOnePackedItems.indexWhere( - (packed) => packed.boxItemId == item.boxItemId, - ); - if (index >= 0) { - _manyToOnePackedItems[index] = _manyToOnePackedItems[index].copyWith( - boxNo: boxNo, - quantity: quantity, - ); - } - _replaceExistingPackedItem(item, boxNo, quantity); - _editingPackedItemId = null; - _editingPackedQuantity = ''; - }); - _showStatusOverride( - '修改成功', - StatusDotColor.green, - const Duration(milliseconds: 1500), - ); - } else { - _showActionError(action, fallback: '修改失败'); - } - } - - void _replaceExistingPackedItem( - ManyToOnePackedItem item, - int boxNo, - int quantity, - ) { - final result = box_mutations.replaceExistingPackedItem( - existingBoxes: _existingBoxes, - item: item, - boxNo: boxNo, - quantity: quantity, - ); - _existingBoxes = result.boxes; - _maxBoxNo = result.maxBoxNo; - } - - Future _deletePackedItem(ManyToOnePackedItem item) async { - setState(() => _isSubmitting = true); - final action = await _apiActions.deleteBoxRecord(boxItemId: item.boxItemId); - if (!mounted) return; - - setState(() { - _isSubmitting = false; - if (action.success) { - _manyToOnePackedItems.removeWhere( - (packed) => packed.boxItemId == item.boxItemId, - ); - _removeExistingBoxItem(item.boxItemId); - if (_editingPackedItemId == item.boxItemId) { - _editingPackedItemId = null; - _editingPackedQuantity = ''; - } - _deletingPackedItemId = null; - } - }); - - if (action.success) { - _showStatusOverride( - '删除成功', - StatusDotColor.green, - const Duration(milliseconds: 1500), - ); - } else { - _showActionError(action, fallback: '删除失败'); - } - } - - void _removeExistingBoxItem(int boxItemId) { - final result = box_mutations.removeExistingBoxItem( - existingBoxes: _existingBoxes, - boxItemId: boxItemId, - ); - _existingBoxes = result.boxes; - _maxBoxNo = result.maxBoxNo; - } - - // === Status bar management === - - StatusDotColor _dotForActionError(BoxingActionErrorKind? kind) { - return switch (kind) { - BoxingActionErrorKind.network => StatusDotColor.yellow, - BoxingActionErrorKind.duplicate => StatusDotColor.amber, - _ => StatusDotColor.red, - }; - } - - void _showActionError( - BoxingActionResult action, { - required String fallback, - }) { - _feedbackService.trigger(switch (action.errorKind) { - BoxingActionErrorKind.network => FeedbackEvent.networkError, - BoxingActionErrorKind.duplicate => FeedbackEvent.duplicateBoxNo, - _ => FeedbackEvent.submitFailure, - }); - _showStatusOverride( - action.errorMessage ?? fallback, - _dotForActionError(action.errorKind), - const Duration(seconds: 2), - ); - } - - void _showStatusOverride(String text, StatusDotColor dot, Duration duration) { - setState(() { - _statusOverrideText = text; - _statusOverrideDot = dot; - }); - Future.delayed(duration, () { - if (mounted) { - setState(() { - _statusOverrideText = null; - _statusOverrideDot = null; - }); - } - }); - } - - void _jumpToCompletedBox() { - final boxNo = _completedJumpBoxNo; - if (boxNo == null) return; - setState(() { - _boxNoController.text = boxNo.toString(); - _completedJumpBoxNo = null; - _statusOverrideText = null; - _statusOverrideDot = null; - }); - } - - // === 导航到详情页 === - - void _openDetail() { - Navigator.push( - context, - MaterialPageRoute( - builder: (_) => BoxingDetailPage( - paichanNo: _paichanNo ?? '', - existingBoxes: _existingBoxes, - ), - ), - ); - } - - // === 状态文字 === - - void _updateBaseStatus() { - final status = boxingStatusFor( - isAutoProcessing: _isAutoProcessing, - isSubmitting: _isSubmitting, - isDuplicateBoxNo: _isDuplicateBoxNo, - quantityTooHigh: _quantityTooHigh, - isEditingAssigned: _editingAssignedItemId != null, - isDeletingAssigned: _deletingAssignedItemId != null, - hasPaichanSwitchNotice: _paichanSwitchNotice != null, - phase: _phase, - isMultiCode: _mode == BoxingMode.multiCode, - hasVisibleManyToOneItems: _visibleManyToOnePackedItems.isNotEmpty, - remainingQuantity: _remainingQuantity, - boxNoText: _boxNoController.text, - ); - _statusDot = status.dot; - _statusText = status.text; - } - // === Build === @override Widget build(BuildContext context) { - _updateBaseStatus(); + this._updateBaseStatus(); final effectiveDot = _statusOverrideDot ?? _statusDot; final effectiveText = _statusOverrideText ?? _statusText; @@ -1160,7 +367,7 @@ class _BoxingPageState extends State with WidgetsBindingObserver { remainingQuantity: _remainingQuantity, zongpaiNo: _zongpaiNo, completedJumpBoxNo: _completedJumpBoxNo, - onJumpToCompletedBox: _jumpToCompletedBox, + onJumpToCompletedBox: () => this._jumpToCompletedBox(), ), Expanded( @@ -1185,16 +392,18 @@ class _BoxingPageState extends State with WidgetsBindingObserver { editingPackedItemId: _editingPackedItemId, editingPackedQuantity: _editingPackedQuantity, deletingPackedItemId: _deletingPackedItemId, - onOpenDetail: _openDetail, + onOpenDetail: () => this._openDetail(), onSubmitFromKeyboard: _submitFromKeyboard, - onSubmit: _submit, + onSubmit: () => this._submit(), onQuantityChanged: () => setState(() {}), - onFinishBox: _finishManyToOneBox, + onFinishBox: () => this._finishManyToOneBox(), onEditingPackedQuantityChanged: (value) => _editingPackedQuantity = value, - onSavePackedItem: _savePackedItem, - onCancelEditPackedItem: _cancelEditPackedItem, - onStartEditPackedItem: _startEditPackedItem, + onSavePackedItem: (item) => this._savePackedItem(item), + onCancelEditPackedItem: () => + this._cancelEditPackedItem(), + onStartEditPackedItem: (item) => + this._startEditPackedItem(item), onStartDeletePackedItem: (item) { setState(() { _deletingPackedItemId = item.boxItemId; @@ -1202,7 +411,8 @@ class _BoxingPageState extends State with WidgetsBindingObserver { _editingPackedQuantity = ''; }); }, - onDeletePackedItem: _deletePackedItem, + onDeletePackedItem: (item) => + this._deletePackedItem(item), onCancelDeletePackedItem: () => setState(() => _deletingPackedItemId = null), ) @@ -1232,16 +442,18 @@ class _BoxingPageState extends State with WidgetsBindingObserver { editingAssignedItemId: _editingAssignedItemId, editingAssignedQuantity: _editingAssignedQuantity, deletingAssignedItemId: _deletingAssignedItemId, - onOpenDetail: _openDetail, + onOpenDetail: () => this._openDetail(), onCheckDuplicateBoxNo: _checkDuplicateBoxNo, onSubmitFromKeyboard: _submitFromKeyboard, - onSubmit: _submit, + onSubmit: () => this._submit(), onQuantityChanged: () => setState(() {}), onEditingAssignedQuantityChanged: (value) => _editingAssignedQuantity = value, - onSaveAssignedItem: _saveAssignedItem, - onCancelEditAssigned: _cancelEditAssigned, - onStartEditAssigned: _startEditAssigned, + onSaveAssignedItem: (item) => + this._saveAssignedItem(item), + onCancelEditAssigned: () => this._cancelEditAssigned(), + onStartEditAssigned: (item) => + this._startEditAssigned(item), onStartDeleteAssigned: (item) { setState(() { _deletingAssignedItemId = item.boxItemId; @@ -1249,7 +461,8 @@ class _BoxingPageState extends State with WidgetsBindingObserver { _editingAssignedQuantity = ''; }); }, - onDeleteAssignedItem: _deleteAssignedItem, + onDeleteAssignedItem: (item) => + this._deleteAssignedItem(item), onCancelDeleteAssigned: () => setState(() => _deletingAssignedItemId = null), ),