feat: implement alias strategy for receipt import with user-scoped and global fallback, enhance validation and normalization, and update UI components
Test Suite / test (24.15.0) (push) Has been cancelled
Test Suite / test (24.15.0) (push) Has been cancelled
This commit is contained in:
@@ -19,6 +19,7 @@ class EditDialog extends StatefulWidget {
|
||||
final List<AdminCategoryNode> categoryTree;
|
||||
final Future<ProductOption?> Function(String name, int? categoryId)? onCreate;
|
||||
final ImportProductEntryMode? initialEntryMode;
|
||||
final bool canLearnGlobalAlias;
|
||||
|
||||
const EditDialog({
|
||||
super.key,
|
||||
@@ -28,6 +29,7 @@ class EditDialog extends StatefulWidget {
|
||||
required this.categoryTree,
|
||||
this.onCreate,
|
||||
this.initialEntryMode,
|
||||
this.canLearnGlobalAlias = false,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -53,6 +55,8 @@ class _EditDialogState extends State<EditDialog> {
|
||||
_Destination _destination = _Destination.inventory;
|
||||
ImportProductEntryMode _entryMode = ImportProductEntryMode.existing;
|
||||
bool _isCreatingProduct = false;
|
||||
bool _learnAlias = false;
|
||||
bool _learnAliasGlobally = false;
|
||||
|
||||
// Lokal lista — utökas om nya produkter skapas under dialogen
|
||||
late List<ProductOption> _localProducts;
|
||||
@@ -68,6 +72,8 @@ class _EditDialogState extends State<EditDialog> {
|
||||
_productName = widget.current.productName == null
|
||||
? null
|
||||
: normalizeProductName(widget.current.productName!);
|
||||
_learnAlias = widget.current.learnAlias;
|
||||
_learnAliasGlobally = widget.current.learnAliasGlobally;
|
||||
_destination = widget.current.destination;
|
||||
_entryMode = widget.initialEntryMode ??
|
||||
(_productId == null
|
||||
@@ -273,6 +279,8 @@ class _EditDialogState extends State<EditDialog> {
|
||||
ItemEdit(
|
||||
productId: _productId,
|
||||
productName: _productName,
|
||||
learnAlias: _learnAlias,
|
||||
learnAliasGlobally: _learnAlias && widget.canLearnGlobalAlias && _learnAliasGlobally,
|
||||
categoryId: _productCategoryId,
|
||||
categoryPath: _productCategoryPath,
|
||||
categorySource: _productCategorySource,
|
||||
@@ -358,6 +366,8 @@ class _EditDialogState extends State<EditDialog> {
|
||||
else
|
||||
_buildCreateProductSection(theme, aiLabel),
|
||||
const SizedBox(height: 12),
|
||||
_buildAliasSection(theme, item),
|
||||
const SizedBox(height: 12),
|
||||
if (_destination == _Destination.inventory)
|
||||
_buildQuantitySection(theme, totalPreview, currentUnit)
|
||||
else
|
||||
@@ -429,6 +439,70 @@ class _EditDialogState extends State<EditDialog> {
|
||||
style: const ButtonStyle(visualDensity: VisualDensity.compact),
|
||||
);
|
||||
|
||||
Widget _buildAliasSection(ThemeData theme, ParsedReceiptItem item) {
|
||||
final alreadyAliasMatch =
|
||||
_entryMode == ImportProductEntryMode.existing &&
|
||||
_productId != null &&
|
||||
item.matchedVia == 'alias' &&
|
||||
item.matchedProductId == _productId;
|
||||
|
||||
if (alreadyAliasMatch) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.primaryContainer.withValues(alpha: 0.45),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Text(
|
||||
'Det här kvittonamnet matchades redan via alias. Ingen ny aliasinlärning behövs.',
|
||||
style: theme.textTheme.bodySmall,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
CheckboxListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
value: _learnAlias,
|
||||
onChanged: (value) => setState(() {
|
||||
_learnAlias = value ?? false;
|
||||
if (!_learnAlias) _learnAliasGlobally = false;
|
||||
}),
|
||||
title: const Text('Lär detta kvittonamn för framtiden'),
|
||||
subtitle: const Text(
|
||||
'Sparar ett alias så att samma kvittonamn kan matchas direkt vid nästa import.',
|
||||
),
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
),
|
||||
if (widget.canLearnGlobalAlias && _learnAlias)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 12),
|
||||
child: SegmentedButton<bool>(
|
||||
segments: const [
|
||||
ButtonSegment<bool>(
|
||||
value: false,
|
||||
label: Text('Privat alias'),
|
||||
icon: Icon(Icons.lock_outline, size: 16),
|
||||
),
|
||||
ButtonSegment<bool>(
|
||||
value: true,
|
||||
label: Text('Global fallback'),
|
||||
icon: Icon(Icons.public_outlined, size: 16),
|
||||
),
|
||||
],
|
||||
selected: {_learnAliasGlobally},
|
||||
onSelectionChanged: (selection) =>
|
||||
setState(() => _learnAliasGlobally = selection.first),
|
||||
style: const ButtonStyle(visualDensity: VisualDensity.compact),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildExistingProductSection(
|
||||
ThemeData theme,
|
||||
ParsedReceiptItem item,
|
||||
|
||||
Reference in New Issue
Block a user