You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于FlutterFlow(Flutter+Firebase)的公益物资捐赠APP批量写入功能扩展需求及代码优化咨询

Extending Firestore Batch Writes for Your Donation App

Got it, let's build out your batch write functionality to cover all four requirements you listed. First, I'll refactor your existing code to be more robust (using async/await instead of .then() for readability and error handling), then walk through each feature with concrete, integrated code examples.


Refactored Base Batch Helper

First, let's clean up your core batch function to handle null safety and errors better:

// Base helper to reuse batch update logic
Future<void> baseBatchUpdate(
  String updateCollection,
  String updateField,
  dynamic currentValue,
  dynamic newValue,
  DocumentReference donationRef,
) async {
  final collectionRef = FirebaseFirestore.instance.collection(updateCollection);
  final batch = FirebaseFirestore.instance.batch();

  try {
    final querySnapshot = await collectionRef.where(updateField, isEqualTo: currentValue).get();
    
    for (final doc in querySnapshot.docs) {
      batch.update(doc.reference, {
        updateField: newValue,
        'donation': donationRef,
      });
    }
    
    await batch.commit();
    print('Base batch update completed successfully');
  } catch (e) {
    print('Error in base batch update: $e');
    rethrow; // Let the caller handle the error if needed
  }
}

Full Integrated Batch Function for Your Requirements

We'll wrap all four tasks into a single atomic batch operation (critical for data consistency—all operations succeed or fail together):

Future<void> processDonationBatch(
  DocumentReference userRef,
  DocumentReference donationRef,
) async {
  final batch = FirebaseFirestore.instance.batch();

  try {
    // 1. Migrate cart items to donation subcollection
    await _migrateCartItems(batch, userRef, donationRef);

    // 2. Update project's cumulative donation stats
    await _updateProjectStats(batch, donationRef);

    // 3. Create/update delivery records & adjust pending order counts
    await _handleDeliveryRecords(batch, donationRef);

    // 4. Update daily/weekly/monthly donation reports
    await _updateDonationReports(batch, donationRef);

    // Commit all operations atomically
    await batch.commit();
    print('Donation batch processed successfully!');
  } catch (e) {
    print('Error processing donation batch: $e');
    rethrow; // Pass error to caller for user feedback
  }
}

Breakdown of Each Task

1. Migrate Cart Items to Donation Subcollection

This copies cart items linked to the user to the donation's subcollection and marks the original cart items as migrated (or delete them with batch.delete() if preferred):

Future<void> _migrateCartItems(
  WriteBatch batch,
  DocumentReference userRef,
  DocumentReference donationRef,
) async {
  final cartCollection = FirebaseFirestore.instance.collection('cartItems');
  final cartSnapshot = await cartCollection.where('userRef', isEqualTo: userRef).get();

  for (final cartDoc in cartSnapshot.docs) {
    // Copy cart item to donation's subcollection
    final donationItemRef = donationRef.collection('donationItems').doc();
    batch.set(donationItemRef, {
      ...cartDoc.data(), // Copy all cart fields
      'donationRef': donationRef,
      'createdAt': FieldValue.serverTimestamp(),
    });

    // Mark original cart item as migrated (optional: use batch.delete(cartDoc.reference) to remove)
    batch.update(cartDoc.reference, {'isMigrated': true});
  }
}

2. Update Project's Cumulative Donation Amount & Completed Quantity

We calculate total amounts/quantities from the donation items, then update the linked project's cumulative stats:

Future<void> _updateProjectStats(
  WriteBatch batch,
  DocumentReference donationRef,
) async {
  // Fetch all donation items for this donation
  final donationItemsSnapshot = await donationRef.collection('donationItems').get();
  
  double totalAmount = 0;
  int totalQuantity = 0;

  for (final item in donationItemsSnapshot.docs) {
    totalAmount += item.data()['subtotal'] ?? 0;
    totalQuantity += item.data()['quantity'] ?? 0;
  }

  // Get the project reference from the donation document
  final donationDoc = await donationRef.get();
  final projectRef = donationDoc.data()!['projectRef'] as DocumentReference;

  // Update project stats with atomic increments
  batch.update(projectRef, {
    'cumulativeDonationAmount': FieldValue.increment(totalAmount),
    'completedQuantity': FieldValue.increment(totalQuantity),
  });
}

3. Create/Update Delivery Records & Update Pending Order Counts

For each donation item, we create a delivery record for the supplier and adjust the donation item's pending count:

