feat(boxing): support editing assigned box records
This commit is contained in:
@@ -61,14 +61,37 @@ class BoxingDetailPage extends StatelessWidget {
|
||||
}
|
||||
|
||||
Widget _buildBoxGroup(BuildContext context, BoxDetailData box) {
|
||||
final boxTotalQuantity = box.items.fold<int>(
|
||||
0,
|
||||
(sum, item) => sum + item.quantity,
|
||||
);
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
'箱号 ${box.boxNo}',
|
||||
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'共 $boxTotalQuantity 件',
|
||||
textAlign: TextAlign.right,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Container(
|
||||
|
||||
@@ -102,6 +102,9 @@ class _BoxingPageState extends State<BoxingPage> {
|
||||
int? _editingPackedItemId;
|
||||
String _editingPackedQuantity = '';
|
||||
int? _deletingPackedItemId;
|
||||
int? _editingAssignedItemId;
|
||||
String _editingAssignedQuantity = '';
|
||||
int? _deletingAssignedItemId;
|
||||
String? _lastScannedPaichanNo;
|
||||
String? _paichanSwitchNotice;
|
||||
|
||||
@@ -187,6 +190,9 @@ class _BoxingPageState extends State<BoxingPage> {
|
||||
_editingPackedItemId = null;
|
||||
_editingPackedQuantity = '';
|
||||
_deletingPackedItemId = null;
|
||||
_editingAssignedItemId = null;
|
||||
_editingAssignedQuantity = '';
|
||||
_deletingAssignedItemId = null;
|
||||
_lastScannedPaichanNo = null;
|
||||
_paichanSwitchNotice = null;
|
||||
_isSubmitting = false;
|
||||
@@ -264,6 +270,9 @@ class _BoxingPageState extends State<BoxingPage> {
|
||||
_phase = _Phase.scanned;
|
||||
_isDuplicateBoxNo = false;
|
||||
_editingBox = null;
|
||||
_editingAssignedItemId = null;
|
||||
_editingAssignedQuantity = '';
|
||||
_deletingAssignedItemId = null;
|
||||
_statusOverrideText = null;
|
||||
if (_mode == BoxingMode.singleCode) {
|
||||
_paichanSwitchNotice = null;
|
||||
@@ -322,7 +331,14 @@ class _BoxingPageState extends State<BoxingPage> {
|
||||
_boxNoController.text = boxNoToApply.toString();
|
||||
}
|
||||
_lastScannedPaichanNo = _paichanNo;
|
||||
_quantityController.text = (_erpQuantity ?? 0).toString();
|
||||
final remaining = _remainingQuantity;
|
||||
if (remaining <= 0) {
|
||||
_quantityController.clear();
|
||||
_statusOverrideText = '该总排号已全部装箱完毕';
|
||||
_statusOverrideDot = StatusDotColor.red;
|
||||
return;
|
||||
}
|
||||
_quantityController.text = remaining.toString();
|
||||
_quantityController.selection = TextSelection(
|
||||
baseOffset: 0,
|
||||
extentOffset: _quantityController.text.length,
|
||||
@@ -386,6 +402,23 @@ class _BoxingPageState extends State<BoxingPage> {
|
||||
return true;
|
||||
}
|
||||
|
||||
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 {
|
||||
final boxNo = int.tryParse(_boxNoController.text.trim());
|
||||
if (boxNo == null || boxNo <= 0) return null;
|
||||
@@ -526,6 +559,7 @@ class _BoxingPageState extends State<BoxingPage> {
|
||||
);
|
||||
_isDuplicateBoxNo = false;
|
||||
});
|
||||
_focusQuantityInput();
|
||||
_showStatusOverride(
|
||||
'请继续完成剩余数量装箱',
|
||||
StatusDotColor.green,
|
||||
@@ -687,11 +721,9 @@ class _BoxingPageState extends State<BoxingPage> {
|
||||
|
||||
void _startEditAssigned(CurrentZongpaiBoxData item) {
|
||||
setState(() {
|
||||
_editingBox = item;
|
||||
_phase = _Phase.scanned;
|
||||
_boxNoController.text = item.boxNo.toString();
|
||||
_quantityController.text = item.quantity.toString();
|
||||
_isDuplicateBoxNo = false;
|
||||
_editingAssignedItemId = item.boxItemId;
|
||||
_editingAssignedQuantity = item.quantity.toString();
|
||||
_deletingAssignedItemId = null;
|
||||
_statusOverrideText = null;
|
||||
_statusOverrideDot = null;
|
||||
});
|
||||
@@ -699,13 +731,158 @@ class _BoxingPageState extends State<BoxingPage> {
|
||||
|
||||
void _cancelEditAssigned() {
|
||||
setState(() {
|
||||
_editingBox = null;
|
||||
_boxNoController.text = ((_lastBoxNo ?? _maxBoxNo) + 1).toString();
|
||||
_quantityController.clear();
|
||||
_isDuplicateBoxNo = false;
|
||||
_editingAssignedItemId = null;
|
||||
_editingAssignedQuantity = '';
|
||||
});
|
||||
}
|
||||
|
||||
int _maxAssignedQuantity(CurrentZongpaiBoxData item) {
|
||||
final total = _erpQuantity ?? 0;
|
||||
final packedWithoutItem = _packedQuantity - item.quantity;
|
||||
return total - packedWithoutItem;
|
||||
}
|
||||
|
||||
void _refreshSingleCodeNewInput({bool focusQuantity = true}) {
|
||||
_lastBoxNo = _maxBoxNo;
|
||||
_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;
|
||||
@@ -1123,43 +1300,132 @@ class _BoxingPageState extends State<BoxingPage> {
|
||||
style: TextStyle(fontSize: 13, color: Colors.black54),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
..._currentZongpaiBoxes.map((item) {
|
||||
final editing = _editingBox?.boxItemId == item.boxItemId;
|
||||
..._currentZongpaiBoxes.map(_buildAssignedBoxRow),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildAssignedBoxRow(CurrentZongpaiBoxData item) {
|
||||
final editing = _editingAssignedItemId == item.boxItemId;
|
||||
final deleting = _deletingAssignedItemId == item.boxItemId;
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: 6),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: editing ? Colors.blue.shade50 : Colors.grey.shade50,
|
||||
color: editing ? Colors.orange.shade50 : Colors.grey.shade50,
|
||||
border: Border.all(
|
||||
color: editing ? Colors.blue : Colors.grey.shade300,
|
||||
color: editing ? Colors.orange : Colors.grey.shade300,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: ListTile(
|
||||
dense: true,
|
||||
contentPadding: const EdgeInsets.only(left: 10, right: 4),
|
||||
onTap: () => _startEditAssigned(item),
|
||||
title: Text(
|
||||
'${item.boxNo}号箱 / 数量 ${item.quantity}',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: editing ? FontWeight.w700 : FontWeight.w500,
|
||||
),
|
||||
),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
'${item.boxNo}号箱',
|
||||
style: const TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
if (editing)
|
||||
SizedBox(
|
||||
width: 56,
|
||||
height: 32,
|
||||
child: TextField(
|
||||
keyboardType: TextInputType.none,
|
||||
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
||||
textAlign: TextAlign.center,
|
||||
controller:
|
||||
TextEditingController(text: _editingAssignedQuantity)
|
||||
..selection = TextSelection.collapsed(
|
||||
offset: _editingAssignedQuantity.length,
|
||||
),
|
||||
onChanged: (value) => _editingAssignedQuantity = value,
|
||||
onEditingComplete: () {},
|
||||
onSubmitted: (_) => _saveAssignedItem(item),
|
||||
decoration: const InputDecoration(
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: 6),
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
)
|
||||
else
|
||||
Text(
|
||||
'数量 ${item.quantity}',
|
||||
style: const TextStyle(fontSize: 13),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
if (editing) ...[
|
||||
IconButton(
|
||||
tooltip: '保存',
|
||||
visualDensity: VisualDensity.compact,
|
||||
icon: const Icon(Icons.check, size: 18),
|
||||
onPressed: _isSubmitting
|
||||
? null
|
||||
: () => _saveAssignedItem(item),
|
||||
),
|
||||
IconButton(
|
||||
tooltip: '取消',
|
||||
visualDensity: VisualDensity.compact,
|
||||
icon: const Icon(Icons.close, size: 18),
|
||||
onPressed: _isSubmitting ? null : _cancelEditAssigned,
|
||||
),
|
||||
] else ...[
|
||||
IconButton(
|
||||
tooltip: '修改',
|
||||
visualDensity: VisualDensity.compact,
|
||||
icon: const Icon(Icons.edit, size: 18),
|
||||
onPressed: () => _startEditAssigned(item),
|
||||
onPressed: _isSubmitting
|
||||
? null
|
||||
: () => _startEditAssigned(item),
|
||||
),
|
||||
IconButton(
|
||||
tooltip: '删除',
|
||||
visualDensity: VisualDensity.compact,
|
||||
icon: const Icon(Icons.delete_outline, size: 18),
|
||||
onPressed: _isSubmitting
|
||||
? null
|
||||
: () {
|
||||
setState(() {
|
||||
_deletingAssignedItemId = item.boxItemId;
|
||||
_editingAssignedItemId = null;
|
||||
_editingAssignedQuantity = '';
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
if (deleting)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 4),
|
||||
child: Row(
|
||||
children: [
|
||||
const Expanded(
|
||||
child: Text('确认删除此条记录?', style: TextStyle(fontSize: 13)),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: _isSubmitting
|
||||
? null
|
||||
: () => _deleteAssignedItem(item),
|
||||
child: const Text('是'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: _isSubmitting
|
||||
? null
|
||||
: () => setState(() => _deletingAssignedItemId = null),
|
||||
child: const Text('否'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1206,6 +1472,8 @@ class _BoxingPageState extends State<BoxingPage> {
|
||||
keyboardType: TextInputType.none,
|
||||
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
||||
textAlign: TextAlign.center,
|
||||
onEditingComplete: () {},
|
||||
onSubmitted: (_) => _submitFromKeyboard(),
|
||||
decoration: InputDecoration(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
border: OutlineInputBorder(
|
||||
@@ -1474,6 +1742,8 @@ class _BoxingPageState extends State<BoxingPage> {
|
||||
keyboardType: TextInputType.none,
|
||||
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
||||
onChanged: (_) => setState(() {}),
|
||||
onEditingComplete: () {},
|
||||
onSubmitted: (_) => _submitFromKeyboard(),
|
||||
decoration: InputDecoration(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
border: const OutlineInputBorder(),
|
||||
@@ -1722,6 +1992,8 @@ class _BoxingPageState extends State<BoxingPage> {
|
||||
keyboardType: TextInputType.none,
|
||||
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
||||
onChanged: (_) => _checkDuplicateBoxNo(),
|
||||
onEditingComplete: () {},
|
||||
onSubmitted: (_) => _submitFromKeyboard(),
|
||||
decoration: InputDecoration(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
border: const OutlineInputBorder(),
|
||||
@@ -1763,6 +2035,8 @@ class _BoxingPageState extends State<BoxingPage> {
|
||||
keyboardType: TextInputType.none,
|
||||
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
||||
onChanged: (_) => setState(() {}),
|
||||
onEditingComplete: () {},
|
||||
onSubmitted: (_) => _submitFromKeyboard(),
|
||||
decoration: InputDecoration(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
border: const OutlineInputBorder(),
|
||||
|
||||
Reference in New Issue
Block a user