Add full app source
Adds the Flutter inventory app source, cleaned up for a shared repo: generic package name, no hardcoded ERP endpoints (moved to gitignored local config), no dead auth code, no debug logging of session data.
This commit is contained in:
262
lib/controllers/home_controller.dart
Normal file
262
lib/controllers/home_controller.dart
Normal file
@@ -0,0 +1,262 @@
|
||||
import 'dart:typed_data';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:inventory_app/constants.dart';
|
||||
import 'package:inventory_app/models/famille_produit.dart';
|
||||
import 'package:inventory_app/models/depot.dart';
|
||||
import 'package:inventory_app/models/inventory_line.dart';
|
||||
import 'package:inventory_app/models/product.dart';
|
||||
import 'package:inventory_app/models/tracking_number.dart';
|
||||
import 'package:inventory_app/service/axelor_client.dart';
|
||||
import 'package:inventory_app/views/login_view.dart';
|
||||
|
||||
enum InventoryLineState { newLine, existingLine }
|
||||
|
||||
class HomeController extends GetxController {
|
||||
final outputController = TextEditingController();
|
||||
final labelController = TextEditingController();
|
||||
final qtyController = TextEditingController(text: "1");
|
||||
final unitController = TextEditingController();
|
||||
final serialNumberTxt = TextEditingController();
|
||||
|
||||
var product = Rxn<Product>();
|
||||
var selectedOption = 'comptage1'.obs;
|
||||
|
||||
var depotList = <Depot>[].obs;
|
||||
var selectedDepot = Rxn<Depot>();
|
||||
|
||||
var trackingNumbers = <TrackingNumber>[].obs;
|
||||
var selectedTrackingNumber = Rxn<TrackingNumber>();
|
||||
|
||||
var isLoading = false.obs;
|
||||
var imageUrl = ''.obs;
|
||||
|
||||
var familleProduits = <FamilleProduit>[].obs;
|
||||
var selectedFamilleProduit = Rxn<FamilleProduit>();
|
||||
|
||||
var sousFamilleProduits = <FamilleProduit>[].obs;
|
||||
var selectedSousFamilleProduit = Rxn<FamilleProduit>();
|
||||
|
||||
var zoneCode = ''.obs;
|
||||
var selectedState = 'Moyen'.obs;
|
||||
|
||||
late AxelorClient client;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
_initClient();
|
||||
}
|
||||
|
||||
@override
|
||||
void onReady() {
|
||||
super.onReady();
|
||||
_handleArgs();
|
||||
}
|
||||
|
||||
Future<void> _initClient() async {
|
||||
isLoading(true);
|
||||
client = await AxelorClient.create();
|
||||
depotList.value = (await client.fetchLocations()) ?? [];
|
||||
isLoading(false);
|
||||
}
|
||||
|
||||
Future<void> fetchLocations() async {
|
||||
isLoading(true);
|
||||
client = await AxelorClient.create();
|
||||
depotList.value = (await client.fetchLocations()) ?? [];
|
||||
isLoading(false);
|
||||
}
|
||||
|
||||
Future<void> _loadProduct(String code) async {
|
||||
try {
|
||||
isLoading(true);
|
||||
|
||||
Product? p = await client.fetchProductByCode(code);
|
||||
if (p == null) {
|
||||
Get.snackbar('Erreur', 'Produit introuvable', backgroundColor: Colors.red);
|
||||
return;
|
||||
}
|
||||
|
||||
product.value = p;
|
||||
labelController.text = p.name ?? '';
|
||||
unitController.text = p.unit?.name ?? '';
|
||||
imageUrl.value = p.imageUrl ?? '';
|
||||
|
||||
trackingNumbers.value =
|
||||
await client.fetchTrackingNumberByProduct(p.id!) ?? [];
|
||||
|
||||
Get.snackbar(
|
||||
'Produit trouvé',
|
||||
'Produit: ${p.name}',
|
||||
backgroundColor: Colors.green,
|
||||
duration: const Duration(seconds: 2),
|
||||
);
|
||||
} catch (e) {
|
||||
Get.snackbar(
|
||||
'Erreur',
|
||||
'Impossible de charger le produit',
|
||||
backgroundColor: Colors.red,
|
||||
);
|
||||
} finally {
|
||||
isLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _handleArgs() async {
|
||||
final args = Get.arguments;
|
||||
if (args is String) {
|
||||
outputController.text = args;
|
||||
await _loadProduct(args);
|
||||
}
|
||||
}
|
||||
|
||||
void handleScannedCode(String code) async {
|
||||
outputController.text = code;
|
||||
trackingNumbers.clear();
|
||||
selectedTrackingNumber.value = null;
|
||||
await _loadProduct(code);
|
||||
}
|
||||
|
||||
void handleProductScan(String code) => handleScannedCode(code);
|
||||
|
||||
void handleLocationScan(String name) async {
|
||||
var finalStr = "";
|
||||
if (name.isNotEmpty) {
|
||||
finalStr = name.split(":")[0].trim();
|
||||
}
|
||||
|
||||
zoneCode.value = finalStr;
|
||||
final locationJson = await client.fetchStockLocationByName(name);
|
||||
if (locationJson == null) {
|
||||
Get.snackbar(
|
||||
'Erreur',
|
||||
'Zone introuvable (Veuillez rafraichir la page et ressayer)',
|
||||
backgroundColor: Colors.red,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
final depot = Depot.fromJson(locationJson);
|
||||
selectedDepot.value = depot;
|
||||
|
||||
Get.snackbar('Zone trouvée', 'Nom: ${depot.name}', backgroundColor: Colors.blue);
|
||||
}
|
||||
|
||||
Future<void> save() async {
|
||||
if (outputController.text.isEmpty ||
|
||||
qtyController.text.isEmpty ||
|
||||
product.value == null) {
|
||||
Get.snackbar('Erreur', 'Produit ou quantité invalide', backgroundColor: Colors.red);
|
||||
return;
|
||||
}
|
||||
|
||||
final now = DateTime.now().toIso8601String();
|
||||
final qty = double.tryParse(qtyController.text) ?? 1;
|
||||
final user = await client.fetchUserProfile();
|
||||
final selected = selectedOption.value;
|
||||
|
||||
double first = 0, second = 0, third = 0;
|
||||
String? firstDate, secondDate, thirdDate;
|
||||
Map<String, dynamic>? firstUser, secondUser, thirdUser;
|
||||
|
||||
switch (selected) {
|
||||
case "comptage1":
|
||||
first = qty;
|
||||
firstDate = now;
|
||||
firstUser = user;
|
||||
break;
|
||||
case "comptage2":
|
||||
second = qty;
|
||||
secondDate = now;
|
||||
secondUser = user;
|
||||
break;
|
||||
case "comptage3":
|
||||
third = qty;
|
||||
thirdDate = now;
|
||||
thirdUser = user;
|
||||
break;
|
||||
}
|
||||
|
||||
final line = InventoryLine(
|
||||
inventoryId: kInventoryId,
|
||||
productId: product.value!.id!,
|
||||
productName: product.value!.name ?? '',
|
||||
currentQty: qty,
|
||||
realQty: qty,
|
||||
unitId: product.value!.unit?.id ?? 2,
|
||||
description: selectedState.value,
|
||||
observation: serialNumberTxt.text,
|
||||
ticketId: "TICKET-${DateTime.now().millisecondsSinceEpoch}",
|
||||
rack: "A1",
|
||||
trackingNumberId: selectedTrackingNumber.value?.id,
|
||||
countingTypeSelect: 1,
|
||||
stockLocationId: selectedDepot.value?.id ?? 4,
|
||||
firstCounting: first,
|
||||
secondCounting: second,
|
||||
thirdCounting: third,
|
||||
firstCountingDate: firstDate,
|
||||
secondCountingDate: secondDate,
|
||||
thirdCountingDate: thirdDate,
|
||||
firstCountingByUser: firstUser,
|
||||
secondCountingByUser: secondUser,
|
||||
thirdCountingByUser: thirdUser,
|
||||
);
|
||||
|
||||
await client.saveInventoryLine(inventoryLine: line);
|
||||
|
||||
_resetForm();
|
||||
|
||||
Get.snackbar('Succès', 'Ligne enregistrée avec succès', backgroundColor: Colors.green);
|
||||
}
|
||||
|
||||
void _resetForm() {
|
||||
outputController.clear();
|
||||
labelController.clear();
|
||||
product.value = null;
|
||||
trackingNumbers.clear();
|
||||
selectedTrackingNumber.value = null;
|
||||
serialNumberTxt.text = "";
|
||||
}
|
||||
|
||||
Future<Uint8List?> fetchImageBytes(int imageId) => client.fetchImageBytes(imageId);
|
||||
|
||||
Future<void> fetchFamilleProduits() async {
|
||||
try {
|
||||
isLoading(true);
|
||||
final list = await client.getFamilleProduit();
|
||||
familleProduits.value = list ?? [];
|
||||
} catch (e) {
|
||||
familleProduits.clear();
|
||||
} finally {
|
||||
isLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> fetchSousFamilleProduits(int parentId) async {
|
||||
try {
|
||||
isLoading(true);
|
||||
final list = await client.getSousFamilleProduit(parentId);
|
||||
sousFamilleProduits.value = list ?? [];
|
||||
} catch (e) {
|
||||
sousFamilleProduits.clear();
|
||||
} finally {
|
||||
isLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
void onFamilleSelected(FamilleProduit? famille) async {
|
||||
selectedFamilleProduit.value = famille;
|
||||
selectedSousFamilleProduit.value = null;
|
||||
|
||||
if (famille?.id != null) {
|
||||
await fetchSousFamilleProduits(famille!.id!);
|
||||
} else {
|
||||
sousFamilleProduits.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void logout() async {
|
||||
if (await client.logout()) Get.off(LoginView());
|
||||
}
|
||||
}
|
||||
9
lib/controllers/simple_ui_controller.dart
Normal file
9
lib/controllers/simple_ui_controller.dart
Normal file
@@ -0,0 +1,9 @@
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class SimpleUIController extends GetxController {
|
||||
RxBool isObscure = true.obs;
|
||||
|
||||
isObscureActive() {
|
||||
isObscure.value = !isObscure.value;
|
||||
}
|
||||
}
|
||||
29
lib/controllers/theme_controller.dart
Normal file
29
lib/controllers/theme_controller.dart
Normal file
@@ -0,0 +1,29 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:get_storage/get_storage.dart';
|
||||
|
||||
class ThemeController extends GetxController {
|
||||
final _box = GetStorage();
|
||||
final _key = 'isDarkMode';
|
||||
|
||||
RxBool isDarkMode = false.obs;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
isDarkMode.value = _loadThemeFromBox();
|
||||
Get.changeThemeMode(isDarkMode.value ? ThemeMode.dark : ThemeMode.light);
|
||||
}
|
||||
|
||||
bool _loadThemeFromBox() => _box.read(_key) ?? false;
|
||||
|
||||
void _saveThemeToBox(bool value) => _box.write(_key, value);
|
||||
|
||||
ThemeMode get theme => isDarkMode.value ? ThemeMode.dark : ThemeMode.light;
|
||||
|
||||
void toggleTheme() {
|
||||
isDarkMode.value = !isDarkMode.value;
|
||||
_saveThemeToBox(isDarkMode.value);
|
||||
Get.changeThemeMode(isDarkMode.value ? ThemeMode.dark : ThemeMode.light);
|
||||
}
|
||||
}
|
||||
25
lib/controllers/user_controller.dart
Normal file
25
lib/controllers/user_controller.dart
Normal file
@@ -0,0 +1,25 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:inventory_app/service/axelor_client.dart';
|
||||
|
||||
class UserController extends GetxController {
|
||||
var isLoading = false.obs;
|
||||
var user = Rx<Map<String, dynamic>?>(null);
|
||||
|
||||
Future<void> loadUser() async {
|
||||
try {
|
||||
isLoading.value = true;
|
||||
final client = await AxelorClient.create();
|
||||
final profile = await client.fetchUserProfile();
|
||||
user.value = profile;
|
||||
} catch (e) {
|
||||
print("❌ Failed to load user: $e");
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Optional: clear user when logging out
|
||||
void clearUser() {
|
||||
user.value = null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user