- boxing_models.dart: add BoxingMode, BoxingPhase, BoxingPageArguments - boxing_box_mutations.dart: box item add/replace/remove operations - boxing_calculations.dart: pure calculation helpers (quantities, validation) - boxing_status_presenter.dart: status bar text/dot determination - dialogs/cross_paichan_dialog.dart: cross-paichan confirmation dialog - widgets/boxing_notice_banners.dart: notice banner widgets - Add unit tests for mutations, calculations, and status presenter Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1349 lines
41 KiB
Dart
1349 lines
41 KiB
Dart
import 'dart:async';
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:pad_scanner/services/app_config_service.dart';
|
|
import 'package:pad_scanner/services/scanner_service.dart';
|
|
import 'package:pad_scanner/services/code_parser.dart';
|
|
import 'package:pad_scanner/services/api_service.dart';
|
|
import 'package:pad_scanner/services/boxing_context.dart';
|
|
import 'package:pad_scanner/services/feedback_service.dart';
|
|
import 'package:pad_scanner/pages/boxing/boxing_box_mutations.dart'
|
|
as box_mutations;
|
|
import 'package:pad_scanner/pages/boxing/boxing_calculations.dart'
|
|
as boxing_calculations;
|
|
import 'package:pad_scanner/pages/boxing/boxing_models.dart';
|
|
import 'package:pad_scanner/pages/boxing/boxing_status_presenter.dart';
|
|
import 'package:pad_scanner/pages/boxing/dialogs/cross_paichan_dialog.dart';
|
|
import 'package:pad_scanner/pages/boxing/widgets/boxing_shared_widgets.dart';
|
|
import 'package:pad_scanner/pages/boxing/widgets/boxing_notice_banners.dart';
|
|
import 'package:pad_scanner/pages/boxing/widgets/multi_code_body.dart';
|
|
import 'package:pad_scanner/pages/boxing/widgets/single_code_body.dart';
|
|
import 'package:pad_scanner/pages/boxing_detail_page.dart';
|
|
import 'package:pad_scanner/widgets/status_bar.dart';
|
|
|
|
export 'package:pad_scanner/pages/boxing/boxing_models.dart'
|
|
show BoxingMode, BoxingPageArguments;
|
|
|
|
// === 主页面 ===
|
|
|
|
class BoxingPage extends StatefulWidget {
|
|
final BoxingPageArguments? arguments;
|
|
|
|
const BoxingPage({super.key, this.arguments});
|
|
|
|
@override
|
|
State<BoxingPage> createState() => _BoxingPageState();
|
|
}
|
|
|
|
class _BoxingPageState extends State<BoxingPage> with WidgetsBindingObserver {
|
|
final _scannerService = ScannerService();
|
|
final _apiService = ApiService();
|
|
final _feedbackService = FeedbackService();
|
|
final _focusNode = FocusNode();
|
|
StreamSubscription<ScanResult>? _scanSubscription;
|
|
|
|
// 模式
|
|
BoxingMode _mode = BoxingMode.singleCode;
|
|
|
|
// 阶段
|
|
BoxingPhase _phase = BoxingPhase.waiting;
|
|
|
|
// 当前总排号
|
|
String? _zongpaiNo;
|
|
|
|
// 排产号信息(来自后端查询)
|
|
String? _paichanNo;
|
|
String? _workOrderNo;
|
|
int? _erpQuantity;
|
|
List<CurrentZongpaiBoxData> _currentZongpaiBoxes = [];
|
|
List<BoxDetailData> _existingBoxes = [];
|
|
int _maxBoxNo = 0;
|
|
|
|
// 输入
|
|
final _boxNoController = TextEditingController();
|
|
final _quantityController = TextEditingController();
|
|
final _boxNoFocusNode = FocusNode();
|
|
final _quantityFocusNode = FocusNode();
|
|
|
|
// 多码凑箱:本次操作已确认装入当前箱的明细
|
|
final _manyToOnePackedItems = <ManyToOnePackedItem>[];
|
|
int? _editingPackedItemId;
|
|
String _editingPackedQuantity = '';
|
|
int? _deletingPackedItemId;
|
|
int? _editingAssignedItemId;
|
|
String _editingAssignedQuantity = '';
|
|
int? _deletingAssignedItemId;
|
|
String? _lastScannedPaichanNo;
|
|
String? _paichanSwitchNotice;
|
|
bool _crossPaichaPending = false;
|
|
|
|
// 提交中
|
|
bool _isSubmitting = false;
|
|
|
|
CurrentZongpaiBoxData? _editingBox;
|
|
|
|
// 重复箱号
|
|
bool _isDuplicateBoxNo = false;
|
|
|
|
// Status bar state
|
|
StatusDotColor _statusDot = StatusDotColor.blue;
|
|
String _statusText = '等待扫码';
|
|
String? _statusOverrideText;
|
|
StatusDotColor? _statusOverrideDot;
|
|
bool _isAutoProcessing = false;
|
|
final _autoWarnings = <String>[];
|
|
|
|
// 多码凑箱:已完成装箱时可跳转的箱号
|
|
int? _completedJumpBoxNo;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
WidgetsBinding.instance.addObserver(this);
|
|
final arguments = widget.arguments;
|
|
if (arguments != null) {
|
|
_mode = arguments.initialMode;
|
|
}
|
|
_boxNoController.addListener(_onBoxNoTextChanged);
|
|
_scanSubscription = _scannerService.scanResults.listen(_onScan);
|
|
if (arguments != null && arguments.autoScanCodes.isNotEmpty) {
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
_processAutoScanCodes(arguments.autoScanCodes);
|
|
});
|
|
}
|
|
WidgetsBinding.instance.addPostFrameCallback((_) => _requestFocus());
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
WidgetsBinding.instance.removeObserver(this);
|
|
_boxNoController.removeListener(_onBoxNoTextChanged);
|
|
_scanSubscription?.cancel();
|
|
_focusNode.dispose();
|
|
_boxNoController.dispose();
|
|
_quantityController.dispose();
|
|
_boxNoFocusNode.dispose();
|
|
_quantityFocusNode.dispose();
|
|
_feedbackService.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
void _requestFocus() {
|
|
if (mounted) {
|
|
_focusNode.requestFocus();
|
|
}
|
|
}
|
|
|
|
@override
|
|
void didChangeAppLifecycleState(AppLifecycleState state) {
|
|
if (state == AppLifecycleState.resumed) {
|
|
_requestFocus();
|
|
}
|
|
}
|
|
|
|
void _onKeyEvent(KeyEvent event) {
|
|
if (event is! KeyDownEvent || _isAutoProcessing) return;
|
|
if (_isModeSwitchKey(event)) {
|
|
_cycleMode();
|
|
}
|
|
if (_isFinishBoxKey(event)) {
|
|
final hasItems = _visibleManyToOnePackedItems.isNotEmpty;
|
|
if (hasItems && _mode == BoxingMode.multiCode) {
|
|
_finishManyToOneBox();
|
|
}
|
|
}
|
|
if (_completedJumpBoxNo != null && _isJumpToBoxKey(event)) {
|
|
_jumpToCompletedBox();
|
|
}
|
|
}
|
|
|
|
bool _isModeSwitchKey(KeyEvent event) {
|
|
final key = event.logicalKey;
|
|
return key == LogicalKeyboardKey.select ||
|
|
key == LogicalKeyboardKey.gameButtonRight1;
|
|
}
|
|
|
|
bool _isFinishBoxKey(KeyEvent event) {
|
|
return event.logicalKey == LogicalKeyboardKey.gameButtonLeft2;
|
|
}
|
|
|
|
bool _isJumpToBoxKey(KeyEvent event) {
|
|
return event.logicalKey == LogicalKeyboardKey.gameButtonRight2;
|
|
}
|
|
|
|
void _onBoxNoTextChanged() {
|
|
if (!mounted || _mode != BoxingMode.multiCode) return;
|
|
setState(() {
|
|
_editingPackedItemId = null;
|
|
_editingPackedQuantity = '';
|
|
_deletingPackedItemId = null;
|
|
});
|
|
}
|
|
|
|
// === 模式切换 ===
|
|
|
|
String get _modeLabel {
|
|
switch (_mode) {
|
|
case BoxingMode.singleCode:
|
|
return '单码装箱';
|
|
case BoxingMode.multiCode:
|
|
return '多码凑箱';
|
|
}
|
|
}
|
|
|
|
void _cycleMode() {
|
|
_feedbackService.trigger(FeedbackEvent.modeSwitch);
|
|
setState(() {
|
|
switch (_mode) {
|
|
case BoxingMode.singleCode:
|
|
_mode = BoxingMode.multiCode;
|
|
case BoxingMode.multiCode:
|
|
_mode = BoxingMode.singleCode;
|
|
}
|
|
_resetState();
|
|
});
|
|
}
|
|
|
|
void _resetState() {
|
|
_phase = BoxingPhase.waiting;
|
|
_zongpaiNo = null;
|
|
_paichanNo = null;
|
|
_workOrderNo = null;
|
|
_erpQuantity = null;
|
|
_currentZongpaiBoxes = [];
|
|
_existingBoxes = [];
|
|
_maxBoxNo = 0;
|
|
_boxNoController.clear();
|
|
_quantityController.clear();
|
|
_manyToOnePackedItems.clear();
|
|
_editingPackedItemId = null;
|
|
_editingPackedQuantity = '';
|
|
_deletingPackedItemId = null;
|
|
_editingAssignedItemId = null;
|
|
_editingAssignedQuantity = '';
|
|
_deletingAssignedItemId = null;
|
|
_lastScannedPaichanNo = null;
|
|
_paichanSwitchNotice = null;
|
|
_crossPaichaPending = false;
|
|
_isSubmitting = false;
|
|
_editingBox = null;
|
|
_isDuplicateBoxNo = false;
|
|
_statusOverrideText = null;
|
|
_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<bool> _queryBoxInfo(String zongpai) async {
|
|
final configService = AppConfigService();
|
|
final baseUrl = await configService.getString('api_url') ?? '';
|
|
if (baseUrl.isEmpty) {
|
|
_feedbackService.trigger(FeedbackEvent.submitFailure);
|
|
_showStatusOverride(
|
|
'未配置 API 地址,请前往设置',
|
|
StatusDotColor.red,
|
|
const Duration(seconds: 2),
|
|
);
|
|
return false;
|
|
}
|
|
|
|
final result = await _apiService.fetchBoxInfo(
|
|
baseUrl: baseUrl,
|
|
zongpaiNo: zongpai,
|
|
);
|
|
|
|
if (!mounted) return false;
|
|
|
|
if (!result.success) {
|
|
final isNetwork = result.errorMessage == '网络异常,请检查网络连接';
|
|
_feedbackService.trigger(
|
|
isNetwork ? FeedbackEvent.networkError : FeedbackEvent.scanInvalid,
|
|
);
|
|
_showStatusOverride(
|
|
result.errorMessage ?? '查询失败',
|
|
isNetwork ? StatusDotColor.yellow : StatusDotColor.red,
|
|
const Duration(seconds: 2),
|
|
);
|
|
return false;
|
|
}
|
|
|
|
// 判断该总牌号是否已全部装箱完毕
|
|
final packedQuantity = result.currentZongpaiBoxes.fold<int>(
|
|
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<void> _processAutoScanCodes(List<String> 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<void> _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() {
|
|
return switch (_mode) {
|
|
BoxingMode.multiCode => false,
|
|
BoxingMode.singleCode => boxing_calculations.singleCodeBoxNoIsDuplicate(
|
|
boxNoText: _boxNoController.text,
|
|
currentBoxes: _currentZongpaiBoxes,
|
|
editingBoxItemId: _editingBox?.boxItemId,
|
|
),
|
|
};
|
|
}
|
|
|
|
void _checkDuplicateBoxNo() {
|
|
setState(() => _isDuplicateBoxNo = _boxNoIsDuplicate());
|
|
}
|
|
|
|
// === 提交 ===
|
|
|
|
int get _packedQuantity {
|
|
return boxing_calculations.packedQuantity(_currentZongpaiBoxes);
|
|
}
|
|
|
|
int get _remainingQuantity {
|
|
return boxing_calculations.remainingQuantity(
|
|
totalQuantity: _erpQuantity,
|
|
currentBoxes: _currentZongpaiBoxes,
|
|
);
|
|
}
|
|
|
|
bool get _quantityTooHigh {
|
|
return boxing_calculations.quantityTooHigh(
|
|
remainingQuantity: _remainingQuantity,
|
|
quantityText: _quantityController.text,
|
|
);
|
|
}
|
|
|
|
bool get _canSubmit {
|
|
return boxing_calculations.canSubmitBoxing(
|
|
isSubmitting: _isSubmitting,
|
|
phase: _phase,
|
|
zongpaiNo: _zongpaiNo,
|
|
boxNoText: _boxNoController.text,
|
|
quantityText: _quantityController.text,
|
|
remainingQuantity: _remainingQuantity,
|
|
quantityTooHigh: _quantityTooHigh,
|
|
isDuplicateBoxNo: _isDuplicateBoxNo,
|
|
);
|
|
}
|
|
|
|
void _submitFromKeyboard() {
|
|
if (_canSubmit) {
|
|
_submit();
|
|
}
|
|
}
|
|
|
|
void _focusQuantityInput() {
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
if (!mounted) return;
|
|
_quantityFocusNode.requestFocus();
|
|
_quantityController.selection = TextSelection(
|
|
baseOffset: 0,
|
|
extentOffset: _quantityController.text.length,
|
|
);
|
|
});
|
|
}
|
|
|
|
int? get _currentManyToOneBoxNo {
|
|
return boxing_calculations.currentManyToOneBoxNo(_boxNoController.text);
|
|
}
|
|
|
|
List<ManyToOnePackedItem> get _visibleManyToOnePackedItems {
|
|
return boxing_calculations.visibleManyToOnePackedItems(
|
|
boxNo: _currentManyToOneBoxNo,
|
|
existingBoxes: _existingBoxes,
|
|
packedItems: _manyToOnePackedItems,
|
|
paichanNo: _paichanNo,
|
|
);
|
|
}
|
|
|
|
Future<bool> _submit() async {
|
|
if (!_canSubmit) return false;
|
|
|
|
final configService = AppConfigService();
|
|
final baseUrl = await configService.getString('api_url') ?? '';
|
|
if (baseUrl.isEmpty) {
|
|
_feedbackService.trigger(FeedbackEvent.submitFailure);
|
|
_showStatusOverride(
|
|
'未配置 API 地址,请前往设置',
|
|
StatusDotColor.red,
|
|
const Duration(seconds: 2),
|
|
);
|
|
return false;
|
|
}
|
|
|
|
final boxNo = int.parse(_boxNoController.text);
|
|
final quantity = int.parse(_quantityController.text);
|
|
|
|
setState(() => _isSubmitting = true);
|
|
|
|
final editing = _editingBox;
|
|
final result = editing == null
|
|
? await _apiService.saveBoxRecord(
|
|
baseUrl: baseUrl,
|
|
zongpaiNo: _zongpaiNo!,
|
|
boxNo: boxNo,
|
|
quantity: quantity,
|
|
)
|
|
: await _apiService.updateBoxRecord(
|
|
baseUrl: baseUrl,
|
|
boxItemId: editing.boxItemId,
|
|
boxNo: boxNo,
|
|
quantity: quantity,
|
|
);
|
|
|
|
if (!mounted) return false;
|
|
|
|
setState(() => _isSubmitting = false);
|
|
|
|
if (result.success) {
|
|
if (editing == null) {
|
|
_onSubmitSuccess(boxNo, quantity, result.boxItemId);
|
|
} else {
|
|
_onUpdateSuccess(editing, boxNo, quantity);
|
|
}
|
|
return true;
|
|
} else if (result.isDuplicate) {
|
|
_feedbackService.trigger(FeedbackEvent.duplicateBoxNo);
|
|
_showStatusOverride(
|
|
'该总排号在箱号 ${result.boxNo ?? ""} 已存在,请重新输入',
|
|
StatusDotColor.amber,
|
|
const Duration(seconds: 2),
|
|
);
|
|
return false;
|
|
} else {
|
|
final isNetwork = result.errorMessage == '网络异常,请检查网络连接';
|
|
_feedbackService.trigger(
|
|
isNetwork ? FeedbackEvent.networkError : FeedbackEvent.submitFailure,
|
|
);
|
|
_showStatusOverride(
|
|
result.errorMessage ?? '提交失败',
|
|
isNetwork ? StatusDotColor.yellow : StatusDotColor.red,
|
|
const Duration(seconds: 2),
|
|
);
|
|
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<void> _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;
|
|
}
|
|
|
|
final configService = AppConfigService();
|
|
final baseUrl = await configService.getString('api_url') ?? '';
|
|
if (baseUrl.isEmpty) {
|
|
_showStatusOverride(
|
|
'未配置 API 地址,请前往设置',
|
|
StatusDotColor.red,
|
|
const Duration(seconds: 2),
|
|
);
|
|
return;
|
|
}
|
|
|
|
setState(() => _isSubmitting = true);
|
|
final result = await _apiService.updateBoxRecord(
|
|
baseUrl: baseUrl,
|
|
boxItemId: item.boxItemId,
|
|
boxNo: item.boxNo,
|
|
quantity: quantity,
|
|
);
|
|
if (!mounted) return;
|
|
|
|
setState(() => _isSubmitting = false);
|
|
if (result.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 {
|
|
final dot = result.isDuplicate
|
|
? StatusDotColor.amber
|
|
: StatusDotColor.red;
|
|
_showStatusOverride(
|
|
result.errorMessage ?? '修改失败',
|
|
dot,
|
|
const Duration(seconds: 2),
|
|
);
|
|
}
|
|
}
|
|
|
|
Future<void> _deleteAssignedItem(CurrentZongpaiBoxData item) async {
|
|
final configService = AppConfigService();
|
|
final baseUrl = await configService.getString('api_url') ?? '';
|
|
if (baseUrl.isEmpty) {
|
|
_showStatusOverride(
|
|
'未配置 API 地址,请前往设置',
|
|
StatusDotColor.red,
|
|
const Duration(seconds: 2),
|
|
);
|
|
return;
|
|
}
|
|
|
|
setState(() => _isSubmitting = true);
|
|
final result = await _apiService.deleteBoxRecord(
|
|
baseUrl: baseUrl,
|
|
boxItemId: item.boxItemId,
|
|
);
|
|
if (!mounted) return;
|
|
|
|
setState(() {
|
|
_isSubmitting = false;
|
|
if (result.success) {
|
|
_currentZongpaiBoxes = _currentZongpaiBoxes
|
|
.where((box) => box.boxItemId != item.boxItemId)
|
|
.toList();
|
|
_removeExistingBoxItem(item.boxItemId);
|
|
if (_editingAssignedItemId == item.boxItemId) {
|
|
_editingAssignedItemId = null;
|
|
_editingAssignedQuantity = '';
|
|
}
|
|
_deletingAssignedItemId = null;
|
|
_refreshSingleCodeNewInput();
|
|
}
|
|
});
|
|
|
|
if (result.success) {
|
|
_showStatusOverride(
|
|
'删除成功',
|
|
StatusDotColor.green,
|
|
const Duration(milliseconds: 1500),
|
|
);
|
|
} else {
|
|
_showStatusOverride(
|
|
result.errorMessage ?? '删除失败',
|
|
StatusDotColor.red,
|
|
const Duration(seconds: 2),
|
|
);
|
|
}
|
|
}
|
|
|
|
void _startEditPackedItem(ManyToOnePackedItem item) {
|
|
setState(() {
|
|
_editingPackedItemId = item.boxItemId;
|
|
_editingPackedQuantity = item.quantity.toString();
|
|
_deletingPackedItemId = null;
|
|
});
|
|
}
|
|
|
|
void _cancelEditPackedItem() {
|
|
setState(() {
|
|
_editingPackedItemId = null;
|
|
_editingPackedQuantity = '';
|
|
});
|
|
}
|
|
|
|
Future<void> _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;
|
|
}
|
|
|
|
final configService = AppConfigService();
|
|
final baseUrl = await configService.getString('api_url') ?? '';
|
|
if (baseUrl.isEmpty) {
|
|
_showStatusOverride(
|
|
'未配置 API 地址,请前往设置',
|
|
StatusDotColor.red,
|
|
const Duration(seconds: 2),
|
|
);
|
|
return;
|
|
}
|
|
|
|
setState(() => _isSubmitting = true);
|
|
final result = await _apiService.updateBoxRecord(
|
|
baseUrl: baseUrl,
|
|
boxItemId: item.boxItemId,
|
|
boxNo: boxNo,
|
|
quantity: quantity,
|
|
);
|
|
if (!mounted) return;
|
|
|
|
setState(() => _isSubmitting = false);
|
|
if (result.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 {
|
|
final dot = result.isDuplicate
|
|
? StatusDotColor.amber
|
|
: StatusDotColor.red;
|
|
_showStatusOverride(
|
|
result.errorMessage ?? '修改失败',
|
|
dot,
|
|
const Duration(seconds: 2),
|
|
);
|
|
}
|
|
}
|
|
|
|
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<void> _deletePackedItem(ManyToOnePackedItem item) async {
|
|
final configService = AppConfigService();
|
|
final baseUrl = await configService.getString('api_url') ?? '';
|
|
if (baseUrl.isEmpty) {
|
|
_showStatusOverride(
|
|
'未配置 API 地址,请前往设置',
|
|
StatusDotColor.red,
|
|
const Duration(seconds: 2),
|
|
);
|
|
return;
|
|
}
|
|
|
|
setState(() => _isSubmitting = true);
|
|
final result = await _apiService.deleteBoxRecord(
|
|
baseUrl: baseUrl,
|
|
boxItemId: item.boxItemId,
|
|
);
|
|
if (!mounted) return;
|
|
|
|
setState(() {
|
|
_isSubmitting = false;
|
|
if (result.success) {
|
|
_manyToOnePackedItems.removeWhere(
|
|
(packed) => packed.boxItemId == item.boxItemId,
|
|
);
|
|
_removeExistingBoxItem(item.boxItemId);
|
|
if (_editingPackedItemId == item.boxItemId) {
|
|
_editingPackedItemId = null;
|
|
_editingPackedQuantity = '';
|
|
}
|
|
_deletingPackedItemId = null;
|
|
}
|
|
});
|
|
|
|
if (result.success) {
|
|
_showStatusOverride(
|
|
'删除成功',
|
|
StatusDotColor.green,
|
|
const Duration(milliseconds: 1500),
|
|
);
|
|
} else {
|
|
_showStatusOverride(
|
|
result.errorMessage ?? '删除失败',
|
|
StatusDotColor.red,
|
|
const Duration(seconds: 2),
|
|
);
|
|
}
|
|
}
|
|
|
|
void _removeExistingBoxItem(int boxItemId) {
|
|
final result = box_mutations.removeExistingBoxItem(
|
|
existingBoxes: _existingBoxes,
|
|
boxItemId: boxItemId,
|
|
);
|
|
_existingBoxes = result.boxes;
|
|
_maxBoxNo = result.maxBoxNo;
|
|
}
|
|
|
|
// === Status bar management ===
|
|
|
|
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();
|
|
final effectiveDot = _statusOverrideDot ?? _statusDot;
|
|
final effectiveText = _statusOverrideText ?? _statusText;
|
|
|
|
return KeyboardListener(
|
|
focusNode: _focusNode,
|
|
onKeyEvent: _onKeyEvent,
|
|
child: Scaffold(
|
|
appBar: AppBar(
|
|
title: Row(
|
|
children: [
|
|
const Text('装箱编号'),
|
|
const SizedBox(width: 8),
|
|
BoxingModePill(
|
|
isSingle: _mode == BoxingMode.singleCode,
|
|
label: _modeLabel,
|
|
enabled: !_isAutoProcessing,
|
|
onTap: _cycleMode,
|
|
),
|
|
],
|
|
),
|
|
toolbarHeight: 44,
|
|
titleSpacing: 12,
|
|
),
|
|
body: Column(
|
|
children: [
|
|
BoxingNoticeBanners(
|
|
isAutoProcessing: _isAutoProcessing,
|
|
autoWarnings: _autoWarnings,
|
|
paichanSwitchNotice: _paichanSwitchNotice,
|
|
isDuplicateBoxNo: _isDuplicateBoxNo,
|
|
quantityTooHigh: _quantityTooHigh,
|
|
phase: _phase,
|
|
boxNoText: _boxNoController.text,
|
|
remainingQuantity: _remainingQuantity,
|
|
zongpaiNo: _zongpaiNo,
|
|
completedJumpBoxNo: _completedJumpBoxNo,
|
|
onJumpToCompletedBox: _jumpToCompletedBox,
|
|
),
|
|
|
|
Expanded(
|
|
child: _mode == BoxingMode.multiCode
|
|
? MultiCodeBody(
|
|
hasScan:
|
|
_zongpaiNo != null && _phase == BoxingPhase.scanned,
|
|
zongpaiNo: _zongpaiNo,
|
|
paichanNo: _paichanNo,
|
|
workOrderNo: _workOrderNo,
|
|
erpQuantity: _erpQuantity,
|
|
maxBoxNo: _maxBoxNo,
|
|
existingBoxes: _existingBoxes,
|
|
visibleItems: _visibleManyToOnePackedItems,
|
|
boxNoController: _boxNoController,
|
|
quantityController: _quantityController,
|
|
boxNoFocusNode: _boxNoFocusNode,
|
|
quantityFocusNode: _quantityFocusNode,
|
|
isSubmitting: _isSubmitting,
|
|
quantityTooHigh: _quantityTooHigh,
|
|
canSubmit: _canSubmit,
|
|
editingPackedItemId: _editingPackedItemId,
|
|
editingPackedQuantity: _editingPackedQuantity,
|
|
deletingPackedItemId: _deletingPackedItemId,
|
|
onOpenDetail: _openDetail,
|
|
onSubmitFromKeyboard: _submitFromKeyboard,
|
|
onSubmit: _submit,
|
|
onQuantityChanged: () => setState(() {}),
|
|
onFinishBox: _finishManyToOneBox,
|
|
onEditingPackedQuantityChanged: (value) =>
|
|
_editingPackedQuantity = value,
|
|
onSavePackedItem: _savePackedItem,
|
|
onCancelEditPackedItem: _cancelEditPackedItem,
|
|
onStartEditPackedItem: _startEditPackedItem,
|
|
onStartDeletePackedItem: (item) {
|
|
setState(() {
|
|
_deletingPackedItemId = item.boxItemId;
|
|
_editingPackedItemId = null;
|
|
_editingPackedQuantity = '';
|
|
});
|
|
},
|
|
onDeletePackedItem: _deletePackedItem,
|
|
onCancelDeletePackedItem: () =>
|
|
setState(() => _deletingPackedItemId = null),
|
|
)
|
|
: SingleCodeBody(
|
|
isWaiting:
|
|
_phase == BoxingPhase.waiting && _zongpaiNo == null,
|
|
isFinished:
|
|
_phase == BoxingPhase.scanned &&
|
|
_zongpaiNo != null &&
|
|
_remainingQuantity <= 0,
|
|
zongpaiNo: _zongpaiNo,
|
|
paichanNo: _paichanNo,
|
|
workOrderNo: _workOrderNo,
|
|
erpQuantity: _erpQuantity,
|
|
packedQuantity: _packedQuantity,
|
|
maxBoxNo: _maxBoxNo,
|
|
assignedItems: _currentZongpaiBoxes,
|
|
existingBoxes: _existingBoxes,
|
|
boxNoController: _boxNoController,
|
|
quantityController: _quantityController,
|
|
boxNoFocusNode: _boxNoFocusNode,
|
|
quantityFocusNode: _quantityFocusNode,
|
|
isSubmitting: _isSubmitting,
|
|
isDuplicateBoxNo: _isDuplicateBoxNo,
|
|
quantityTooHigh: _quantityTooHigh,
|
|
canSubmit: _canSubmit,
|
|
editingAssignedItemId: _editingAssignedItemId,
|
|
editingAssignedQuantity: _editingAssignedQuantity,
|
|
deletingAssignedItemId: _deletingAssignedItemId,
|
|
onOpenDetail: _openDetail,
|
|
onCheckDuplicateBoxNo: _checkDuplicateBoxNo,
|
|
onSubmitFromKeyboard: _submitFromKeyboard,
|
|
onSubmit: _submit,
|
|
onQuantityChanged: () => setState(() {}),
|
|
onEditingAssignedQuantityChanged: (value) =>
|
|
_editingAssignedQuantity = value,
|
|
onSaveAssignedItem: _saveAssignedItem,
|
|
onCancelEditAssigned: _cancelEditAssigned,
|
|
onStartEditAssigned: _startEditAssigned,
|
|
onStartDeleteAssigned: (item) {
|
|
setState(() {
|
|
_deletingAssignedItemId = item.boxItemId;
|
|
_editingAssignedItemId = null;
|
|
_editingAssignedQuantity = '';
|
|
});
|
|
},
|
|
onDeleteAssignedItem: _deleteAssignedItem,
|
|
onCancelDeleteAssigned: () =>
|
|
setState(() => _deletingAssignedItemId = null),
|
|
),
|
|
),
|
|
|
|
// 底部状态栏
|
|
StatusBar(dotColor: effectiveDot, text: effectiveText),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|