feat(android): add attachment config dialog and FJ- scan in registration

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Misaka_Company
2026-08-12 14:56:28 +08:00
parent b7484915c6
commit 6b05d6c0e3
6 changed files with 503 additions and 1 deletions

View File

@@ -0,0 +1,185 @@
// ignore_for_file: invalid_use_of_protected_member
part of '../../registration_page.dart';
extension _RegistrationAttachmentPart on _RegistrationPageState {
/// Handle FJ- attachment scan: pick category, then register location.
Future<void> _handleAttachmentScan(String zongpaiNo) async {
if (_crossPaichaPending) {
_triggerDoubleVibration();
return;
}
final baseUrl = await _baseUrl();
if (baseUrl == null || !mounted) return;
// 1. Fetch categories (cache in the page state)
final catResult = await _apiService.fetchAttachmentCategories(
baseUrl: baseUrl,
);
if (!catResult.success || !mounted) {
_feedbackService.trigger(FeedbackEvent.scanInvalid);
_showStatusOverride(
catResult.errorMessage ?? '加载附件类型失败',
StatusDotColor.red,
const Duration(seconds: 2),
);
return;
}
final categories = catResult.categories;
// 2. Pick category (single choice)
final picked = await showAttachmentCategoryPicker(
context: context,
categories: categories,
);
if (picked == null || !mounted) return;
// 3. Now treat this zongpai as "scanned" with the picked category
// For the registration flow: append scanned zongpai (for location binding)
// and store category context for the submit path
_feedbackService.trigger(FeedbackEvent.scanValid);
setState(() {
if (_mode == RegistrationMode.multiCode) {
if (!_zongpaiNos.contains(zongpaiNo)) {
_zongpaiNos.add(zongpaiNo);
}
} else {
if (_zongpaiNos.isEmpty) {
_zongpaiNos.add(zongpaiNo);
} else {
_zongpaiNos[0] = zongpaiNo;
}
}
// Store the last-scanned attachment category for submit
_pendingAttachmentCategory = picked.id;
_clearStatusOverride();
});
// 4. Refresh the overview (the zongpai is now a product scan for display,
// attachment status will be shown via a separate banner/tag)
final shouldRefresh = _overviewZongpaiNo != zongpaiNo;
if (shouldRefresh) {
_loadOverview(zongpaiNo);
}
}
/// Handle a product scan with attachment-config integration.
/// Called from the existing _handleZongpaiScan when a product code is scanned.
Future<void> _checkAndConfigureAttachment(String zongpaiNo) async {
final baseUrl = await _baseUrl();
if (baseUrl == null || !mounted) return;
// Fetch attachment status for this zongpai
final status = await _apiService.fetchAttachmentStatus(
baseUrl: baseUrl,
zongpaiNo: zongpaiNo,
);
if (!mounted) return;
if (!status.success) {
// Network error or other — defer to product scan flow without blocking
return;
}
if (status.determination == 'none') {
// Already determined none — nothing to configure
return;
}
if (status.determination == 'undetermined' ||
status.determination == 'has') {
// Fetch categories for the dialog
final catResult = await _apiService.fetchAttachmentCategories(
baseUrl: baseUrl,
);
if (!catResult.success || !mounted) return;
// Fetch ERP quantity for default expected_qty
final overview = _overview;
final erpQty = overview?.success == true
? overview!.items
.where((i) => i.zongpaiNo == zongpaiNo)
.fold<int>(0, (sum, i) => sum + i.quantity)
: 0;
final result = await showAttachmentConfigDialog(
context: context,
zongpaiNo: zongpaiNo,
categories: catResult.categories,
prefillItems: status.items,
erpQuantity: erpQty > 0 ? erpQty : 1,
);
if (result == null || !mounted) return;
// Submit config
final configResult = await _apiService.putAttachmentConfig(
baseUrl: baseUrl,
zongpaiNo: zongpaiNo,
determination: result.determination,
items: result.items,
);
if (!mounted) return;
if (!configResult.success) {
_feedbackService.trigger(FeedbackEvent.submitFailure);
_showStatusOverride(
configResult.errorMessage ?? '配置失败',
StatusDotColor.red,
const Duration(seconds: 2),
);
}
}
}
/// Submit attachment location for the given zongpai.
Future<bool> _submitAttachmentLocation(
String baseUrl,
String zongpaiNo,
int categoryId,
) async {
final result = await _apiService.registerAttachmentLocation(
baseUrl: baseUrl,
zongpaiNo: zongpaiNo,
categoryId: categoryId,
locationCode: _locationCode!,
);
if (!mounted) return false;
if (result.success) {
return true;
} else if (result.isDuplicate) {
_feedbackService.trigger(FeedbackEvent.duplicateError);
if (result.conflictInfo != null) {
final msg =
'附件 $zongpaiNo 已上架至 ${result.conflictInfo!['location_code']}';
_showStatusOverride(
msg,
StatusDotColor.red,
const Duration(seconds: 2),
);
}
return false;
} else if (result.isAlreadyOffShelf) {
_feedbackService.trigger(FeedbackEvent.submitFailure);
_showStatusOverride(
result.errorMessage ?? '该附件已下架至转运区域,不可重新上架',
StatusDotColor.red,
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;
}
}
}