Version initiale de Gestion Prix
App Flutter de gestion de stock/prix pour tablette Android (100% local) : scan code-barres (OpenFoodFacts), prix au kilo/litre, packs, inventaire, prix d'achat + marge, filtres de tri, étiquettes PDF, design noir & blanc. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
import 'dart:convert';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
import '../models/produit.dart';
|
||||
|
||||
/// Résultat d'une recherche produit sur OpenFoodFacts.
|
||||
class InfoProduit {
|
||||
final String? nom;
|
||||
final String? imageUrl;
|
||||
final String? marque;
|
||||
final double? quantite;
|
||||
final String? unite;
|
||||
|
||||
const InfoProduit({
|
||||
this.nom,
|
||||
this.imageUrl,
|
||||
this.marque,
|
||||
this.quantite,
|
||||
this.unite,
|
||||
});
|
||||
|
||||
bool get estVide => (nom == null || nom!.isEmpty) && imageUrl == null;
|
||||
}
|
||||
|
||||
/// Interroge l'API publique OpenFoodFacts pour récupérer le nom, l'image et la
|
||||
/// quantité (grammage / litrage) d'un produit à partir de son code-barres.
|
||||
class OpenFoodFactsService {
|
||||
static const String _base = 'https://world.openfoodfacts.org/api/v2/product';
|
||||
|
||||
Future<InfoProduit?> chercherParCodeBarres(String codeBarres) async {
|
||||
final uri = Uri.parse(
|
||||
'$_base/$codeBarres.json?fields=product_name,product_name_fr,brands,'
|
||||
'image_front_url,image_url,product_quantity,product_quantity_unit',
|
||||
);
|
||||
|
||||
try {
|
||||
final res = await http
|
||||
.get(uri, headers: {'User-Agent': 'GestionPrixProduit/1.0'})
|
||||
.timeout(const Duration(seconds: 8));
|
||||
|
||||
if (res.statusCode != 200) return null;
|
||||
|
||||
final data = jsonDecode(res.body) as Map<String, dynamic>;
|
||||
// status == 1 => produit trouvé
|
||||
if (data['status'] != 1) return const InfoProduit();
|
||||
|
||||
final p = data['product'] as Map<String, dynamic>? ?? {};
|
||||
final nom = (p['product_name_fr'] as String?)?.trim();
|
||||
final nomEn = (p['product_name'] as String?)?.trim();
|
||||
final image = (p['image_front_url'] as String?)?.trim() ??
|
||||
(p['image_url'] as String?)?.trim();
|
||||
|
||||
final (quantite, unite) = _quantite(p);
|
||||
|
||||
return InfoProduit(
|
||||
nom: (nom != null && nom.isNotEmpty) ? nom : nomEn,
|
||||
imageUrl: (image != null && image.isNotEmpty) ? image : null,
|
||||
marque: (p['brands'] as String?)?.split(',').first.trim(),
|
||||
quantite: quantite,
|
||||
unite: unite,
|
||||
);
|
||||
} catch (_) {
|
||||
// Pas de réseau ou timeout : on retourne null, l'utilisateur saisit à la main.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Normalise la quantité OpenFoodFacts (product_quantity + unité) vers nos unités.
|
||||
(double?, String?) _quantite(Map<String, dynamic> p) {
|
||||
final q = (p['product_quantity'] as num?)?.toDouble();
|
||||
final u = (p['product_quantity_unit'] as String?)?.toLowerCase().trim();
|
||||
if (q == null || q <= 0 || u == null) return (null, null);
|
||||
|
||||
return switch (u) {
|
||||
'g' => (q, Unites.g),
|
||||
'kg' => (q, Unites.kg),
|
||||
'mg' => (q / 1000, Unites.g),
|
||||
'ml' => (q, Unites.ml),
|
||||
'cl' => (q, Unites.cl),
|
||||
'l' => (q, Unites.l),
|
||||
_ => (null, null),
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user