feat: add cross-paichan warning dialog to multi-code boxing & fix box number calculation
Multi-code boxing mode now shows a confirmation dialog when scanning a code from a different paichan, matching the registration page pattern. Scans are blocked while the dialog is visible. Also removes the redundant _lastBoxNo variable. Box numbers now always use _maxBoxNo + 1 from the backend, fixing a bug where switching to a new paichan incorrectly carried over the previous paichan's box number. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -123,12 +123,11 @@ class _BoxingPageState extends State<BoxingPage> with WidgetsBindingObserver {
|
|||||||
int? _deletingAssignedItemId;
|
int? _deletingAssignedItemId;
|
||||||
String? _lastScannedPaichanNo;
|
String? _lastScannedPaichanNo;
|
||||||
String? _paichanSwitchNotice;
|
String? _paichanSwitchNotice;
|
||||||
|
bool _crossPaichaPending = false;
|
||||||
|
|
||||||
// 提交中
|
// 提交中
|
||||||
bool _isSubmitting = false;
|
bool _isSubmitting = false;
|
||||||
|
|
||||||
// 单码装箱:上次提交的箱号(用于继续添加预填)
|
|
||||||
int? _lastBoxNo;
|
|
||||||
CurrentZongpaiBoxData? _editingBox;
|
CurrentZongpaiBoxData? _editingBox;
|
||||||
|
|
||||||
// 重复箱号
|
// 重复箱号
|
||||||
@@ -273,8 +272,8 @@ class _BoxingPageState extends State<BoxingPage> with WidgetsBindingObserver {
|
|||||||
_deletingAssignedItemId = null;
|
_deletingAssignedItemId = null;
|
||||||
_lastScannedPaichanNo = null;
|
_lastScannedPaichanNo = null;
|
||||||
_paichanSwitchNotice = null;
|
_paichanSwitchNotice = null;
|
||||||
|
_crossPaichaPending = false;
|
||||||
_isSubmitting = false;
|
_isSubmitting = false;
|
||||||
_lastBoxNo = null;
|
|
||||||
_editingBox = null;
|
_editingBox = null;
|
||||||
_isDuplicateBoxNo = false;
|
_isDuplicateBoxNo = false;
|
||||||
_statusOverrideText = null;
|
_statusOverrideText = null;
|
||||||
@@ -288,6 +287,11 @@ class _BoxingPageState extends State<BoxingPage> with WidgetsBindingObserver {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_crossPaichaPending) {
|
||||||
|
_feedbackService.trigger(FeedbackEvent.alreadyCompleted);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
final parsed = CodeParser.parse(result.barcode);
|
final parsed = CodeParser.parse(result.barcode);
|
||||||
|
|
||||||
if (parsed.type != CodeType.zongpaiNo) {
|
if (parsed.type != CodeType.zongpaiNo) {
|
||||||
@@ -438,11 +442,7 @@ class _BoxingPageState extends State<BoxingPage> with WidgetsBindingObserver {
|
|||||||
_statusOverrideDot = StatusDotColor.red;
|
_statusOverrideDot = StatusDotColor.red;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (_lastBoxNo != null) {
|
_boxNoController.text = (_maxBoxNo + 1).toString();
|
||||||
_boxNoController.text = (_lastBoxNo! + 1).toString();
|
|
||||||
} else {
|
|
||||||
_boxNoController.text = (_maxBoxNo + 1).toString();
|
|
||||||
}
|
|
||||||
_quantityController.text = remaining.toString();
|
_quantityController.text = remaining.toString();
|
||||||
_quantityController.selection = TextSelection(
|
_quantityController.selection = TextSelection(
|
||||||
baseOffset: 0,
|
baseOffset: 0,
|
||||||
@@ -461,12 +461,10 @@ class _BoxingPageState extends State<BoxingPage> with WidgetsBindingObserver {
|
|||||||
maxBoxNo: _maxBoxNo,
|
maxBoxNo: _maxBoxNo,
|
||||||
);
|
);
|
||||||
if (decision.isSwitched) {
|
if (decision.isSwitched) {
|
||||||
_manyToOnePackedItems.clear();
|
_crossPaichaPending = true;
|
||||||
_editingPackedItemId = null;
|
_feedbackService.trigger(FeedbackEvent.alreadyCompleted);
|
||||||
_editingPackedQuantity = '';
|
_showCrossPaichaDialog();
|
||||||
_deletingPackedItemId = null;
|
return;
|
||||||
_paichanSwitchNotice = '排产号已切换:${_paichanNo ?? "--"}';
|
|
||||||
_feedbackService.trigger(FeedbackEvent.paichanSwitch);
|
|
||||||
} else {
|
} else {
|
||||||
_paichanSwitchNotice = null;
|
_paichanSwitchNotice = null;
|
||||||
}
|
}
|
||||||
@@ -500,6 +498,178 @@ class _BoxingPageState extends State<BoxingPage> with WidgetsBindingObserver {
|
|||||||
_isDuplicateBoxNo = _boxNoIsDuplicate();
|
_isDuplicateBoxNo = _boxNoIsDuplicate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// === 跨排产号确认对话框 ===
|
||||||
|
|
||||||
|
void _showCrossPaichaDialog() {
|
||||||
|
final currentPaicha = _lastScannedPaichanNo ?? '--';
|
||||||
|
final newPaicha = _paichanNo ?? '--';
|
||||||
|
final dialogFocus = FocusNode();
|
||||||
|
var dismissed = false;
|
||||||
|
var selectedIndex = 0; // 0 = "否" (default), 1 = "是"
|
||||||
|
|
||||||
|
void dismissAsNo() {
|
||||||
|
if (dismissed) return;
|
||||||
|
dismissed = true;
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
_cancelPaichanSwitch();
|
||||||
|
}
|
||||||
|
|
||||||
|
void confirmSwitch() {
|
||||||
|
if (dismissed) return;
|
||||||
|
dismissed = true;
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
_confirmPaichanSwitch();
|
||||||
|
}
|
||||||
|
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
barrierDismissible: false,
|
||||||
|
builder: (ctx) => StatefulBuilder(
|
||||||
|
builder: (ctx, setDialogState) {
|
||||||
|
return KeyboardListener(
|
||||||
|
focusNode: dialogFocus,
|
||||||
|
onKeyEvent: (event) {
|
||||||
|
if (event is! KeyDownEvent) return;
|
||||||
|
if (event.logicalKey == LogicalKeyboardKey.arrowUp) {
|
||||||
|
setDialogState(() => selectedIndex = 0);
|
||||||
|
} else if (event.logicalKey == LogicalKeyboardKey.arrowDown) {
|
||||||
|
setDialogState(() => selectedIndex = 1);
|
||||||
|
} else if (event.logicalKey == LogicalKeyboardKey.enter) {
|
||||||
|
selectedIndex == 0 ? dismissAsNo() : confirmSwitch();
|
||||||
|
} else if (event.logicalKey == LogicalKeyboardKey.escape) {
|
||||||
|
dismissAsNo();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: PopScope(
|
||||||
|
canPop: false,
|
||||||
|
onPopInvokedWithResult: (didPop, _) {
|
||||||
|
if (!didPop) dismissAsNo();
|
||||||
|
},
|
||||||
|
child: AlertDialog(
|
||||||
|
backgroundColor: Colors.orange.shade50,
|
||||||
|
title: Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.warning_amber, color: Colors.orange.shade800),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Text(
|
||||||
|
'跨排产号扫描',
|
||||||
|
style: TextStyle(color: Colors.orange.shade900),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
content: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'当前排产号:$currentPaicha\n'
|
||||||
|
'新排产号:$newPaicha\n\n'
|
||||||
|
'是否切换到新的排产号?\n\n'
|
||||||
|
'选择「是」将放弃当前已扫描的所有数据。',
|
||||||
|
style: const TextStyle(fontSize: 14),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
_dialogOptionBtn(
|
||||||
|
label: '否',
|
||||||
|
selected: selectedIndex == 0,
|
||||||
|
onTap: dismissAsNo,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
_dialogOptionBtn(
|
||||||
|
label: '是,切换排产号',
|
||||||
|
selected: selectedIndex == 1,
|
||||||
|
onTap: confirmSwitch,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
dialogFocus.requestFocus();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = _Phase.waiting;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _dialogOptionBtn({
|
||||||
|
required String label,
|
||||||
|
required bool selected,
|
||||||
|
required VoidCallback onTap,
|
||||||
|
}) {
|
||||||
|
return SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
height: 40,
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: onTap,
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor:
|
||||||
|
selected ? Colors.blue.shade700 : Colors.grey.shade200,
|
||||||
|
foregroundColor: selected ? Colors.white : Colors.black87,
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
label,
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: selected ? FontWeight.w800 : FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// === 重复箱号检测 ===
|
// === 重复箱号检测 ===
|
||||||
|
|
||||||
bool _boxNoIsDuplicate() {
|
bool _boxNoIsDuplicate() {
|
||||||
@@ -674,7 +844,6 @@ class _BoxingPageState extends State<BoxingPage> with WidgetsBindingObserver {
|
|||||||
_feedbackService.trigger(FeedbackEvent.submitSuccess);
|
_feedbackService.trigger(FeedbackEvent.submitSuccess);
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_lastBoxNo = boxNo;
|
|
||||||
if (_mode == BoxingMode.singleCode && boxItemId != null) {
|
if (_mode == BoxingMode.singleCode && boxItemId != null) {
|
||||||
_currentZongpaiBoxes = List.from(_currentZongpaiBoxes)
|
_currentZongpaiBoxes = List.from(_currentZongpaiBoxes)
|
||||||
..add(
|
..add(
|
||||||
@@ -765,7 +934,6 @@ class _BoxingPageState extends State<BoxingPage> with WidgetsBindingObserver {
|
|||||||
}).toList();
|
}).toList();
|
||||||
_replaceExistingBoxItem(editing, boxNo, quantity);
|
_replaceExistingBoxItem(editing, boxNo, quantity);
|
||||||
_editingBox = null;
|
_editingBox = null;
|
||||||
_lastBoxNo = _maxBoxNo;
|
|
||||||
_boxNoController.text = (_maxBoxNo + 1).toString();
|
_boxNoController.text = (_maxBoxNo + 1).toString();
|
||||||
final remaining = _remainingQuantity;
|
final remaining = _remainingQuantity;
|
||||||
if (remaining > 0) {
|
if (remaining > 0) {
|
||||||
@@ -884,7 +1052,6 @@ class _BoxingPageState extends State<BoxingPage> with WidgetsBindingObserver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _refreshSingleCodeNewInput({bool focusQuantity = true}) {
|
void _refreshSingleCodeNewInput({bool focusQuantity = true}) {
|
||||||
_lastBoxNo = _maxBoxNo;
|
|
||||||
_boxNoController.text = (_maxBoxNo + 1).toString();
|
_boxNoController.text = (_maxBoxNo + 1).toString();
|
||||||
final remaining = _remainingQuantity;
|
final remaining = _remainingQuantity;
|
||||||
if (remaining > 0) {
|
if (remaining > 0) {
|
||||||
|
|||||||
Reference in New Issue
Block a user