Commit b4e53fb4 authored by Sai Srinivas's avatar Sai Srinivas
Browse files

15-05-2025 By Sai Srinivas

Finance Module
parent 9dd9376d
......@@ -4,9 +4,8 @@ import java.io.FileInputStream
plugins {
id("com.android.application")
id("kotlin-android")
id("com.google.gms.google-services")
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id("dev.flutter.flutter-gradle-plugin")
id("com.google.gms.google-services")
}
val localProperties = Properties()
val localPropertiesFile = rootProject.file("local.properties")
......@@ -72,12 +71,15 @@ flutter {
}
dependencies {
implementation("com.google.firebase:firebase-inappmessaging-display:20.4.0")
implementation("androidx.core:core-ktx:1.16.0")
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.5")
implementation(platform("com.google.firebase:firebase-bom:33.13.0"))
implementation("com.google.firebase:firebase-analytics")
implementation("com.google.firebase:firebase-auth-ktx:23.2.0")
implementation("com.google.firebase:firebase-messaging-ktx:23.4.0")
implementation("com.google.android.gms:play-services-base:18.5.0")
implementation("com.google.android.gms:play-services-location:21.3.0")
implementation("androidx.core:core-ktx:1.16.0")
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.5")
}
\ No newline at end of file
......@@ -5,8 +5,8 @@
android:required="false" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
......@@ -21,29 +21,29 @@
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<application
android:label="GEN ERP"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
android:icon="@mipmap/ic_launcher"
android:label="GEN ERP">
<activity
android:name=".MainActivity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:exported="true"
android:hardwareAccelerated="true"
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:screenOrientation="portrait"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme" />
<meta-data
android:name="android.webkit.WebView.EnableSafeBrowsing"
android:value="true"/>
android:value="true" />
<meta-data
android:name="com.google.android.webview.WebViewPackage"
android:value="com.google.android.webview" />
......@@ -53,28 +53,34 @@
<meta-data
android:name="firebase_analytics_collection_enabled"
android:value="false" />
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http" />
<data android:scheme="https" />
<data android:host="whizzdom.gengroup.in"/>
<data android:pathPrefix=""/>
<data android:host="whizzdom.gengroup.in" />
<data android:pathPrefix="" />
</intent-filter>
</activity>
<meta-data android:name="flutter_deeplinking_enabled" android:value="true"/>
<meta-data
android:name="flutter_deeplinking_enabled"
android:value="true" />
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
<meta-data android:name="com.google.android.geo.API_KEY"
android:value="AIzaSyCA06NWEP5D-z8WpebENgd4mSOqV-uXIUE"/>
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="AIzaSyCA06NWEP5D-z8WpebENgd4mSOqV-uXIUE" />
<provider
android:name="com.pichillilorenzo.flutter_inappwebview_android.InAppWebViewFileProvider"
......@@ -95,8 +101,8 @@
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
<action android:name="android.intent.action.PROCESS_TEXT" />
<data android:mimeType="text/plain" />
</intent>
</queries>
</manifest>
buildscript {
dependencies {
classpath("com.google.gms:google-services:4.3.15")
classpath("com.android.tools.build:gradle:8.6.0")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.0")
classpath("com.google.gms:google-services:4.4.2")
}
repositories {
google()
......
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_448_3319)">
<path d="M23.8465 11.9233C23.8465 18.5083 18.5083 23.8465 11.9233 23.8465C5.33817 23.8465 0 18.5083 0 11.9233C0 5.33817 5.33817 0 11.9233 0C18.5083 0 23.8465 5.33817 23.8465 11.9233Z" fill="#FFC107"/>
<path d="M14.361 17.3416C14.2028 17.3416 14.0444 17.2962 13.9037 17.2007L7.9419 13.136C7.64816 12.9345 7.51816 12.566 7.62335 12.2244C7.72834 11.883 8.04391 11.651 8.40037 11.651H10.8392C11.8854 11.651 12.7362 10.8001 12.7362 9.75417C12.7362 8.70821 11.8854 7.85735 10.8392 7.85735H8.40037C7.95162 7.85735 7.58742 7.49315 7.58742 7.0444C7.58742 6.59565 7.95162 6.23145 8.40037 6.23145H10.8392C12.7817 6.23145 14.3621 7.8117 14.3621 9.75417C14.3621 11.6325 12.8835 13.1729 11.029 13.2725L14.8206 15.8578C15.1914 16.1103 15.2866 16.6166 15.0342 16.9872C14.877 17.217 14.6212 17.3416 14.361 17.3416Z" fill="#FAFAFA"/>
<path d="M15.4464 7.85735H8.40072C7.95197 7.85735 7.58777 7.49315 7.58777 7.0444C7.58777 6.59565 7.95197 6.23145 8.40072 6.23145H15.4464C15.8951 6.23145 16.2593 6.59565 16.2593 7.0444C16.2593 7.49315 15.8951 7.85735 15.4464 7.85735Z" fill="#FAFAFA"/>
<path d="M15.446 10.8388H8.40035C7.9516 10.8388 7.5874 10.4746 7.5874 10.0258C7.5874 9.57709 7.9516 9.21289 8.40035 9.21289H15.446C15.8947 9.21289 16.2589 9.57709 16.2589 10.0258C16.2589 10.4746 15.8947 10.8388 15.446 10.8388Z" fill="#FAFAFA"/>
</g>
<defs>
<clipPath id="clip0_448_3319">
<rect width="23.8466" height="23.8466" fill="white"/>
</clipPath>
</defs>
</svg>
<svg width="25" height="25" viewBox="0 0 25 25" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_534_3064)">
<path d="M12.8177 24.0879C19.4452 24.0879 24.8177 18.7153 24.8177 12.0879C24.8177 5.46047 19.4452 0.0878906 12.8177 0.0878906C6.19033 0.0878906 0.817749 5.46047 0.817749 12.0879C0.817749 18.7153 6.19033 24.0879 12.8177 24.0879Z" fill="#009933"/>
<path opacity="0.3" d="M4.33262 20.5733C2.10476 18.3182 0.859695 15.2732 0.869318 12.1032C0.878941 8.93326 2.14247 5.89588 4.38398 3.65437C6.62549 1.41285 9.66287 0.149326 12.8328 0.139704C16.0028 0.130081 19.0478 1.37515 21.3029 3.60301L4.33262 20.5733Z" fill="white"/>
<path d="M11.11 16.3675H12.3955V8.80227L11.0177 10.0165L10.2902 9.16602L12.8098 6.54102H14.0631L14.0567 6.55377L14.0631 6.54739V16.3675H15.3426V17.6335H11.11V16.3675Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_534_3064">
<rect width="24" height="24" fill="white" transform="translate(0.817749 0.0878906)"/>
</clipPath>
</defs>
</svg>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_534_3069)">
<path d="M12 24C18.6274 24 24 18.6274 24 12C24 5.37258 18.6274 0 12 0C5.37258 0 0 5.37258 0 12C0 18.6274 5.37258 24 12 24Z" fill="#009933"/>
<path opacity="0.3" d="M3.51487 20.4854C1.28701 18.2303 0.0419465 15.1853 0.051569 12.0153C0.0611916 8.84537 1.32472 5.80799 3.56623 3.56648C5.80774 1.32496 8.84512 0.0614357 12.0151 0.0518132C15.185 0.0421906 18.23 1.28726 20.4851 3.51512L3.51487 20.4854Z" fill="white"/>
<path d="M9.34424 16.6077L11.7922 11.8936C12.6881 10.1724 12.8827 9.40025 12.8827 8.822C12.8827 7.91262 12.558 7.62725 11.9284 7.62725C11.1686 7.62725 10.8442 8.28312 10.8442 9.452V10.0816H9.54524C9.44274 9.64709 9.38407 9.20337 9.37011 8.75712C9.37011 7.63212 10.1299 6.40625 11.9284 6.40625C13.8634 6.40625 14.6557 7.33475 14.6557 8.91875C14.6557 9.82137 14.3441 10.5939 13.461 12.1134L11.2729 16.2699H14.5125V17.5947H9.34424V16.6077Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_534_3069">
<rect width="24" height="24" fill="white"/>
</clipPath>
</defs>
</svg>
class addPaymentRequestionResponse {
List<String>? requestingPurposes;
List<Accounts>? accounts;
List<PaymentModes>? paymentModes;
String? error;
String? message;
addPaymentRequestionResponse(
{this.requestingPurposes,
this.accounts,
this.paymentModes,
this.error,
this.message});
addPaymentRequestionResponse.fromJson(Map<String, dynamic> json) {
requestingPurposes = json['requesting_purposes'].cast<String>();
if (json['accounts'] != null) {
accounts = <Accounts>[];
json['accounts'].forEach((v) {
accounts!.add(new Accounts.fromJson(v));
});
}
if (json['payment_modes'] != null) {
paymentModes = <PaymentModes>[];
json['payment_modes'].forEach((v) {
paymentModes!.add(new PaymentModes.fromJson(v));
});
}
error = json['error'];
message = json['message'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['requesting_purposes'] = this.requestingPurposes;
if (this.accounts != null) {
data['accounts'] = this.accounts!.map((v) => v.toJson()).toList();
}
if (this.paymentModes != null) {
data['payment_modes'] =
this.paymentModes!.map((v) => v.toJson()).toList();
}
data['error'] = this.error;
data['message'] = this.message;
return data;
}
}
class Accounts {
String? id;
String? name;
Accounts({this.id, this.name});
Accounts.fromJson(Map<String, dynamic> json) {
id = json['id'];
name = json['name'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['id'] = this.id;
data['name'] = this.name;
return data;
}
}
class PaymentModes {
String? id;
String? name;
PaymentModes({this.id, this.name});
PaymentModes.fromJson(Map<String, dynamic> json) {
id = json['id'];
name = json['name'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['id'] = this.id;
data['name'] = this.name;
return data;
}
}
\ No newline at end of file
class financeDashboardPagesResponse {
String? error;
List<PagesAccessible>? pagesAccessible;
String? message;
financeDashboardPagesResponse(
{this.error, this.pagesAccessible, this.message});
financeDashboardPagesResponse.fromJson(Map<String, dynamic> json) {
error = json['error'];
if (json['pages_accessible'] != null) {
pagesAccessible = <PagesAccessible>[];
json['pages_accessible'].forEach((v) {
pagesAccessible!.add(new PagesAccessible.fromJson(v));
});
}
message = json['message'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['error'] = this.error;
if (this.pagesAccessible != null) {
data['pages_accessible'] =
this.pagesAccessible!.map((v) => v.toJson()).toList();
}
data['message'] = this.message;
return data;
}
}
class PagesAccessible {
int? id;
String? pageName;
String? mode;
PagesAccessible({this.id, this.pageName, this.mode});
PagesAccessible.fromJson(Map<String, dynamic> json) {
id = json['id'];
pageName = json['page_name'];
mode = json['mode'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['id'] = this.id;
data['page_name'] = this.pageName;
data['mode'] = this.mode;
return data;
}
}
import 'package:flutter/foundation.dart';
import 'package:generp/Models/financeModels/financeDashboardPagesResponse.dart';
import 'package:generp/Notifiers/HomeScreenNotifier.dart';
import 'package:generp/services/api_calling.dart';
import 'package:provider/provider.dart';
class Dashboardprovider extends ChangeNotifier{
List<PagesAccessible> _accessiblePagesList = [];
List<PagesAccessible> _accessiblePagesList2 = [];
List<PagesAccessible> get accessiblePagesList => _accessiblePagesList;
List<PagesAccessible> get accessiblePagesList2 => _accessiblePagesList2;
Future<void> DashboardPagesAPIFunction(context) async {
try{
var provider = Provider.of<HomescreenNotifier>(context,listen: false);
final data = await ApiCalling.financeDashboardPagesAPI(provider.empId, provider.session);
if(data!=null){
if(data.error=="0"){
_accessiblePagesList = data.pagesAccessible!;
notifyListeners();
}else{
}
}
}catch(e,s){
}
}
Future<void> addFormfinanceFormAccessPagesAPIFunction(context) async {
try{
var provider = Provider.of<HomescreenNotifier>(context,listen: false);
final data = await ApiCalling.addFormfinanceFormAccessPagesAPI(provider.empId, provider.session);
if(data!=null){
if(data.error=="0"){
_accessiblePagesList2 = data.pagesAccessible!;
notifyListeners();
}else{
}
}
}catch(e,s){
}
}
}
\ No newline at end of file
import 'dart:io';
import 'package:camera/camera.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_image_compress/flutter_image_compress.dart';
import 'package:generp/Notifiers/HomeScreenNotifier.dart';
import 'package:generp/Utils/commonServices.dart';
import 'package:generp/services/api_calling.dart';
import 'package:image_picker/image_picker.dart';
import 'package:provider/provider.dart';
import '../../Models/financeModels/addPaymentRequestionResponse.dart';
class Requestionlistprovider extends ChangeNotifier {
var res = {
"requesting_purposes": [
"Salary Advance",
"Incentive Advance",
"Tour Advance",
],
"accounts": [
{"id": "22", "name": "Super Admin"},
],
"payment_modes": [
{"id": "1", "name": "Cash"},
{"id": "2", "name": "Cheque"},
{"id": "3", "name": "RTGS"},
{"id": "4", "name": "NEFT"},
{"id": "5", "name": "IMPS"},
{"id": "6", "name": "UPI"},
{"id": "7", "name": "Online Portal"},
],
"error": "0",
"message": "Fetched Successfully",
};
TextEditingController reqPurposeController = TextEditingController();
TextEditingController descController = TextEditingController();
TextEditingController amountController = TextEditingController();
TextEditingController bankNameController = TextEditingController();
TextEditingController bankBranchController = TextEditingController();
TextEditingController bankAccNumberController = TextEditingController();
TextEditingController bankIfscController = TextEditingController();
TextEditingController bankAcHolderController = TextEditingController();
TextEditingController bankUpiController = TextEditingController();
List<Accounts> _accounts = [];
List<PaymentModes> _paymentModes = [];
List<String> _requestingPurposes = [];
Accounts? _selectedAccounts;
PaymentModes? _selectedPayment;
String? _selectReqPurpose;
String _paymentModeId = "";
String _paymentModeValue = "";
String _accountId = "";
String _reqPurpose = "";
String? selectAccountError;
String? reqPurposeError;
String? descriptionError;
String? amountError;
String? selectPaymentError;
String? bankNameError;
String? bankBranchError;
String? bankNumberError;
String? bankIFSCError;
String? bankHolderError;
String? UPIError;
String? FileError;
bool buttonEnabled = false;
// bool _submitClicked = false;
var _image_picked = 0;
final ImagePicker _picker = ImagePicker();
File? _image;
String get paymentModeId => _paymentModeId;
String get paymentModeValue => _paymentModeValue;
get image_picked => _image_picked;
File? get imagePath => _image;
String get accountId => _accountId;
String get reqPurpose => _reqPurpose;
String? get selectReqPurpose => _selectReqPurpose;
Accounts? get selectedAccount => _selectedAccounts;
PaymentModes? get selectedPayment => _selectedPayment;
List<Accounts> get accounts => _accounts;
List<PaymentModes> get paymentModes => _paymentModes;
List<String> get requestingPurposes => _requestingPurposes;
// bool get submitClicked => _submitClicked;
// set submitClicked(bool value){
// _submitClicked = value;
// notifyListeners();
// }
set selectedAccount(Accounts? value) {
_selectedAccounts = value;
_accountId = value!.id!;
selectAccountError = null;
notifyListeners();
}
set selectedPayment(PaymentModes? value) {
_selectedPayment = value;
_paymentModeId = value!.id!;
_paymentModeValue = value!.name!;
selectPaymentError = null;
notifyListeners();
}
set selectReqPurpose(String? value) {
_selectReqPurpose = value;
reqPurposeError = null;
notifyListeners();
}
set paymentModeId(String value) {
_paymentModeId = value;
notifyListeners();
}
set paymentModeValue(String value) {
_paymentModeValue = value;
notifyListeners();
}
set accountId(String value) {
_accountId = value;
notifyListeners();
}
set reqPurposeId(String value) {
_reqPurpose = value;
notifyListeners();
}
imgFromCamera(context) async {
// Capture a photo
try {
final XFile? galleryImage = await _picker.pickImage(
source: ImageSource.camera,
imageQuality: 50,
);
debugPrint("added");
_image = File(galleryImage!.path);
_image_picked = 1;
FileError = null;
notifyListeners();
} catch (e) {
debugPrint("mmmm: ${e.toString()}");
}
}
imgFromGallery(context) async {
// Pick an image
try {
final XFile? galleryImage = await _picker.pickImage(
source: ImageSource.gallery,
);
final bytes = (await galleryImage?.readAsBytes())?.lengthInBytes;
final kb = bytes! / 1024;
final mb = kb / 1024;
debugPrint("Jenny: bytes:$bytes, kb:$kb, mb: $mb");
_image = File(galleryImage!.path);
_image_picked = 1;
FileError = null;
notifyListeners();
// var file = FlutterImageCompress.compressWithFile(galleryImage!.path);
} catch (e) {
debugPrint("mmmm: ${e.toString()}");
}
}
Future<void> addPaymentRequestionViewAPI(context, mode) async {
try {
var homeProvider = Provider.of<HomescreenNotifier>(
context,
listen: false,
);
final data = await ApiCalling.addPaymentRequestionViewAPI(
homeProvider.empId,
homeProvider.session,
mode,
);
if (data != null) {
if (data.error == "0") {
_accounts = data.accounts!;
_paymentModes = data.paymentModes!;
_requestingPurposes = data.requestingPurposes!;
if (_selectedAccounts != null &&
!_accounts.contains(_selectedAccounts)) {
_selectedAccounts = null;
_accountId = "";
}
if (_selectedPayment != null &&
!_paymentModes.contains(_selectedPayment)) {
_selectedPayment = null;
_paymentModeId = "";
}
} else {}
}
} catch (e, s) {}
}
Future<void> addPaymentRequestionSubmitAPI(context, mode) async {
try {
// _submitClicked = true;
if (!validateForm(context, mode)) {
// _submitClicked = false;
return;
}
var homeProvider = Provider.of<HomescreenNotifier>(
context,
listen: false,
);
final data = await ApiCalling.addPaymentRequestionSubmitAPI(
homeProvider.empId,
homeProvider.session,
mode,
_accountId,
["self","admin"].contains(mode) ?_selectReqPurpose:reqPurposeController.text,
descController.text,
amountController.text,
_paymentModeId,
bankNameController.text,
bankBranchController.text,
bankAccNumberController.text,
bankIfscController.text,
bankAcHolderController.text,
bankUpiController.text,
_image
);
if (data != null) {
if (data['error'] == "0") {
print(data['error']=="0");
toast(context, "Added Successfully");
resetForm();
Navigator.pop(context,true);
notifyListeners();
} else {}
}
} catch (e, s) {}
}
void resetForm() {
reqPurposeController.clear();
descController.clear();
amountController.clear();
bankNameController.clear();
bankBranchController.clear();
bankAccNumberController.clear();
bankIfscController.clear();
bankAcHolderController.clear();
bankUpiController.clear();
_selectedAccounts = null;
_selectedPayment = null;
_selectReqPurpose = null;
_paymentModeId = "";
_paymentModeValue = "";
_accountId = "";
_reqPurpose = "";
_image = null;
_image_picked = 0;
// _submitClicked = false;
// Clear validation errors
selectAccountError = null;
reqPurposeError = null;
descriptionError = null;
amountError = null;
selectPaymentError = null;
bankNameError = null;
bankBranchError = null;
bankNumberError = null;
bankIFSCError = null;
bankHolderError = null;
UPIError = null;
FileError = null;
buttonEnabled = false;
notifyListeners();
}
bool validateForm(BuildContext context, String mode) {
// Reset all errors
selectAccountError = null;
reqPurposeError = null;
descriptionError = null;
amountError = null;
selectPaymentError = null;
bankNameError = null;
bankBranchError = null;
bankNumberError = null;
bankIFSCError = null;
bankHolderError = null;
UPIError = null;
FileError = null;
bool isValid = true;
if (_selectedAccounts == null || _accountId.isEmpty) {
selectAccountError = "Please select an account";
isValid = false;
}
if (["self", "admin"].contains(mode)) {
if (_selectReqPurpose == null || _selectReqPurpose!.isEmpty) {
reqPurposeError = "Please select a requisition purpose";
isValid = false;
}
} else {
if (reqPurposeController.text.trim().isEmpty) {
reqPurposeError = "Please enter a requisition purpose";
isValid = false;
}
}
if (descController.text.trim().isEmpty) {
descriptionError = "Please enter a description";
isValid = false;
}
if (amountController.text.trim().isEmpty) {
amountError = "Please enter an amount";
isValid = false;
}
if (_selectedPayment == null || _paymentModeId.isEmpty) {
selectPaymentError = "Please select a payment mode";
isValid = false;
}
if (["Cheque", "RTGS", "IMPS", "NEFT"].contains(_paymentModeValue)) {
if (bankNameController.text.trim().isEmpty) {
bankNameError = "Please enter bank name";
isValid = false;
}
if (bankBranchController.text.trim().isEmpty) {
bankBranchError = "Please enter bank branch";
isValid = false;
}
if (bankAccNumberController.text.trim().isEmpty) {
bankNumberError = "Please enter account number";
isValid = false;
}
if (bankIfscController.text.trim().isEmpty) {
bankIFSCError = "Please enter IFSC code";
isValid = false;
}
if (bankAcHolderController.text.trim().isEmpty) {
bankHolderError = "Please enter account holder name";
isValid = false;
}
}
if (_paymentModeValue == "UPI") {
if (bankUpiController.text.trim().isEmpty) {
UPIError = "Please enter UPI ID";
isValid = false;
}
}
if (_image_picked == 0) {
FileError = "Please attach a file";
isValid = false;
}
buttonEnabled = isValid;
notifyListeners();
return isValid;
}
void updateReqPupose(String value) {
reqPurposeError = null;
notifyListeners();
}
void updateDescription(String value) {
descriptionError = null;
notifyListeners();
}
void updateAmount(String value) {
amountError = null;
notifyListeners();
}
void updateBankName(String value) {
bankNameError = null;
notifyListeners();
}
void updateBankBranch(String value) {
bankBranchError = null;
notifyListeners();
}
void updateNumber(String value) {
bankNumberError = null;
notifyListeners();
}
void updateIFSC(String value) {
bankIFSCError = null;
notifyListeners();
}
void updateHolder(String value) {
bankHolderError = null;
notifyListeners();
}
void updateUPI(String value) {
UPIError = null;
notifyListeners();
}
}
......@@ -15,6 +15,7 @@ class Loginnotifier extends ChangeNotifier {
// Private variables
String _emailError = '';
String _passwordError = '';
int _loginStatus = 0;
bool _isLoading = false;
int? _loginErrorCode;
bool _pwdVisible = false;
......@@ -38,7 +39,7 @@ class Loginnotifier extends ChangeNotifier {
String get deviceId => _deviceId;
String get deviceDetails => _deviceDetails;
String get AndroidDevId => _androidId;
int get loginStatus => _loginStatus;
Future<void> initAndroidId() async {
......@@ -174,12 +175,13 @@ class Loginnotifier extends ChangeNotifier {
debugPrint("${data.error} login error here");
if (data.error == 0) {
_loginStatus = 0;
SharedpreferencesService().saveInt("loginStatus", 1);
SharedpreferencesService().saveString("UserId", data.userId!);
SharedpreferencesService().saveString("UserName", data.name!);
SharedpreferencesService().saveString("UserEmail", data.emailId!);
SharedpreferencesService().saveString("Session_id", data.sessionId!);
var roles = data.permissions!.toString();
SharedpreferencesService().saveString("roles", roles);
......@@ -187,6 +189,7 @@ class Loginnotifier extends ChangeNotifier {
context,
MaterialPageRoute(builder: (context) => MyHomePage()),
);
notifyListeners();
} else if (data.error == 1) {
toast(context,
"You are not authorized to login in this device !");
......
......@@ -4,11 +4,14 @@ import 'dart:math';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:generp/Models/VersionsResponse.dart';
import 'package:generp/Notifiers/HomeScreenNotifier.dart';
import 'package:generp/Notifiers/loginNotifier.dart';
import 'package:generp/Utils/SharedpreferencesService.dart';
import 'package:generp/screens/HomeScreen.dart';
import 'package:generp/screens/LoginScreen.dart';
import 'package:generp/screens/UpdatePasswordScreen.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher.dart';
import '../services/api_calling.dart';
......@@ -29,19 +32,31 @@ class SplashVersionNotifier extends ChangeNotifier {
"Package Name": packageInfo.packageName,
"Build Number": packageInfo.buildNumber,
};
if (kDebugMode) {
print("Package Info: $_packagedetails");
}
notifyListeners();
}
Future<void> handleVersionCheck(context) async {
var loginStatus = await SharedpreferencesService().getInt("loginstatus");
if (_packagedetails.isEmpty) {
await initPackageInfo();
}
var loginProvider = Provider.of<Loginnotifier>(context,listen: false);
var loginStatus = await SharedpreferencesService().getInt("loginStatus");
print("login status: ${loginStatus}");
print("login status provider: ${loginProvider.loginStatus}");
try {
final data = await ApiCalling.checkAppVersionApi();
if (data != null) {
final int currentBuild =
int.parse(_packagedetails["Build Number"] ?? "0") ?? 0;
print(currentBuild);
if (kDebugMode) {
print("Current Build: $currentBuild");
print("Server Response: $data");
}
if (Platform.isAndroid &&
currentBuild < (data.latestVersionCode ?? 0)) {
......
......@@ -23,6 +23,8 @@ import 'package:generp/Notifiers/ServiceEngineerDashboardProvider.dart';
import 'package:generp/Notifiers/TodayMontlyVisitsProvider.dart';
import 'package:generp/Notifiers/UpdatePasswordProvider.dart';
import 'package:generp/Notifiers/VisitDetailsProvider.dart';
import 'package:generp/Notifiers/financeProvider/DashboardProvider.dart';
import 'package:generp/Notifiers/financeProvider/RequestionListProvider.dart';
import 'package:generp/Notifiers/loginNotifier.dart';
import 'package:generp/Notifiers/scanLoginProvider.dart';
import 'package:generp/Notifiers/splashVersionNotifier.dart';
......@@ -74,6 +76,20 @@ Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
void main() async {
WidgetsFlutterBinding.ensureInitialized();
if (Platform.isAndroid) {
await Firebase.initializeApp(
options: FirebaseOptions(
apiKey: "AIzaSyBmkmKdYfBt2n5QRlmZJ9MV_Amh9xR3UOY",
appId: "1:329382566569:android:26dc8519537b04deff67b8",
messagingSenderId: "329382566569",
projectId: "generp-fe09d",
),
);
} else if (Platform.isIOS) {
await Firebase.initializeApp();
}
if (kDebugMode) {
if (Firebase.apps.isNotEmpty) {
print("Firebase is initialized");
......@@ -82,19 +98,6 @@ void main() async {
}
}
// if (Platform.isAndroid) {
// await Firebase.initializeApp(
// options: FirebaseOptions(
// apiKey: "AIzaSyBmkmKdYfBt2n5QRlmZJ9MV_Amh9xR3UOY",
// appId: "1:329382566569:android:26dc8519537b04deff67b8",
// messagingSenderId: "329382566569",
// projectId: "generp-fe09d",
// ),
// );
// } else if (Platform.isIOS) {
await Firebase.initializeApp();
// }
FirebaseMessaging messaging = FirebaseMessaging.instance;
NotificationSettings settings = await messaging.requestPermission(
......@@ -203,6 +206,9 @@ class MyApp extends StatelessWidget {
ChangeNotifierProvider(create: (_) => Paymentdetailsprovider()),
ChangeNotifierProvider(create: (_) => Generatordetailsprovider()),
ChangeNotifierProvider(create: (_) => Nearbygeneratorsprovider()),
///finance
ChangeNotifierProvider(create: (_) => Dashboardprovider(),),
ChangeNotifierProvider(create: (_) => Requestionlistprovider(),),
],
child: Builder(
builder: (BuildContext context) {
......
......@@ -12,6 +12,7 @@ import 'package:generp/screens/LoginScreen.dart';
import 'package:generp/screens/ScannerLogin.dart';
import 'package:generp/screens/WebERPScreen.dart';
import 'package:generp/screens/WebWhizzdomScreen.dart';
import 'package:generp/screens/finance/financeDashboard.dart';
import 'package:generp/screens/genTracker/GenTrackerDashboard.dart';
import 'package:generp/screens/serviceEngineer/NearbyGenerators.dart';
import 'package:generp/screens/serviceEngineer/serviceEngineerDashboard.dart';
......@@ -125,6 +126,8 @@ class _MyHomePageState extends State<MyHomePage> {
"Nearby",
"Inventory",
"Whizzdom",
// "CRM",
// "Finance",
];
final icons = [
"assets/svg/home_icons_1.svg",
......@@ -134,8 +137,20 @@ class _MyHomePageState extends State<MyHomePage> {
"assets/svg/home_icons_5.svg",
"assets/svg/home_icons_6.svg",
"assets/svg/home_icons_81.svg",
// "assets/svg/home_icons_8.svg",
// "assets/svg/home_icons_8.svg",
];
final requiredRoles = [
"430",
"431",
"434",
"433",
"433",
"432",
"431",
// "431",
// "431",
];
final requiredRoles = ["430", "431", "434", "433", "433", "432", "431"];
final filteredItems = <Map<String, String>>[];
for (int i = 0; i < names.length; i++) {
......@@ -425,9 +440,18 @@ class _MyHomePageState extends State<MyHomePage> {
requestGpsPermission();
}
break;
// case "CRM":
// //res = await Navigator.push(context, MaterialPageRoute(builder: (context)=>CRMScreen()));
// break;
case "CRM":
//res = await Navigator.push(context, MaterialPageRoute(builder: (context)=>CRMScreen()));
break;
case "Finance":
// res = await Navigator.push(
// context,
// MaterialPageRoute(
// builder:
// (context) => Financedashboard(),
// ),
// );
break;
default:
print("111");
break;
......
......@@ -613,6 +613,7 @@ class _LoginScreenState extends State<LoginScreen>
],
),
),
// bottomNavigationBar: ,
),
);
}
......
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:generp/Notifiers/financeProvider/DashboardProvider.dart';
import 'package:generp/Utils/app_colors.dart';
import 'package:generp/Utils/commonWidgets.dart';
import 'package:generp/screens/finance/paymentRequestionListsByMode.dart';
import 'package:provider/provider.dart';
class Financedashboard extends StatefulWidget {
const Financedashboard({super.key});
@override
State<Financedashboard> createState() => _FinancedashboardState();
}
class _FinancedashboardState extends State<Financedashboard> {
@override
void initState() {
// TODO: implement initState
super.initState();
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
var provider = Provider.of<Dashboardprovider>(context, listen: false);
provider.DashboardPagesAPIFunction(context);
provider.addFormfinanceFormAccessPagesAPIFunction(context);
});
}
@override
Widget build(BuildContext context) {
return Consumer<Dashboardprovider>(
builder: (context, provider, child) {
return WillPopScope(
onWillPop: () => onBackPressed(context),
child: Scaffold(
resizeToAvoidBottomInset: true,
backgroundColor: AppColors.scaffold_bg_color,
appBar: appbar(context, "Finance"),
body: Container(
child: SingleChildScrollView(
child: Column(
children: [
ListView.builder(
itemCount: provider.accessiblePagesList.length,
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemBuilder: (context, index) {
Widget? SvgIcon;
switch (provider.accessiblePagesList[index].mode) {
case "apr_lvl1":
SvgIcon = SvgPicture.asset(
"assets/svg/fin_lv1.svg",
);
break;
case "apr_lvl2":
SvgIcon = SvgPicture.asset(
"assets/svg/fin_lv2.svg",
);
break;
default:
SvgIcon = SvgPicture.asset("assets/svg/fin_ic.svg");
break;
}
return Container(
margin: EdgeInsets.symmetric(
horizontal: 5,
vertical: 5,
),
padding: EdgeInsets.symmetric(
horizontal: 10,
vertical: 15,
),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(14),
),
child: Row(
children: [
Expanded(flex: 1, child: SvgIcon),
Expanded(
flex: 5,
child: Text(
"${provider.accessiblePagesList[index].pageName}",
),
),
Expanded(
flex: 1,
child: SvgPicture.asset(
"assets/svg/arrow_right_new.svg",
),
),
],
),
);
},
),
],
),
),
),
floatingActionButtonLocation:
FloatingActionButtonLocation.centerFloat,
floatingActionButton: InkResponse(
onTap: () {
_showPaymentOptionsSheet(context);
},
child: Container(
height: 45,
alignment: Alignment.center,
margin: EdgeInsets.symmetric(horizontal: 10),
padding: EdgeInsets.symmetric(horizontal: 10, vertical: 5),
decoration: BoxDecoration(
color: AppColors.app_blue,
borderRadius: BorderRadius.circular(15),
),
child: Text(
"Add Payment",
style: TextStyle(
fontSize: 15,
fontFamily: "JakartaMedium",
color: Colors.white,
),
),
),
),
),
);
},
);
}
Future<void> _showPaymentOptionsSheet(BuildContext context) {
return showModalBottomSheet(
useSafeArea: true,
isDismissible: true,
isScrollControlled: true,
showDragHandle: true,
backgroundColor: Colors.white,
enableDrag: true,
context: context,
builder: (context) {
return StatefulBuilder(
builder: (context, setState) {
return SafeArea(
child: Consumer<Dashboardprovider>(
builder: (context, provider, child) {
return Container(
margin: EdgeInsets.only(
bottom: 15,
left: 15,
right: 15,
top: 10,
),
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(height: 15),
...List.generate(provider.accessiblePagesList2.length, (index) {
print(provider.accessiblePagesList2[index].mode);
List<String> mode_lst = [
"self",
"other",
"process",
"admin",
"direct",
];
final Headingnames = [
"Add Self Payment Requisition",
"Add Other Payment Requisition",
"Add Processed Payment Requisition",
"Add Admin Payment Requisition",
"Add Direct Payment",
];
return ListTile(
onTap: () {
Navigator.pop(context);
Navigator.push(
context,
MaterialPageRoute(
builder:
(context) =>
Paymentrequestionlistsbymode(
mode: "${provider.accessiblePagesList2[index].mode}",
pageTitleName: "${provider.accessiblePagesList2[index].pageName}",
),
),
);
},
leading: SvgPicture.asset(
"assets/svg/fin_ic.svg",
),
title: Text(
"${provider.accessiblePagesList2[index].pageName}",
style: TextStyle(
fontSize: 14,
fontFamily: "JakartaMedium",
),
),
trailing: SvgPicture.asset(
"assets/svg/arrow_right_new.svg",
),
);
}),
],
),
),
);
},
),
);
},
);
},
);
}
}
This diff is collapsed.
......@@ -37,8 +37,10 @@ import '../Models/UpdateComplaintResponse.dart';
import '../Models/UpdatePasswordResponse.dart';
import '../Models/VersionsResponse.dart';
import '../Models/ViewVisitDetailsResponse.dart';
import '../Models/financeModels/addPaymentRequestionResponse.dart';
import '../Models/generatorComplaintResponse.dart';
import '../Models/loadGeneratorDetailsResponse.dart';
import '../Models/financeModels/financeDashboardPagesResponse.dart';
import '../Utils/commonServices.dart';
class ApiCalling {
......@@ -1104,4 +1106,132 @@ class ApiCalling {
return null;
}
}
///finance Module
static Future<financeDashboardPagesResponse?> financeDashboardPagesAPI(
empId,
session,
) async {
try {
Map<String, String> data = {
'emp_id': (empId).toString(),
'session_id': (session).toString(),
};
final res = await post(data, financeDashboardPagesUrl, {});
if (res != null) {
debugPrint(res.body);
return financeDashboardPagesResponse.fromJson(jsonDecode(res.body));
} else {
debugPrint("Null Response");
return null;
}
} catch (e) {
debugPrint('hello bev=bug $e ');
return null;
}
}
static Future<financeDashboardPagesResponse?> addFormfinanceFormAccessPagesAPI(
empId,
session,
) async {
try {
Map<String, String> data = {
'emp_id': (empId).toString(),
'session_id': (session).toString(),
};
final res = await post(data, financeAddFormPagesAccessUrl, {});
if (res != null) {
debugPrint(res.body);
return financeDashboardPagesResponse.fromJson(jsonDecode(res.body));
} else {
debugPrint("Null Response");
return null;
}
} catch (e) {
debugPrint('hello bev=bug $e ');
return null;
}
}
static Future<addPaymentRequestionResponse?> addPaymentRequestionViewAPI(
empId,
session,
mode,
) async {
try {
Map<String, String> data = {
'emp_id': (empId).toString(),
'session_id': (session).toString(),
'mode': (mode).toString(),
};
final res = await post(data, addPaymentRequestionViewUrl, {});
if (res != null) {
debugPrint(res.body);
return addPaymentRequestionResponse.fromJson(jsonDecode(res.body));
} else {
debugPrint("Null Response");
return null;
}
} catch (e) {
debugPrint('hello bev=bug $e ');
return null;
}
}
static addPaymentRequestionSubmitAPI(
emp_id,
session_id,
type,
account_id,
requesting_purpose,
description,
amount,
payment_mode_id,
bank_name,
bank_branch_name,
bank_acc_number,
bank_ifsc_code,
acc_holder_name,
bank_upi_id,
attachment
) async {
try {
Map<String, String> data = {
'emp_id': emp_id.toString(),
'session_id': session_id.toString(),
'type': type.toString(),
'account_id': account_id.toString(),
'requesting_purpose': requesting_purpose.toString(),
'description': description.toString(),
'amount': amount.toString(),
'payment_mode_id': payment_mode_id.toString(),
'bank_name': bank_name.toString(),
'bank_branch_name': bank_branch_name.toString(),
'bank_acc_number': bank_acc_number.toString(),
'bank_ifsc_code': bank_ifsc_code.toString(),
'acc_holder_name': acc_holder_name.toString(),
'bank_upi_id': bank_upi_id.toString(),
};
var res;
if (attachment != null) {
res = await postImageNew(data, {}, addPaymentRequestionSubmitUrl, attachment,"attachment");
res = jsonDecode(res);
} else {
res = await post(data, addPaymentRequestionSubmitUrl, {});
res = jsonDecode(res);
}
if (res != null) {
return res;
} else {
debugPrint("Null Response");
return null;
}
} catch (e) {
debugPrint('hello bev=bug $e ');
return null;
}
}
}
const baseUrl = "https://erp.gengroup.in/ci/app/";
const baseUrl_test = "https://erp.gengroup.in/ci/app/Dheeraj/";
// var WEB_SOCKET_URL = "wss://ws.erp.gengroup.in/?type=user&route=employe_live_location_update&session_id=${Sessionid}";
const getAppVersionUrl = "https://erp.gengroup.in/ci/assets/appversion.json";
......@@ -50,3 +51,10 @@ const technicianComplaintDetailsUrl= "${baseUrl}home/technician_complaint_detail
const technicianComplaintFollowUpUrl= "${baseUrl}home/technician_complaint_followup_list";
const technicianAddContactUrl= "${baseUrl}home/technician_add_contact";
const technicianUpdateVisitUrl= "${baseUrl}home/technician_update_visit";
///finance Module
const financeDashboardPagesUrl = "${baseUrl_test}finance_accessible_pages";
const financeAddFormPagesAccessUrl = "${baseUrl_test}finance_add_form_page_access";
const addPaymentRequestionViewUrl = "${baseUrl_test}add_payment_requisiton_view";
const addPaymentRequestionSubmitUrl = "${baseUrl_test}add_payment_requsition_submit";
const paymentRequestionListUrl = "${baseUrl_test}payment_requsition_list";
......@@ -3,37 +3,50 @@ import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:http/http.dart' as http;
Future<http.Response?> post(Map<String,dynamic> Body,
api_url,Map<String,String> Headers) async{
Future<http.Response?> post(
Map<String, dynamic> Body,
api_url,
Map<String, String> Headers,
) async {
http.Response? response;
try{
response = await http.post(Uri.parse(api_url),headers: Headers,body: Body);
try {
response = await http.post(
Uri.parse(api_url),
headers: Headers,
body: Body,
);
return response;
}on Exception catch (e,s){
} on Exception catch (e, s) {
print(e);
print(s);
}
return response;
}
Future<http.Response?> get(api_url,Map<String,String> Headers) async{
Future<http.Response?> get(api_url, Map<String, String> Headers) async {
http.Response? response;
try{
response = await http.get(Uri.parse(api_url),headers: Headers);
try {
response = await http.get(Uri.parse(api_url), headers: Headers);
return response;
}on Exception catch (e,s){
} on Exception catch (e, s) {
print(e);
print(s);
}
return response;
}
Future<String?> postImage(Map<String, String> body, String urlLink,
Map<String, String> headers, File image) async {
Future<String?> postImage(
Map<String, String> body,
String urlLink,
Map<String, String> headers,
File image,
) async {
try {
var req = http.MultipartRequest('POST', Uri.parse(urlLink));
req.headers.addAll(headers);
req.files.add(await http.MultipartFile.fromPath('check_in_pic', image.path));
req.files.add(
await http.MultipartFile.fromPath('check_in_pic', image.path),
);
req.fields.addAll(body);
var res = await req.send();
......@@ -52,12 +65,18 @@ Future<String?> postImage(Map<String, String> body, String urlLink,
}
}
Future<String?> postImage2(Map<String, String> body,Map<String, String> headers, String urlLink,
File image) async {
Future<String?> postImage2(
Map<String, String> body,
Map<String, String> headers,
String urlLink,
File image,
) async {
try {
var req = http.MultipartRequest('POST', Uri.parse(urlLink));
req.headers.addAll(headers);
req.files.add(await http.MultipartFile.fromPath('check_out_pic', image.path));
req.files.add(
await http.MultipartFile.fromPath('check_out_pic', image.path),
);
req.fields.addAll(body);
var res = await req.send();
......@@ -76,12 +95,18 @@ Future<String?> postImage2(Map<String, String> body,Map<String, String> headers,
}
}
Future<String?> postImage3(Map<String, String> body,Map<String, String> headers, String urlLink,
File image) async {
Future<String?> postImage3(
Map<String, String> body,
Map<String, String> headers,
String urlLink,
File image,
) async {
try {
var req = http.MultipartRequest('POST', Uri.parse(urlLink));
req.headers.addAll(headers);
req.files.add(await http.MultipartFile.fromPath('payment_proof', image.path));
req.files.add(
await http.MultipartFile.fromPath('payment_proof', image.path),
);
req.fields.addAll(body);
var res = await req.send();
......@@ -100,8 +125,12 @@ Future<String?> postImage3(Map<String, String> body,Map<String, String> headers,
}
}
Future<String?> postImage4 (Map<String, String> body,Map<String, String> headers, String urlLink,
File image) async {
Future<String?> postImage4(
Map<String, String> body,
Map<String, String> headers,
String urlLink,
File image,
) async {
try {
var req = http.MultipartRequest('POST', Uri.parse(urlLink));
req.headers.addAll(headers);
......@@ -124,8 +153,41 @@ Future<String?> postImage4 (Map<String, String> body,Map<String, String> headers
}
}
Future<String?> PostMultipleImages(Map<String, String> body, String urlLink,
Map<String, String> headers, List<http.MultipartFile> newList) async {
Future<String?> postImageNew(
Map<String, String> body,
Map<String, String> headers,
String urlLink,
File image,
req_field
) async {
try {
var req = http.MultipartRequest('POST', Uri.parse(urlLink));
req.headers.addAll(headers?? {});
req.files.add(await http.MultipartFile.fromPath(req_field, image.path));
req.fields.addAll(body?? {});
var res = await req.send();
final resBody = await res.stream.bytesToString();
if (res.statusCode >= 200 && res.statusCode < 300) {
print("**** $resBody .... ${res.statusCode}");
return resBody;
} else {
print("error: ${res.reasonPhrase}");
return null;
}
} catch (e) {
debugPrint(e.toString());
return null;
}
}
Future<String?> PostMultipleImages(
Map<String, String> body,
String urlLink,
Map<String, String> headers,
List<http.MultipartFile> newList,
) async {
try {
var req = http.MultipartRequest('POST', Uri.parse(urlLink));
req.headers.addAll(headers);
......@@ -146,4 +208,4 @@ Future<String?> PostMultipleImages(Map<String, String> body, String urlLink,
debugPrint(e.toString());
return null;
}
}
\ No newline at end of file
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment