Post

Projet : CERES


Description

L’objectif du projet CERES était de développer une application mobile pour une entreprise du même nom. Cette entreprise propose des solutions pour la création de magasins sans caissiers où le client peut lui-même scanner et payer ses articles. L’objectif principal de notre collaboration était de leur fournir une application d’aide à la gestion des stocks pour les propriétaires et gérants de tels magasins. Cette dernière permet à ses utilisateurs de scanner des produits, de passer des commandes et d’envoyer automatiquement des e-mails de confirmation de commande. De plus, l’application offre la possibilité d’afficher l’historique des commandes précédentes et leurs détails.

Au début du projet, les membres de l’équipe impliqués étaient Maxime Dénervaud, Hugo Fairon et Flávio Moreira, puis Joanna Baranowska et moi-même avons rejoint le projet en cours de route.

Voici une démo du concept du magasin :

Ce que j’ai fait

Dans ce projet j’ai principalement réalisé :

  • Les premier tests unitaires

    Setup de l’environnement de test :

    1
    2
    3
    4
    5
    6
    7
    8
    
    setUp(() {
          // Prepare fake firebase firestore and pass it to ProductRepository class
          final firestore = FakeFirebaseFirestore();
          const price = 10;
          const quantity = 5;
          productRepository = ProductRepository(firebaseInstance: firestore);
          _setupDummyFirestoreData(firestore, price, quantity);
        });
    

    Setup de la fausse base de données pour les tests :

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    
      void _setupDummyFirestoreData(
        FakeFirebaseFirestore firestore, int price, int quantity) {
          firestore
            .collection('stores')
            .doc('QWgdTqH9Yext1bLZ1wsG')
            .collection('products')
            .doc('product_1')
            .set({
          'ean': [1234567890],
          'title': 'Test Product', // Required by products_model
          'price': 10,
          'quantity': 5,
          'category': "category", // Required by products_model
          'externalID': "externalID", // Required by products_model
          'isPriceInEAN': false, // Required by products_model
        });
      }
    

    Exemple de test :

    1
    2
    3
    4
    5
    6
    
      // Test valid input and output
          test('Get Product with valid EAN', () async {
            const barcode = '1234567890';
            final product = await productRepository.getProductByBarcode(barcode);
            expect(product.title, 'Test Product');
          });
    
  • La feature de recherche des commandes fournisseurs depuis le Firestore :

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    // Récupère la liste de toutes les commandes fournisseur
    Future<List<AllSupplierOrders>> getOrders() async {
      QuerySnapshot ordersSnapshot = await _supplierOrdersCollection.get();
      List<AllSupplierOrders> ordersList = [];
      for (var document in ordersSnapshot.docs) {
        List<dynamic> basket = document['basket'];
        Timestamp date = document['date'];
        int total = document['total'];
        bool? send = document['send'];
        AllSupplierOrders order = AllSupplierOrders(
          basket: basket,
          date: date,
          total: total,
          send: send,
        );
        ordersList.add(order);
      }
      return ordersList;
    }
    
  • La préparation et le tri des données récupérées :

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    // Chargement des produits dans la liste locale
    Future<void> loadOrders() async {
      List<AllSupplierOrders> loadedOrdersList =
          await SupplierOrdersRepository().getOrders();
      // Rechargement de la page lorsque la liste est remplie
      setState(() {
        ordersList = loadedOrdersList;
        loadedOrdersList
            .sort((order1, order2) => order2.date.compareTo(order1.date));
      });
    }
    
  • L’affichage et formatage des données :

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    
    @override
    Widget build(BuildContext context) {
      return Scaffold(
        appBar: AppBar(
          title: const Text('Commandes fournisseurs'),
          backgroundColor: Colors.green,
        ),
        body: ListView.builder(
          itemCount: ordersList.length,
          itemBuilder: (context, index) {
            AllSupplierOrders order = ordersList[index];
            DateTime date = order.date.toDate();
            // formatage de la date en gmt+1
            date.toLocal();
            // formatage de la date en jour/mois/année
            String formattedDate = DateFormat('dd/MM/yyyy').format(date);
    
            int totalQuantity = order.basket
                .fold<num>(0, (sum, item) => sum + item['quantity'])
                .toInt();
    
            return ListTile(
              title: Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  Text(
                    'ID_Commande ${index + 1}',
                    style: const TextStyle(fontWeight: FontWeight.bold),
                  ),
                  Text(
                    '${(order.total / 100).toStringAsFixed(2)} CHF',
                    style: const TextStyle(fontWeight: FontWeight.bold),
                  ),
                ],
              ),
              subtitle: Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  Text(
                    'Quantité articles: $totalQuantity',
                    style: const TextStyle(fontWeight: FontWeight.bold),
                  ),
                  Text(
                    'Date: ${formattedDate.toString()}',
                    style: const TextStyle(fontWeight: FontWeight.bold),
                  ),
                ],
              ),
              onTap: () {
                Navigator.of(context).push(
                  MaterialPageRoute(
                    builder: (context) => SupplierOrderListDetail(order: ordersList[index]),
                  ),
                );
              },
            );
          },
        ),
      );
    }
    

Voici une démonstration de la version quasi finale de l’application :

Pour plus d’informations, consultez le rapport ici.

This post is licensed under CC BY 4.0 by the author.