Future<void> _handleDeliveryRecords(
  WriteBatch batch,
  DocumentReference donationRef,
) async {
  final donationItemsSnapshot = await donationRef.collection('donationItems').get();

  for (final itemDoc in donationItemsSnapshot.docs) {
    final itemData = itemDoc.data();
    final supplierRef = itemData['supplierRef'] as DocumentReference;
    final productId = itemData['productId'];
    final quantity = itemData['quantity'] ?? 0;

    // Create or update delivery record (unique ID using donation + product ID)
    final deliveryRecordRef = supplierRef.collection('deliveryRecords').doc('${donationRef.id}_$productId');
    batch.set(deliveryRecordRef, {
      'donationRef': donationRef,
      'productId': productId,
      'quantity': quantity,
      'status': 'pending',
      'updatedAt': FieldValue.serverTimestamp(),
    }, SetOptions(merge: true)); // Merge to update existing records

    // Increment pending order count for the donation item
    batch.update(itemDoc.reference, {
      'pendingOrderCount': FieldValue.increment(1),
    });
  }
}

4. Update Daily/Weekly/Monthly Donation Reports

We aggregate donation data by project, supplier, and product, then update time-based reports:

Future<void> _updateDonationReports(
  WriteBatch batch,
  DocumentReference donationRef,
) async {
  final donationDoc = await donationRef.get();
  final donationData = donationDoc.data()!;
  final projectRef = donationData['projectRef'] as DocumentReference;
  final supplierRef = donationData['supplierRef'] as DocumentReference;
  
  final donationItemsSnapshot = await donationRef.collection('donationItems').get();
  final now = DateTime.now().toUtc(); // Use UTC to avoid timezone issues

  // Helper functions to generate time period IDs
  String getDailyId(DateTime date) => '${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}';
  String getWeeklyId(DateTime date) => '${date.year}-W${date.weekOfYear.toString().padLeft(2, '0')}'; // Add weekOfYear extension or use intl package
  String getMonthlyId(DateTime date) => '${date.year}-${date.month.toString().padLeft(2, '0')}';

  // Aggregate data by product
  final productAggregates = <String, Map<String, dynamic>>{};
  for (final item in donationItemsSnapshot.docs) {
    final productId = item.data()['productId'];
    final amount = item.data()['subtotal'] ?? 0;
    final quantity = item.data()['quantity'] ?? 0;

    productAggregates.putIfAbsent(productId, () => {'totalAmount': 0, 'totalQuantity': 0});
    productAggregates[productId]!['totalAmount'] += amount;
    productAggregates[productId]!['totalQuantity'] += quantity;
  }

  // Update reports for each time period
  for (final period in ['daily', 'weekly', 'monthly']) {
    final reportCollection = FirebaseFirestore.instance.collection('donationReports').doc(period).collection('projectSupplierProduct');
    String reportId;

    switch (period) {
      case 'daily':
        reportId = '${projectRef.id}_${supplierRef.id}_${getDailyId(now)}';
        break;
      case 'weekly':
        reportId = '${projectRef.id}_${supplierRef.id}_${getWeeklyId(now)}';
        break;
      case 'monthly':
        reportId = '${projectRef.id}_${supplierRef.id}_${getMonthlyId(now)}';
        break;
      default:
        reportId = '';
    }

    for (final productId in productAggregates.keys) {
      final productReportRef = reportCollection.doc('${reportId}_$productId');
      batch.set(productReportRef, {
        'projectRef': projectRef,
        'supplierRef': supplierRef,
        'productId': productId,
        'period': period,
        'totalAmount': FieldValue.increment(productAggregates[productId]!['totalAmount']),
        'totalQuantity': FieldValue.increment(productAggregates[productId]!['totalQuantity']),
        'lastUpdated': FieldValue.serverTimestamp(),
      }, SetOptions(merge: true));
    }
  }
}

Key Notes

  • Atomicity: All operations in the batch are committed together—if any fail, no changes are written to Firestore, ensuring data consistency.
  • Batch Limits: Firestore allows a maximum of 500 operations per batch. If you expect large cart lists, add logic to split operations into multiple batches.
  • Null Safety: I've added fallbacks for nullable fields (e.g., ?? 0), adjust these to match your actual data model.
  • Time Zones: Using UTC for report timestamps avoids discrepancies across time zones.

内容的提问来源于stack exchange,提问作者Ahmed Mahmoud

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.27 13:52:29