diff --git a/lib/pages/registration_page.dart b/lib/pages/registration_page.dart index 9e1c1f3..2de2282 100644 --- a/lib/pages/registration_page.dart +++ b/lib/pages/registration_page.dart @@ -224,16 +224,33 @@ class _RegistrationPageState extends State { bool get _isTransitTarget => isTransitTarget(_locationType, _locationCode); - /// Find the first scanned item that is already on shelf (for batch submit blocking). - PaichaOverviewItem? _findOnShelfInScanned() { - if (_overview?.success != true) return null; + /// Find all scanned items that are already on shelf. + List _findOnShelfItemsInScanned() { + if (_overview?.success != true) return []; final scannedSet = _zongpaiNos.toSet(); - for (final item in _overview!.items) { - if (scannedSet.contains(item.zongpaiNo) && item.status == 'on_shelf') { - return item; - } - } - return null; + return _overview!.items + .where((item) => + scannedSet.contains(item.zongpaiNo) && item.status == 'on_shelf') + .toList(); + } + + /// Find all scanned items that are already transferred. + List _findTransferredItemsInScanned() { + if (_overview?.success != true) return []; + final scannedSet = _zongpaiNos.toSet(); + return _overview!.items + .where((item) => + scannedSet.contains(item.zongpaiNo) && item.status == 'transferred') + .toList(); + } + + /// Whether any scanned item already has a location binding (on_shelf or transferred). + bool _hasAnyLocatedInScanned() { + if (_overview?.success != true) return false; + final scannedSet = _zongpaiNos.toSet(); + return _overview!.items.any((item) => + scannedSet.contains(item.zongpaiNo) && + (item.status == 'on_shelf' || item.status == 'transferred')); } Future _baseUrl() async { @@ -311,6 +328,21 @@ class _RegistrationPageState extends State { setState(() => _isSubmitting = true); + // For transit target: block if any scanned item already has a location + if (_isTransitTarget) { + if (_hasAnyLocatedInScanned()) { + final onShelfItems = _findOnShelfItemsInScanned(); + final transferredItems = _findTransferredItemsInScanned(); + setState(() => _isSubmitting = false); + _feedbackService.trigger(FeedbackEvent.submitFailure); + _showLocationConflictDialog( + onShelfItems: onShelfItems, + transferredItems: transferredItems, + ); + return; + } + } + if (_isLocked && _zongpaiNos.length > 1) { if (_isTransitTarget && !await _ensureBatchSamePaicha(baseUrl)) { if (!mounted) return; @@ -323,15 +355,16 @@ class _RegistrationPageState extends State { ); return; } - // Pre-check: block entire batch if any item is already on shelf (normal shelf) + // Pre-check: block entire batch if any item already has a location binding if (!_isTransitTarget) { - final onShelfItem = _findOnShelfInScanned(); - if (onShelfItem != null) { + final onShelfItems = _findOnShelfItemsInScanned(); + final transferredItems = _findTransferredItemsInScanned(); + if (onShelfItems.isNotEmpty || transferredItems.isNotEmpty) { setState(() => _isSubmitting = false); _feedbackService.trigger(FeedbackEvent.duplicateError); - _showDuplicateDialog( - onShelfItem.zongpaiNo, - {'location_code': onShelfItem.locationCode ?? '未知'}, + _showLocationConflictDialog( + onShelfItems: onShelfItems, + transferredItems: transferredItems, ); return; } @@ -633,6 +666,92 @@ class _RegistrationPageState extends State { ); } + void _showLocationConflictDialog({ + required List onShelfItems, + required List transferredItems, + }) { + final contentParts = []; + + if (onShelfItems.isNotEmpty) { + contentParts.add( + Text( + '重复上架', + style: TextStyle( + fontWeight: FontWeight.bold, + color: Colors.red.shade700, + ), + ), + ); + for (final item in onShelfItems) { + contentParts.addAll([ + Text('总排号:${item.zongpaiNo}'), + Text('已登记货位:${item.locationCode ?? "未知"}'), + const SizedBox(height: 6), + ]); + } + } + + if (transferredItems.isNotEmpty) { + if (onShelfItems.isNotEmpty) { + contentParts.add(const Divider()); + contentParts.add(const SizedBox(height: 4)); + } + contentParts.add( + Text( + '货物已转运', + style: TextStyle( + fontWeight: FontWeight.bold, + color: Colors.orange.shade700, + ), + ), + ); + for (final item in transferredItems) { + contentParts.addAll([ + Text('总排号:${item.zongpaiNo}'), + Text('转运货位:${item.locationCode ?? "未知"}'), + const SizedBox(height: 6), + ]); + } + } + + contentParts.add(const SizedBox(height: 4)); + contentParts.add( + const Text( + '请核查实物,确认是否操作错误。', + style: TextStyle(fontWeight: FontWeight.bold), + ), + ); + + showDialog( + context: context, + barrierDismissible: false, + builder: (ctx) => AlertDialog( + backgroundColor: Colors.red.shade50, + title: const Row( + children: [ + Icon(Icons.warning, color: Colors.red), + SizedBox(width: 8), + Text('操作冲突', style: TextStyle(color: Colors.red)), + ], + ), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: contentParts, + ), + actions: [ + TextButton( + onPressed: () { + _feedbackService.stopAlert(); + Navigator.pop(ctx); + }, + child: const Text('关闭'), + ), + ], + ), + ); + } + void _showCrossPaichaDialog(String newZongpaiNo) { final currentPaicha = _overview?.paichaNo ?? '--'; final dialogFocus = FocusNode();