Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
99ed3bb9ec |
@@ -97,6 +97,7 @@ extension _RegistrationDialogsPart on _RegistrationPageState {
|
|||||||
void _showLocationConflictDialog({
|
void _showLocationConflictDialog({
|
||||||
required List<PaichaOverviewItem> onShelfItems,
|
required List<PaichaOverviewItem> onShelfItems,
|
||||||
required List<PaichaOverviewItem> transferredItems,
|
required List<PaichaOverviewItem> transferredItems,
|
||||||
|
List<PaichaOverviewItem> tempStoredItems = const [],
|
||||||
}) {
|
}) {
|
||||||
final contentParts = <Widget>[];
|
final contentParts = <Widget>[];
|
||||||
|
|
||||||
@@ -142,6 +143,29 @@ extension _RegistrationDialogsPart on _RegistrationPageState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (tempStoredItems.isNotEmpty) {
|
||||||
|
if (onShelfItems.isNotEmpty || transferredItems.isNotEmpty) {
|
||||||
|
contentParts.add(const Divider());
|
||||||
|
contentParts.add(const SizedBox(height: 4));
|
||||||
|
}
|
||||||
|
contentParts.add(
|
||||||
|
Text(
|
||||||
|
'暂存中',
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: const Color(0xFF7B1FA2),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
for (final item in tempStoredItems) {
|
||||||
|
contentParts.addAll([
|
||||||
|
Text('总排号:${item.zongpaiNo}'),
|
||||||
|
Text('暂存货位:${item.locationCode ?? "未知"}'),
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
contentParts.add(const SizedBox(height: 4));
|
contentParts.add(const SizedBox(height: 4));
|
||||||
contentParts.add(
|
contentParts.add(
|
||||||
const Text(
|
const Text(
|
||||||
|
|||||||
@@ -19,6 +19,14 @@ extension _RegistrationOverviewPart on _RegistrationPageState {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Find all scanned items that are in temp storage.
|
||||||
|
List<PaichaOverviewItem> _findTempStoredItemsInScanned() {
|
||||||
|
return registration_calculations.findTempStoredItemsInScanned(
|
||||||
|
overview: _overview,
|
||||||
|
zongpaiNos: _zongpaiNos,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Future<String?> _baseUrl() async {
|
Future<String?> _baseUrl() async {
|
||||||
final configService = AppConfigService();
|
final configService = AppConfigService();
|
||||||
final baseUrl = await configService.getString('api_url') ?? '';
|
final baseUrl = await configService.getString('api_url') ?? '';
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ extension _RegistrationScanPart on _RegistrationPageState {
|
|||||||
_handleZongpaiScan(parsed.value);
|
_handleZongpaiScan(parsed.value);
|
||||||
case CodeType.locationNormal:
|
case CodeType.locationNormal:
|
||||||
case CodeType.locationTransit:
|
case CodeType.locationTransit:
|
||||||
|
case CodeType.locationTempStorage:
|
||||||
_handleLocationScan(parsed.value, parsed.type);
|
_handleLocationScan(parsed.value, parsed.type);
|
||||||
case CodeType.invalid:
|
case CodeType.invalid:
|
||||||
_feedbackService.trigger(FeedbackEvent.scanInvalid);
|
_feedbackService.trigger(FeedbackEvent.scanInvalid);
|
||||||
|
|||||||
@@ -52,4 +52,7 @@ extension _RegistrationStatusPart on _RegistrationPageState {
|
|||||||
_zongpaiNos.isNotEmpty && _locationCode != null && !_isSubmitting;
|
_zongpaiNos.isNotEmpty && _locationCode != null && !_isSubmitting;
|
||||||
|
|
||||||
bool get _isTransitTarget => isTransitTarget(_locationType, _locationCode);
|
bool get _isTransitTarget => isTransitTarget(_locationType, _locationCode);
|
||||||
|
|
||||||
|
bool get _isTempStorageTarget =>
|
||||||
|
isTempStorageTarget(_locationType, _locationCode);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,23 @@ extension _RegistrationSubmitPart on _RegistrationPageState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For temp storage target: block if any scanned item is transferred or temp_stored
|
||||||
|
// (on_shelf items are allowed — temp storage acts as shelf change)
|
||||||
|
if (_isTempStorageTarget) {
|
||||||
|
final transferredItems = _findTransferredItemsInScanned();
|
||||||
|
final tempStoredItems = _findTempStoredItemsInScanned();
|
||||||
|
if (transferredItems.isNotEmpty || tempStoredItems.isNotEmpty) {
|
||||||
|
setState(() => _isSubmitting = false);
|
||||||
|
_feedbackService.trigger(FeedbackEvent.submitFailure);
|
||||||
|
_showLocationConflictDialog(
|
||||||
|
onShelfItems: [],
|
||||||
|
transferredItems: transferredItems,
|
||||||
|
tempStoredItems: tempStoredItems,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (_mode == RegistrationMode.multiCode && _zongpaiNos.length > 1) {
|
if (_mode == RegistrationMode.multiCode && _zongpaiNos.length > 1) {
|
||||||
if (_isTransitTarget && !await _ensureBatchSamePaicha(baseUrl)) {
|
if (_isTransitTarget && !await _ensureBatchSamePaicha(baseUrl)) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
@@ -47,7 +64,7 @@ extension _RegistrationSubmitPart on _RegistrationPageState {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Pre-check: block entire batch if any item already has a location binding
|
// Pre-check: block entire batch if any item already has a location binding
|
||||||
if (!_isTransitTarget) {
|
if (!_isTransitTarget && !_isTempStorageTarget) {
|
||||||
final onShelfItems = _findOnShelfItemsInScanned();
|
final onShelfItems = _findOnShelfItemsInScanned();
|
||||||
final transferredItems = _findTransferredItemsInScanned();
|
final transferredItems = _findTransferredItemsInScanned();
|
||||||
if (onShelfItems.isNotEmpty || transferredItems.isNotEmpty) {
|
if (onShelfItems.isNotEmpty || transferredItems.isNotEmpty) {
|
||||||
@@ -117,13 +134,15 @@ extension _RegistrationSubmitPart on _RegistrationPageState {
|
|||||||
}
|
}
|
||||||
setState(() {
|
setState(() {
|
||||||
_zongpaiNos.remove(zongpaiNo);
|
_zongpaiNos.remove(zongpaiNo);
|
||||||
if (_mode == RegistrationMode.singleCode) {
|
if (_mode == RegistrationMode.singleCode && !_isTempStorageTarget) {
|
||||||
_locationCode = null;
|
_locationCode = null;
|
||||||
_locationType = null;
|
_locationType = null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
final msg = result.isOffShelfSuccess
|
final msg = result.isOffShelfSuccess
|
||||||
? '下架成功'
|
? '下架成功'
|
||||||
|
: _isTempStorageTarget
|
||||||
|
? '变更货架成功'
|
||||||
: (_mode == RegistrationMode.multiCode ? '多码上架模式,请扫描下一张执行卡' : '上架成功');
|
: (_mode == RegistrationMode.multiCode ? '多码上架模式,请扫描下一张执行卡' : '上架成功');
|
||||||
_showStatusOverride(
|
_showStatusOverride(
|
||||||
msg,
|
msg,
|
||||||
|
|||||||
@@ -5,11 +5,13 @@ class RegistrationOverviewStats {
|
|||||||
final int totalCount;
|
final int totalCount;
|
||||||
final int shelvedCount;
|
final int shelvedCount;
|
||||||
final int transferredCount;
|
final int transferredCount;
|
||||||
|
final int tempStoredCount;
|
||||||
|
|
||||||
const RegistrationOverviewStats({
|
const RegistrationOverviewStats({
|
||||||
required this.totalCount,
|
required this.totalCount,
|
||||||
required this.shelvedCount,
|
required this.shelvedCount,
|
||||||
required this.transferredCount,
|
required this.transferredCount,
|
||||||
|
required this.tempStoredCount,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,17 +33,21 @@ RegistrationOverviewStats overviewStats(PaichaOverviewResult? overview) {
|
|||||||
totalCount: 0,
|
totalCount: 0,
|
||||||
shelvedCount: 0,
|
shelvedCount: 0,
|
||||||
transferredCount: 0,
|
transferredCount: 0,
|
||||||
|
tempStoredCount: 0,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
var shelvedCount = 0;
|
var shelvedCount = 0;
|
||||||
var transferredCount = 0;
|
var transferredCount = 0;
|
||||||
|
var tempStoredCount = 0;
|
||||||
for (final item in overview!.items) {
|
for (final item in overview!.items) {
|
||||||
switch (item.status) {
|
switch (item.status) {
|
||||||
case 'on_shelf':
|
case 'on_shelf':
|
||||||
shelvedCount++;
|
shelvedCount++;
|
||||||
case 'transferred':
|
case 'transferred':
|
||||||
transferredCount++;
|
transferredCount++;
|
||||||
|
case 'temp_stored':
|
||||||
|
tempStoredCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,6 +55,7 @@ RegistrationOverviewStats overviewStats(PaichaOverviewResult? overview) {
|
|||||||
totalCount: overview.totalCount,
|
totalCount: overview.totalCount,
|
||||||
shelvedCount: shelvedCount,
|
shelvedCount: shelvedCount,
|
||||||
transferredCount: transferredCount,
|
transferredCount: transferredCount,
|
||||||
|
tempStoredCount: tempStoredCount,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,6 +98,17 @@ List<PaichaOverviewItem> findTransferredItemsInScanned({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<PaichaOverviewItem> findTempStoredItemsInScanned({
|
||||||
|
required PaichaOverviewResult? overview,
|
||||||
|
required Iterable<String> zongpaiNos,
|
||||||
|
}) {
|
||||||
|
return _findScannedItemsByStatus(
|
||||||
|
overview: overview,
|
||||||
|
zongpaiNos: zongpaiNos,
|
||||||
|
status: 'temp_stored',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
bool hasAnyLocatedInScanned({
|
bool hasAnyLocatedInScanned({
|
||||||
required PaichaOverviewResult? overview,
|
required PaichaOverviewResult? overview,
|
||||||
required Iterable<String> zongpaiNos,
|
required Iterable<String> zongpaiNos,
|
||||||
@@ -100,7 +118,9 @@ bool hasAnyLocatedInScanned({
|
|||||||
return overview!.items.any(
|
return overview!.items.any(
|
||||||
(item) =>
|
(item) =>
|
||||||
scannedSet.contains(item.zongpaiNo) &&
|
scannedSet.contains(item.zongpaiNo) &&
|
||||||
(item.status == 'on_shelf' || item.status == 'transferred'),
|
(item.status == 'on_shelf' ||
|
||||||
|
item.status == 'transferred' ||
|
||||||
|
item.status == 'temp_stored'),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,10 +128,13 @@ Color registrationBarColor({
|
|||||||
required String status,
|
required String status,
|
||||||
required bool isScanned,
|
required bool isScanned,
|
||||||
required bool isTransitTarget,
|
required bool isTransitTarget,
|
||||||
|
bool isTempStorageTarget = false,
|
||||||
}) {
|
}) {
|
||||||
if (isScanned) {
|
if (isScanned) {
|
||||||
final isConflict = isTransitTarget
|
final isConflict = isTransitTarget
|
||||||
? status == 'transferred'
|
? status == 'transferred'
|
||||||
|
: isTempStorageTarget
|
||||||
|
? (status == 'transferred' || status == 'temp_stored')
|
||||||
: (status == 'on_shelf' || status == 'transferred');
|
: (status == 'on_shelf' || status == 'transferred');
|
||||||
return isConflict ? Colors.red : const Color(0xFF43A047);
|
return isConflict ? Colors.red : const Color(0xFF43A047);
|
||||||
}
|
}
|
||||||
@@ -119,6 +142,8 @@ Color registrationBarColor({
|
|||||||
switch (status) {
|
switch (status) {
|
||||||
case 'on_shelf':
|
case 'on_shelf':
|
||||||
return const Color(0xFF2196F3);
|
return const Color(0xFF2196F3);
|
||||||
|
case 'temp_stored':
|
||||||
|
return const Color(0xFF7B1FA2);
|
||||||
case 'transferred':
|
case 'transferred':
|
||||||
return const Color(0xFFFF9800);
|
return const Color(0xFFFF9800);
|
||||||
default:
|
default:
|
||||||
@@ -130,10 +155,13 @@ Color registrationRowBgColor({
|
|||||||
required String status,
|
required String status,
|
||||||
required bool isScanned,
|
required bool isScanned,
|
||||||
required bool isTransitTarget,
|
required bool isTransitTarget,
|
||||||
|
bool isTempStorageTarget = false,
|
||||||
}) {
|
}) {
|
||||||
if (isScanned) {
|
if (isScanned) {
|
||||||
final isConflict = isTransitTarget
|
final isConflict = isTransitTarget
|
||||||
? status == 'transferred'
|
? status == 'transferred'
|
||||||
|
: isTempStorageTarget
|
||||||
|
? (status == 'transferred' || status == 'temp_stored')
|
||||||
: (status == 'on_shelf' || status == 'transferred');
|
: (status == 'on_shelf' || status == 'transferred');
|
||||||
return isConflict ? Colors.red.shade50 : const Color(0xFFF1F8E9);
|
return isConflict ? Colors.red.shade50 : const Color(0xFFF1F8E9);
|
||||||
}
|
}
|
||||||
@@ -141,6 +169,8 @@ Color registrationRowBgColor({
|
|||||||
switch (status) {
|
switch (status) {
|
||||||
case 'on_shelf':
|
case 'on_shelf':
|
||||||
return const Color(0xFFE3F2FD);
|
return const Color(0xFFE3F2FD);
|
||||||
|
case 'temp_stored':
|
||||||
|
return const Color(0xFFF3E5F5);
|
||||||
case 'transferred':
|
case 'transferred':
|
||||||
return const Color(0xFFFFF3E0);
|
return const Color(0xFFFFF3E0);
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -5,11 +5,13 @@ part of '../../registration_page.dart';
|
|||||||
extension _RegistrationWidgetsPart on _RegistrationPageState {
|
extension _RegistrationWidgetsPart on _RegistrationPageState {
|
||||||
String _locationLabel(CodeType? type) {
|
String _locationLabel(CodeType? type) {
|
||||||
if (type == CodeType.locationTransit) return '转运区域';
|
if (type == CodeType.locationTransit) return '转运区域';
|
||||||
|
if (type == CodeType.locationTempStorage) return '暂存货架';
|
||||||
return '普通货架';
|
return '普通货架';
|
||||||
}
|
}
|
||||||
|
|
||||||
Color _locationLabelColor(CodeType? type) {
|
Color _locationLabelColor(CodeType? type) {
|
||||||
if (type == CodeType.locationTransit) return Colors.orange;
|
if (type == CodeType.locationTransit) return Colors.orange;
|
||||||
|
if (type == CodeType.locationTempStorage) return const Color(0xFF7B1FA2);
|
||||||
return Colors.blue;
|
return Colors.blue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,6 +110,12 @@ extension _RegistrationWidgetsPart on _RegistrationPageState {
|
|||||||
const Color(0xFFE65100),
|
const Color(0xFFE65100),
|
||||||
hasData,
|
hasData,
|
||||||
),
|
),
|
||||||
|
_buildStatTag(
|
||||||
|
'暂存 ${stats.tempStoredCount}',
|
||||||
|
const Color(0xFFF3E5F5),
|
||||||
|
const Color(0xFF7B1FA2),
|
||||||
|
hasData,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -293,11 +301,13 @@ extension _RegistrationWidgetsPart on _RegistrationPageState {
|
|||||||
status: item.status,
|
status: item.status,
|
||||||
isScanned: isScanned,
|
isScanned: isScanned,
|
||||||
isTransitTarget: _isTransitTarget,
|
isTransitTarget: _isTransitTarget,
|
||||||
|
isTempStorageTarget: _isTempStorageTarget,
|
||||||
);
|
);
|
||||||
final bgColor = registration_calculations.registrationRowBgColor(
|
final bgColor = registration_calculations.registrationRowBgColor(
|
||||||
status: item.status,
|
status: item.status,
|
||||||
isScanned: isScanned,
|
isScanned: isScanned,
|
||||||
isTransitTarget: _isTransitTarget,
|
isTransitTarget: _isTransitTarget,
|
||||||
|
isTempStorageTarget: _isTempStorageTarget,
|
||||||
);
|
);
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
|
|||||||
@@ -1,14 +1,8 @@
|
|||||||
// lib/services/code_parser.dart
|
// lib/services/code_parser.dart
|
||||||
class CodeParser {
|
class CodeParser {
|
||||||
static final _zongpaiRegex = RegExp(
|
static final _zongpaiRegex = RegExp(r'^\d{2}(B|C|T)\d+$|^\d{2}(BW|CW)\d{4}$');
|
||||||
r'^\d{2}(B|C|T)\d+$|^\d{2}(BW|CW)\d{4}$',
|
static final _locationRegex = RegExp(r'^[A-Z0-9]+-[A-Z0-9]+-[A-Z0-9]+$');
|
||||||
);
|
static final _transitRegex = RegExp(r'^TRANS-');
|
||||||
static final _locationRegex = RegExp(
|
|
||||||
r'^[A-Z0-9]+-[A-Z0-9]+-[A-Z0-9]+$',
|
|
||||||
);
|
|
||||||
static final _transitRegex = RegExp(
|
|
||||||
r'^TRANS-',
|
|
||||||
);
|
|
||||||
|
|
||||||
static ParseResult parse(String code) {
|
static ParseResult parse(String code) {
|
||||||
final trimmed = code.trim();
|
final trimmed = code.trim();
|
||||||
@@ -23,16 +17,30 @@ class CodeParser {
|
|||||||
return ParseResult(type: CodeType.zongpaiNo, value: normalized);
|
return ParseResult(type: CodeType.zongpaiNo, value: normalized);
|
||||||
}
|
}
|
||||||
if (_locationRegex.hasMatch(normalized)) {
|
if (_locationRegex.hasMatch(normalized)) {
|
||||||
|
if (normalized.startsWith('B')) {
|
||||||
|
return ParseResult(
|
||||||
|
type: CodeType.locationTempStorage,
|
||||||
|
value: normalized,
|
||||||
|
);
|
||||||
|
}
|
||||||
return ParseResult(type: CodeType.locationNormal, value: normalized);
|
return ParseResult(type: CodeType.locationNormal, value: normalized);
|
||||||
}
|
}
|
||||||
return ParseResult(type: CodeType.invalid, value: code);
|
return ParseResult(type: CodeType.invalid, value: code);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool isLocation(CodeType type) =>
|
static bool isLocation(CodeType type) =>
|
||||||
type == CodeType.locationNormal || type == CodeType.locationTransit;
|
type == CodeType.locationNormal ||
|
||||||
|
type == CodeType.locationTransit ||
|
||||||
|
type == CodeType.locationTempStorage;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum CodeType { zongpaiNo, locationNormal, locationTransit, invalid }
|
enum CodeType {
|
||||||
|
zongpaiNo,
|
||||||
|
locationNormal,
|
||||||
|
locationTransit,
|
||||||
|
locationTempStorage,
|
||||||
|
invalid,
|
||||||
|
}
|
||||||
|
|
||||||
class ParseResult {
|
class ParseResult {
|
||||||
final CodeType type;
|
final CodeType type;
|
||||||
|
|||||||
@@ -8,13 +8,20 @@ bool isTransitTarget(CodeType? locationType, String? locationCode) {
|
|||||||
(locationCode?.startsWith('TRANS-') ?? false);
|
(locationCode?.startsWith('TRANS-') ?? false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isTempStorageTarget(CodeType? locationType, String? locationCode) {
|
||||||
|
return locationType == CodeType.locationTempStorage ||
|
||||||
|
(locationCode?.startsWith('B') ?? false);
|
||||||
|
}
|
||||||
|
|
||||||
String registrationSubmitLabel({
|
String registrationSubmitLabel({
|
||||||
required bool isTransitTarget,
|
required bool isTransitTarget,
|
||||||
required bool isMultiCode,
|
required bool isMultiCode,
|
||||||
required int zongpaiCount,
|
required int zongpaiCount,
|
||||||
}) {
|
}) {
|
||||||
if (!isTransitTarget) {
|
if (!isTransitTarget) {
|
||||||
return isMultiCode && zongpaiCount > 1 ? '批量上架($zongpaiCount 条)' : '确 认 上 架';
|
return isMultiCode && zongpaiCount > 1
|
||||||
|
? '批量上架($zongpaiCount 条)'
|
||||||
|
: '确 认 上 架';
|
||||||
}
|
}
|
||||||
return isMultiCode && zongpaiCount > 1 ? '批量转运并凑箱($zongpaiCount 条)' : '转运并装箱';
|
return isMultiCode && zongpaiCount > 1 ? '批量转运并凑箱($zongpaiCount 条)' : '转运并装箱';
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user