completed migrating data scope from user to workspace

This commit is contained in:
2026-06-22 22:10:38 +02:00
parent 62c7f53de0
commit ae2705363a
37 changed files with 918 additions and 221 deletions

View File

@@ -90,10 +90,12 @@ class _BatchCreateScreenState extends State<BatchCreateScreen> {
final prefs = await SharedPreferences.getInstance();
final userId = prefs.getString('userId') ?? '';
final workspaceId = prefs.getString('workspaceId') ?? '';
final req = CreateBatchRequest(
recipientGroupId: widget.group.id ?? '',
userId: userId,
workspaceId: workspaceId,
currency: _currency,
description: _descriptionController.text.trim(),
amount: amount,
@@ -115,7 +117,7 @@ class _BatchCreateScreenState extends State<BatchCreateScreen> {
batch.id!,
selectedProvider,
selectedProduct,
userId,
workspaceId,
);
}
@@ -130,7 +132,7 @@ class _BatchCreateScreenState extends State<BatchCreateScreen> {
String batchId,
BillProvider provider,
BillProduct? product,
String userId,
String workspaceId,
) async {
// Fetch the batch items so we can update them
final itemsResult = await batchController.listBatchItems(batchId);
@@ -154,7 +156,7 @@ class _BatchCreateScreenState extends State<BatchCreateScreen> {
final updateList = GroupBatchItemUpdateList(
items: updateItems,
userId: userId,
workspaceId: workspaceId,
);
await batchController.updateBatchItems(batchId, updateList);

View File

@@ -33,12 +33,13 @@ class _BatchDetailScreenState extends State<BatchDetailScreen>
ConfirmController? _confirmController;
GroupBatchUpdateRequest? _cachedUpdateReq;
bool _showPollButton = false;
String _userId = '';
String _workspaceId = '';
bool _initialLoading = true;
String? _loadError;
PaymentProcessor? _selectedProcessor;
final TextEditingController _searchController = TextEditingController();
final TextEditingController _accountController = TextEditingController();
final ScrollController _scrollController = ScrollController();
bool _editMode = false;
@@ -85,9 +86,9 @@ class _BatchDetailScreenState extends State<BatchDetailScreen>
Future<void> _init() async {
final prefs = await SharedPreferences.getInstance();
_userId = prefs.getString('userId') ?? '';
_workspaceId = prefs.getString('workspaceId') ?? '';
final result = await controller.getBatch(widget.batchId, _userId);
final result = await controller.getBatch(widget.batchId, _workspaceId);
if (!mounted) return;
if (result.isSuccess) {
@@ -117,6 +118,7 @@ class _BatchDetailScreenState extends State<BatchDetailScreen>
@override
void dispose() {
_searchController.dispose();
_accountController.dispose();
_scrollController.dispose();
_entryController.dispose();
_confirmController?.dispose();
@@ -136,7 +138,7 @@ class _BatchDetailScreenState extends State<BatchDetailScreen>
// Persist the selected payment processor on the batch before confirming
final updateReq = GroupBatchUpdateRequest(
id: batch.id,
userId: _userId,
workspaceId: _workspaceId,
paymentProcessorLabel: processor.label,
paymentProcessorName: processor.name,
paymentProcessorImage: processor.image,
@@ -152,7 +154,11 @@ class _BatchDetailScreenState extends State<BatchDetailScreen>
_cachedUpdateReq = updateReq;
// Step 2: Confirm the batch
final result = await controller.confirmBatch(batch.id!, _userId);
final result = await controller.confirmBatch(
batch.id!,
_workspaceId,
_selectedProcessor!.authType,
);
if (!mounted) return;
if (!result.isSuccess) {
_showError(result.error ?? 'Confirm failed.');
@@ -431,7 +437,7 @@ class _BatchDetailScreenState extends State<BatchDetailScreen>
final batch = _batch;
if (batch == null) return;
final result = await controller.requestBatch(batch.id!, _userId);
final result = await controller.requestBatch(batch.id!, _workspaceId);
if (!mounted) return;
if (!result.isSuccess) {
@@ -484,7 +490,7 @@ class _BatchDetailScreenState extends State<BatchDetailScreen>
}
}
} else {
final pollResult = await controller.pollBatch(batch.id!, _userId);
final pollResult = await controller.pollBatch(batch.id!, _workspaceId);
if (!mounted) return;
if (pollResult.isSuccess) {
setState(() => _showPollButton = false);
@@ -672,7 +678,7 @@ class _BatchDetailScreenState extends State<BatchDetailScreen>
Future<void> _onPoll() async {
final batch = _batch;
if (batch == null) return;
final result = await controller.pollBatch(batch.id!, _userId);
final result = await controller.pollBatch(batch.id!, _workspaceId);
if (!mounted) return;
if (result.isSuccess) {
setState(() => _showPollButton = false);
@@ -685,7 +691,10 @@ class _BatchDetailScreenState extends State<BatchDetailScreen>
Future<void> _onDownloadReport() async {
final batch = _batch;
if (batch == null) return;
final result = await controller.downloadBatchReport(batch.id!, _userId);
final result = await controller.downloadBatchReport(
batch.id!,
_workspaceId,
);
if (!mounted) return;
if (result.isSuccess) {
final fileUrl = result.data?['fileUrl'] as String?;
@@ -1435,7 +1444,10 @@ class _BatchDetailScreenState extends State<BatchDetailScreen>
if (items.isEmpty) return false;
final updateList = GroupBatchItemUpdateList(items: items, userId: _userId);
final updateList = GroupBatchItemUpdateList(
items: items,
workspaceId: _workspaceId,
);
final result = await controller.updateBatchItems(batch.id!, updateList);
if (!mounted) return false;
@@ -1492,6 +1504,8 @@ class _BatchDetailScreenState extends State<BatchDetailScreen>
onPressed: () {
setSheetState(() {
controller.model.batchItemsStatusFilter = null;
controller.model.batchItemsRecipientAccount = null;
_accountController.clear();
});
},
child: const Text('Clear All'),
@@ -1517,14 +1531,18 @@ class _BatchDetailScreenState extends State<BatchDetailScreen>
items: const [
DropdownMenuItem(value: null, child: Text('All')),
DropdownMenuItem(
value: 'PENDING',
child: Text('Pending'),
value: 'CONFIRMED',
child: Text('CONFIRMED'),
),
DropdownMenuItem(
value: 'SUCCESS',
child: Text('Success'),
value: 'INTEGRATED',
child: Text('INTEGRATED'),
),
DropdownMenuItem(value: 'FAILED', child: Text('FAILED')),
DropdownMenuItem(
value: 'SKIPPED',
child: Text('SKIPPED'),
),
DropdownMenuItem(value: 'FAILED', child: Text('Failed')),
],
onChanged: (v) {
setSheetState(
@@ -1532,6 +1550,24 @@ class _BatchDetailScreenState extends State<BatchDetailScreen>
);
},
),
const SizedBox(height: 16),
TextField(
controller: _accountController,
decoration: InputDecoration(
labelText: 'Account Number',
hintText: 'Filter by account number',
filled: true,
fillColor: Colors.grey.shade100,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none,
),
contentPadding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 14,
),
),
),
const SizedBox(height: 24),
SizedBox(
width: double.infinity,
@@ -1546,6 +1582,9 @@ class _BatchDetailScreenState extends State<BatchDetailScreen>
? _searchController.text
: null,
status: controller.model.batchItemsStatusFilter,
account: _accountController.text.isNotEmpty
? _accountController.text
: null,
);
}
},
@@ -2021,7 +2060,9 @@ class _BatchDetailScreenState extends State<BatchDetailScreen>
// Action Bar — search, filter, edit, refresh
// ──────────────────────────────────────────────────────────────
Widget _buildActionBar(ThemeData theme, bool isDark) {
final hasFilters = controller.model.batchItemsStatusFilter != null;
final hasFilters =
controller.model.batchItemsStatusFilter != null ||
controller.model.batchItemsRecipientAccount != null;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@@ -2033,7 +2074,7 @@ class _BatchDetailScreenState extends State<BatchDetailScreen>
child: TextFormField(
controller: _searchController,
decoration: InputDecoration(
hintText: 'Search by name or phone',
hintText: 'Search by recipient name',
prefixIcon: const Icon(Icons.search, size: 20),
suffixIcon: _searchController.text.isNotEmpty
? IconButton(
@@ -2046,6 +2087,9 @@ class _BatchDetailScreenState extends State<BatchDetailScreen>
batchId,
status:
controller.model.batchItemsStatusFilter,
account: controller
.model
.batchItemsRecipientAccount,
);
}
},
@@ -2075,6 +2119,7 @@ class _BatchDetailScreenState extends State<BatchDetailScreen>
batchId,
search: value.isNotEmpty ? value : null,
status: controller.model.batchItemsStatusFilter,
account: controller.model.batchItemsRecipientAccount,
);
}
},
@@ -2147,6 +2192,26 @@ class _BatchDetailScreenState extends State<BatchDetailScreen>
search: _searchController.text.isNotEmpty
? _searchController.text
: null,
account: controller.model.batchItemsRecipientAccount,
);
}
},
theme,
),
if (controller.model.batchItemsRecipientAccount != null)
_activeFilterChip(
'Account: ${controller.model.batchItemsRecipientAccount}',
() {
controller.model.batchItemsRecipientAccount = null;
_accountController.clear();
final batchId = _batch?.id ?? '';
if (batchId.isNotEmpty) {
controller.searchBatchItems(
batchId,
search: _searchController.text.isNotEmpty
? _searchController.text
: null,
status: controller.model.batchItemsStatusFilter,
);
}
},

View File

@@ -65,10 +65,12 @@ class _CreateGroupFromFileScreenState extends State<CreateGroupFromFileScreen> {
}
final prefs = await SharedPreferences.getInstance();
final workspaceId = prefs.getString('workspaceId') ?? '';
final userId = prefs.getString('userId') ?? '';
final result = await controller.createGroupFromFile(
groupName: _groupNameController.text.trim(),
workspaceId: workspaceId,
userId: userId,
description: _descriptionController.text.trim().isEmpty
? null

View File

@@ -82,6 +82,7 @@ class _GroupCreateScreenState extends State<GroupCreateScreen> {
final prefs = await SharedPreferences.getInstance();
final userId = prefs.getString('userId') ?? '';
final workspaceId = prefs.getString('workspaceId') ?? '';
final req = CreateGroupRequest(
name: _nameController.text.trim(),
@@ -89,6 +90,7 @@ class _GroupCreateScreenState extends State<GroupCreateScreen> {
? null
: _descriptionController.text.trim(),
userId: userId,
workspaceId: workspaceId,
recipients: recipients,
);

View File

@@ -552,7 +552,7 @@ class _GroupDetailScreenState extends State<GroupDetailScreen>
await groupController.removeMember(
groupId: widget.group.id ?? '',
recipientId: member.id ?? '',
userId: widget.group.userId ?? '',
workspaceId: widget.group.workspaceId ?? '',
);
}
}
@@ -675,7 +675,7 @@ class _GroupDetailScreenState extends State<GroupDetailScreen>
}
void _onDeleteSelected() async {
final userId = widget.group.userId ?? '';
final workspaceId = widget.group.workspaceId ?? '';
final count = batchController.model.selectedBatchIds.length;
final confirmed = await showDialog<bool>(
@@ -700,7 +700,7 @@ class _GroupDetailScreenState extends State<GroupDetailScreen>
if (confirmed == true) {
await batchController.deleteSelectedBatches(
groupId: widget.group.id ?? '',
userId: userId,
workspaceId: workspaceId,
);
}
}

View File

@@ -30,26 +30,28 @@ class _GroupsScreenState extends State<GroupsScreen> {
void _onScroll() {
if (_scrollController.position.pixels >=
_scrollController.position.maxScrollExtent - 200) {
controller.loadNextPage(userId: prefs.getString('userId') ?? '');
controller.loadNextPage(
workspaceId: prefs.getString('workspaceId') ?? '',
);
}
}
Future<void> _loadData() async {
prefs = await SharedPreferences.getInstance();
final userId = prefs.getString('userId') ?? '';
await controller.listGroups(userId: userId);
final workspaceId = prefs.getString('workspaceId') ?? '';
await controller.listGroups(workspaceId: workspaceId);
}
void _onSearchChanged(String value) {
final userId = prefs.getString('userId') ?? '';
final workspaceId = prefs.getString('workspaceId') ?? '';
controller.searchGroups(
userId: userId,
workspaceId: workspaceId,
name: value.isNotEmpty ? value : null,
);
}
void _onDeleteSelected() async {
final userId = prefs.getString('userId') ?? '';
final workspaceId = prefs.getString('workspaceId') ?? '';
final count = controller.model.selectedGroupIds.length;
final confirmed = await showDialog<bool>(
@@ -72,7 +74,7 @@ class _GroupsScreenState extends State<GroupsScreen> {
);
if (confirmed == true) {
await controller.deleteSelectedGroups(userId: userId);
await controller.deleteSelectedGroups(workspaceId: workspaceId);
}
}