feat(android): add attachment boxing FJ- scan and submit

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Misaka_Company
2026-08-12 15:02:00 +08:00
parent 6b05d6c0e3
commit 15c29bceb8
5 changed files with 351 additions and 0 deletions

View File

@@ -15,6 +15,11 @@ extension _BoxingScanPart on _BoxingPageState {
final parsed = CodeParser.parse(result.barcode);
if (parsed.type == CodeType.attachmentCode) {
this._handleAttachmentBoxScan(parsed.value);
return;
}
if (parsed.type != CodeType.zongpaiNo) {
_feedbackService.trigger(FeedbackEvent.scanInvalid);
this._showStatusOverride(
@@ -92,6 +97,119 @@ extension _BoxingScanPart on _BoxingPageState {
return true;
}
Future<void> _handleAttachmentBoxScan(String zongpai) async {
if (_isAutoProcessing || _crossPaichaPending) return;
// 1. Fetch categories
final catAction = await _apiActions.fetchAttachmentCategories();
if (!mounted) return;
if (!catAction.success) {
_feedbackService.trigger(FeedbackEvent.scanInvalid);
_showStatusOverride(
catAction.errorMessage ?? '加载附件类型失败',
StatusDotColor.red,
const Duration(seconds: 2),
);
return;
}
// 2. Pick category
final picked = await showBoxingAttachmentCategoryPicker(
context: context,
categories: catAction.data!.categories,
);
if (picked == null || !mounted) return;
setState(() {
_attachmentCategoryId = picked.id;
_attachmentCategoryName = picked.name;
});
// 3. Fetch box info (server-extended: includes attachment_summary)
final action = await _apiActions.fetchBoxInfo(zongpai);
if (!mounted) return;
if (!action.success) {
_feedbackService.trigger(switch (action.errorKind) {
BoxingActionErrorKind.network => FeedbackEvent.networkError,
BoxingActionErrorKind.missingApiUrl => FeedbackEvent.submitFailure,
_ => FeedbackEvent.scanInvalid,
});
_showStatusOverride(
action.errorMessage ?? '查询失败',
_dotForActionError(action.errorKind),
const Duration(seconds: 2),
);
return;
}
final result = action.data!;
final totalQuantity = result.quantity ?? 0;
// 4. Derive attachment qty context from the box-info attachment_summary
// or from a separate status call
final baseUrl = await _baseUrl();
int expectedQty = totalQuantity;
int boxedQty = 0;
if (baseUrl != null) {
final statusResult = await _apiService.fetchAttachmentStatus(
baseUrl: baseUrl,
zongpaiNo: zongpai,
);
if (statusResult.success) {
for (final item in statusResult.items) {
if (item.categoryId == _attachmentCategoryId) {
expectedQty = item.expectedQty;
boxedQty = item.boxedQty;
break;
}
}
}
}
final remaining = expectedQty - boxedQty;
final alreadyCompleted = remaining <= 0;
_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;
_attachmentExpectedQty = expectedQty;
_attachmentBoxedQty = boxedQty;
_phase = BoxingPhase.scanned;
_isDuplicateBoxNo = false;
_editingBox = null;
_completedJumpBoxNo = null;
_statusOverrideText = null;
});
// Auto-fill: box_no = maxBoxNo+1, quantity = remaining
if (alreadyCompleted) {
_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,
);
}
Future<void> _processAutoScanCodes(List<String> codes) async {
if (!mounted) return;
setState(() {

View File

@@ -6,6 +6,53 @@ extension _BoxingSubmitPart on _BoxingPageState {
Future<bool> _submit() async {
if (!_canSubmit) return false;
final attachCatId = _attachmentCategoryId;
if (attachCatId != null) {
// Attachment boxing submit
setState(() => _isSubmitting = true);
final action = await _apiActions.saveAttachmentBox(
zongpaiNo: _zongpaiNo!,
categoryId: attachCatId,
boxNo: int.parse(_boxNoController.text),
quantity: int.parse(_quantityController.text),
);
if (!mounted) return false;
setState(() => _isSubmitting = false);
if (action.success) {
_feedbackService.trigger(FeedbackEvent.submitSuccess);
// Reset to waiting for next scan
setState(() {
_zongpaiNo = null;
_phase = BoxingPhase.waiting;
_attachmentCategoryId = null;
_attachmentCategoryName = null;
});
_showStatusOverride(
'附件装箱成功',
StatusDotColor.green,
const Duration(milliseconds: 1500),
);
_requestFocus();
return true;
} else if (action.errorKind == BoxingActionErrorKind.duplicate) {
_feedbackService.trigger(FeedbackEvent.duplicateBoxNo);
_showStatusOverride(
action.errorMessage ?? '该箱号已存在同类型附件',
StatusDotColor.red,
const Duration(seconds: 2),
);
} else {
_feedbackService.trigger(FeedbackEvent.submitFailure);
_showStatusOverride(
action.errorMessage ?? '提交失败',
StatusDotColor.red,
const Duration(seconds: 2),
);
}
return false;
}
final boxNo = int.parse(_boxNoController.text);
final quantity = int.parse(_quantityController.text);