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