fix logout
This commit is contained in:
@@ -44,24 +44,6 @@ class OdooClient {
|
|||||||
return _call('object', 'execute_kw', params);
|
return _call('object', 'execute_kw', params);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> executeKwdWithArgs(String model, String method,
|
|
||||||
{List<dynamic> fields = const [], Map<String, dynamic>? kwargs}) async {
|
|
||||||
_uid ??= await authenticate();
|
|
||||||
final args = [
|
|
||||||
|
|
||||||
db,
|
|
||||||
_uid,
|
|
||||||
password,
|
|
||||||
model,
|
|
||||||
method,
|
|
||||||
[],
|
|
||||||
fields,
|
|
||||||
|
|
||||||
if (kwargs != null) kwargs,
|
|
||||||
];
|
|
||||||
return _call('object', 'execute_kw', args);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<dynamic> executeSearchReadPayload(String model, List<String> fields) async {
|
Future<dynamic> executeSearchReadPayload(String model, List<String> fields) async {
|
||||||
int uid = await authenticate();
|
int uid = await authenticate();
|
||||||
final payload = OdooRpcHelper.searchRead(
|
final payload = OdooRpcHelper.searchRead(
|
||||||
@@ -145,4 +127,41 @@ class OdooClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void close() => _http.close();
|
void close() => _http.close();
|
||||||
|
|
||||||
|
Future<bool> logout(int uid, String db, String password) async {
|
||||||
|
|
||||||
|
int uid = await authenticate();
|
||||||
|
|
||||||
|
final payload = OdooRpcHelper.buildLogout(
|
||||||
|
service: "common",
|
||||||
|
db: db,
|
||||||
|
uid: uid,
|
||||||
|
password: password,
|
||||||
|
model: "", // or just use session logout
|
||||||
|
method: "", // some versions expose this
|
||||||
|
domain: [[]],
|
||||||
|
kwargs: {},
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
final resp = await _http
|
||||||
|
.post(_endpoint,
|
||||||
|
headers: {'Content-Type': 'application/json'}, body: jsonEncode(payload))
|
||||||
|
.timeout(const Duration(seconds: 25));
|
||||||
|
final json = jsonDecode(resp.body) as Map<String, dynamic>;
|
||||||
|
if (json.containsKey('error')) {
|
||||||
|
final err = json['error'] as Map?;
|
||||||
|
String? message = (err?['data'] as Map?)?['message'] as String? ??
|
||||||
|
(err?['message'] as String?) ??
|
||||||
|
'Unknown Odoo error';
|
||||||
|
throw Exception('Odoo error: $message');
|
||||||
|
}
|
||||||
|
return json['result'];
|
||||||
|
} on TimeoutException {
|
||||||
|
throw Exception('Timeout — pastikan server Odoo:18069 tercapai');
|
||||||
|
} on http.ClientException catch (e) {
|
||||||
|
throw Exception('Koneksi gagal: ${e.message}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -297,3 +297,4 @@ String _formatAbs(double v) {
|
|||||||
final a = v.abs().toStringAsFixed(0);
|
final a = v.abs().toStringAsFixed(0);
|
||||||
return a.replaceAllMapped(RegExp(r'\B(?=(\d{3})+(?!\d))'), (m) => '.');
|
return a.replaceAllMapped(RegExp(r'\B(?=(\d{3})+(?!\d))'), (m) => '.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -49,8 +49,31 @@ class _RootScreenState extends State<RootScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _logout() async {
|
Future<void> _logout() async {
|
||||||
|
// try {
|
||||||
|
// String base = await Session.readBase();
|
||||||
|
// String? db = await Session.readDb();
|
||||||
|
// String? pass = await Session.readPass();
|
||||||
|
// String? user = await Session.readUser();
|
||||||
|
//
|
||||||
|
// int uid = await Session.readUid() ?? 0;
|
||||||
|
//
|
||||||
|
// final c = OdooClient(
|
||||||
|
// baseUrl: base, db: db!, username: user!,
|
||||||
|
// password: pass!,
|
||||||
|
// );
|
||||||
|
//
|
||||||
|
// c.logout(uid, db, pass);
|
||||||
|
// } catch(e) {
|
||||||
|
// // leave it empty
|
||||||
|
// }
|
||||||
|
|
||||||
await Session.clear();
|
await Session.clear();
|
||||||
setState(() => _check = Session.load());
|
|
||||||
|
Navigator.pushAndRemoveUntil(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(builder: (_) => const RootScreen()),
|
||||||
|
(route) => false,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -177,7 +200,10 @@ class _LoginScreenState extends State<LoginScreen> {
|
|||||||
baseUrl: base, db: _db.text.trim(), username: _user.text.trim(),
|
baseUrl: base, db: _db.text.trim(), username: _user.text.trim(),
|
||||||
password: _pass.text,
|
password: _pass.text,
|
||||||
);
|
);
|
||||||
await c.authenticate(); // hit; lempar bila gagal
|
int _uid = await c.authenticate(); // hit; lempar bila gagal
|
||||||
|
if (_uid > 0) {
|
||||||
|
await Session.saveUid(_uid);
|
||||||
|
}
|
||||||
await Session.save(base, _db.text.trim(), _user.text.trim(), _pass.text);
|
await Session.save(base, _db.text.trim(), _user.text.trim(), _pass.text);
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
widget.onSuccess(base: base, db: _db.text.trim(), user: _user.text.trim(), pass: _pass.text);
|
widget.onSuccess(base: base, db: _db.text.trim(), user: _user.text.trim(), pass: _pass.text);
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ class OdooRpcHelper {
|
|||||||
static const String _jsonRpcVersion = "2.0";
|
static const String _jsonRpcVersion = "2.0";
|
||||||
static const String _method = "call";
|
static const String _method = "call";
|
||||||
|
|
||||||
/// Builds the correct JSON-RPC payload for any Odoo model method
|
static Map<String, dynamic> buildLogout({
|
||||||
static Map<String, dynamic> buildExecuteKw({
|
required String service,
|
||||||
required String db,
|
required String db,
|
||||||
required int uid,
|
required int uid,
|
||||||
required String password,
|
required String password,
|
||||||
@@ -21,7 +21,40 @@ class OdooRpcHelper {
|
|||||||
"jsonrpc": _jsonRpcVersion,
|
"jsonrpc": _jsonRpcVersion,
|
||||||
"method": _method,
|
"method": _method,
|
||||||
"params": {
|
"params": {
|
||||||
"service": "object",
|
"service": service,
|
||||||
|
"method": "logout",
|
||||||
|
"args": [
|
||||||
|
db,
|
||||||
|
uid,
|
||||||
|
password,
|
||||||
|
model,
|
||||||
|
method,
|
||||||
|
safeDomain,
|
||||||
|
kwargs,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"id": DateTime.now().millisecondsSinceEpoch,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
/// Builds the correct JSON-RPC payload for any Odoo model method
|
||||||
|
static Map<String, dynamic> buildExecuteKw({
|
||||||
|
required String service,
|
||||||
|
required String db,
|
||||||
|
required int uid,
|
||||||
|
required String password,
|
||||||
|
required String model,
|
||||||
|
required String method,
|
||||||
|
List<dynamic> domain = const [],
|
||||||
|
Map<String, dynamic> kwargs = const {},
|
||||||
|
}) {
|
||||||
|
// Make sure domain is always a list of lists (even if empty)
|
||||||
|
final safeDomain = domain.isEmpty ? [[]] : domain;
|
||||||
|
|
||||||
|
return {
|
||||||
|
"jsonrpc": _jsonRpcVersion,
|
||||||
|
"method": _method,
|
||||||
|
"params": {
|
||||||
|
"service": service,
|
||||||
"method": "execute_kw",
|
"method": "execute_kw",
|
||||||
"args": [
|
"args": [
|
||||||
db,
|
db,
|
||||||
@@ -57,6 +90,7 @@ class OdooRpcHelper {
|
|||||||
if (order != null) kwargs["order"] = order;
|
if (order != null) kwargs["order"] = order;
|
||||||
|
|
||||||
return buildExecuteKw(
|
return buildExecuteKw(
|
||||||
|
service: "object",
|
||||||
db: db,
|
db: db,
|
||||||
uid: uid,
|
uid: uid,
|
||||||
password: password,
|
password: password,
|
||||||
@@ -67,6 +101,37 @@ class OdooRpcHelper {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Map<String, dynamic> executeKw({
|
||||||
|
required String method,
|
||||||
|
required String db,
|
||||||
|
required int uid,
|
||||||
|
required String password,
|
||||||
|
required String model,
|
||||||
|
List<dynamic> domain = const [],
|
||||||
|
List<String> fields = const [],
|
||||||
|
int? limit,
|
||||||
|
int? offset,
|
||||||
|
String? order,
|
||||||
|
}) {
|
||||||
|
final kwargs = <String, dynamic>{};
|
||||||
|
|
||||||
|
if (fields.isNotEmpty) kwargs["fields"] = fields;
|
||||||
|
if (limit != null) kwargs["limit"] = limit;
|
||||||
|
if (offset != null) kwargs["offset"] = offset;
|
||||||
|
if (order != null) kwargs["order"] = order;
|
||||||
|
|
||||||
|
return buildExecuteKw(
|
||||||
|
service: "object",
|
||||||
|
db: db,
|
||||||
|
uid: uid,
|
||||||
|
password: password,
|
||||||
|
model: model,
|
||||||
|
method: method,
|
||||||
|
domain: domain,
|
||||||
|
kwargs: kwargs,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// Convenience wrapper for create
|
/// Convenience wrapper for create
|
||||||
static Map<String, dynamic> create({
|
static Map<String, dynamic> create({
|
||||||
required String db,
|
required String db,
|
||||||
@@ -76,6 +141,7 @@ class OdooRpcHelper {
|
|||||||
required Map<String, dynamic> values,
|
required Map<String, dynamic> values,
|
||||||
}) {
|
}) {
|
||||||
return buildExecuteKw(
|
return buildExecuteKw(
|
||||||
|
service: "object",
|
||||||
db: db,
|
db: db,
|
||||||
uid: uid,
|
uid: uid,
|
||||||
password: password,
|
password: password,
|
||||||
|
|||||||
@@ -4,12 +4,17 @@ const _kBase = 'odoo_base';
|
|||||||
const _kDb = 'odoo_db';
|
const _kDb = 'odoo_db';
|
||||||
const _kUser = 'odoo_user';
|
const _kUser = 'odoo_user';
|
||||||
const _kPass = 'odoo_pass';
|
const _kPass = 'odoo_pass';
|
||||||
|
const _kUid = 'odoo_uid';
|
||||||
|
|
||||||
/// Menyimpan konfigurasi login (url, db, user, password) secara aman
|
/// Menyimpan konfigurasi login (url, db, user, password) secara aman
|
||||||
/// + menyediakan OdooClient terautentikasi.
|
/// + menyediakan OdooClient terautentikasi.
|
||||||
class Session {
|
class Session {
|
||||||
static const _storage = FlutterSecureStorage();
|
static const _storage = FlutterSecureStorage();
|
||||||
|
|
||||||
|
static Future<void> saveUid(int uid) async {
|
||||||
|
await _storage.write(key: _kUid, value: uid.toString());
|
||||||
|
}
|
||||||
|
|
||||||
static Future<void> save(String baseUrl, String db, String user, String pass) async {
|
static Future<void> save(String baseUrl, String db, String user, String pass) async {
|
||||||
await _storage.write(key: _kBase, value: baseUrl);
|
await _storage.write(key: _kBase, value: baseUrl);
|
||||||
await _storage.write(key: _kDb, value: db);
|
await _storage.write(key: _kDb, value: db);
|
||||||
@@ -35,4 +40,28 @@ class Session {
|
|||||||
static Future<String?> readLastUrl() async {
|
static Future<String?> readLastUrl() async {
|
||||||
return _storage.read(key: _kBase);
|
return _storage.read(key: _kBase);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future<String> readBase() async {
|
||||||
|
final base = await _storage.read(key: _kBase);
|
||||||
|
if (base == null) throw Exception('URL tidak ditemukan');
|
||||||
|
return base;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<String?> readDb() async {
|
||||||
|
return _storage.read(key: _kDb);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<String?> readUser() async {
|
||||||
|
return _storage.read(key: _kUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<String?> readPass() async {
|
||||||
|
return _storage.read(key: _kPass);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<int?> readUid() async {
|
||||||
|
final uid = await _storage.read(key: _kUid);
|
||||||
|
if (uid == null) return null;
|
||||||
|
return int.tryParse(uid);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,31 +9,6 @@ class StockService {
|
|||||||
final OdooClient client;
|
final OdooClient client;
|
||||||
|
|
||||||
// ---- Saham ----
|
// ---- Saham ----
|
||||||
// Future<List<Saham>> listSahamOld() async {
|
|
||||||
// final res = await client.executeKwd(
|
|
||||||
// 'saat.saham',
|
|
||||||
// 'search_read',
|
|
||||||
// args: [
|
|
||||||
// "saat.saham",
|
|
||||||
// "search_read",
|
|
||||||
// [[]],
|
|
||||||
// {'fields': ['name', 'perusahaan', 'sector'], "limit": 100, "offset": 0, "order": "name asc"},
|
|
||||||
// ],
|
|
||||||
// );
|
|
||||||
// return (res as List).map((e) => Saham.fromOdoo(e as Map<String, dynamic>)).toList();
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// Future<List<Saham>> listSaham() async {
|
|
||||||
// final res = await client.executeKwdWithArgs(
|
|
||||||
// 'saat.saham',
|
|
||||||
// 'search_read',
|
|
||||||
// fields: [
|
|
||||||
// {'fields': ['name', 'perusahaan', 'sector'],}
|
|
||||||
// ]
|
|
||||||
// );
|
|
||||||
// return (res as List).map((e) => Saham.fromOdoo(e as Map<String, dynamic>)).toList();
|
|
||||||
// }
|
|
||||||
|
|
||||||
Future<List<Saham>> listSaham() async {
|
Future<List<Saham>> listSaham() async {
|
||||||
final fields = ['name', 'perusahaan', 'sector'];
|
final fields = ['name', 'perusahaan', 'sector'];
|
||||||
final res = await client.executeSearchReadPayload("saat.saham", fields);
|
final res = await client.executeSearchReadPayload("saat.saham", fields);
|
||||||
@@ -41,39 +16,6 @@ class StockService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ---- Portofolio ----
|
// ---- Portofolio ----
|
||||||
// Future<List<Portfolio>> listPortofolio() async {
|
|
||||||
// final res = await client.executeKwd(
|
|
||||||
// 'saat.portofolio',
|
|
||||||
// 'search_read',
|
|
||||||
// args: [
|
|
||||||
// [],
|
|
||||||
// {
|
|
||||||
// 'fields': [
|
|
||||||
// 'saham_id', 'sisa_lembar', 'avg_cost', 'harga_sekarang',
|
|
||||||
// 'nilai_sekarang', 'realized_pl', 'unrealized_pl',
|
|
||||||
// ]
|
|
||||||
// },
|
|
||||||
// ],
|
|
||||||
// );
|
|
||||||
// return (res as List).map((e) => Portfolio.fromOdoo(e as Map<String, dynamic>)).toList();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Future<List<Portfolio>> listPortofolio() async {
|
|
||||||
// final res = await client.executeKwdWithArgs(
|
|
||||||
// 'saat.portofolio',
|
|
||||||
// 'search_read',
|
|
||||||
// fields: [
|
|
||||||
// {
|
|
||||||
// "fields": ["saham_id", "sisa_lembar", "avg_cost", "harga_sekarang",
|
|
||||||
// "nilai_sekarang", "realized_pl", "unrealized_pl"],
|
|
||||||
// "limit": 20,
|
|
||||||
// "offset": 0
|
|
||||||
// }
|
|
||||||
// ],
|
|
||||||
// );
|
|
||||||
// return (res as List).map((e) => Portfolio.fromOdoo(e as Map<String, dynamic>)).toList();
|
|
||||||
// }
|
|
||||||
|
|
||||||
Future<List<Portfolio>> listPortofolio() async {
|
Future<List<Portfolio>> listPortofolio() async {
|
||||||
final fields = [
|
final fields = [
|
||||||
'saham_id', 'sisa_lembar', 'avg_cost', 'harga_sekarang',
|
'saham_id', 'sisa_lembar', 'avg_cost', 'harga_sekarang',
|
||||||
@@ -84,22 +26,6 @@ class StockService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ---- Transaksi ----
|
// ---- Transaksi ----
|
||||||
// Future<List<Transaksi>> listTransaksi() async {
|
|
||||||
// final res = await client.executeKwdWithArgs(
|
|
||||||
// 'saat.transaksi',
|
|
||||||
// 'search_read',
|
|
||||||
// fields: [
|
|
||||||
// {
|
|
||||||
// 'fields': [
|
|
||||||
// 'tanggal', 'tipe', 'saham_id', 'volume', 'harga', 'fee', 'total'
|
|
||||||
// ],
|
|
||||||
// 'order': 'tanggal desc, id desc',
|
|
||||||
// },
|
|
||||||
// ],
|
|
||||||
// );
|
|
||||||
// return (res as List).map((e) => Transaksi.fromOdoo(e as Map<String, dynamic>)).toList();
|
|
||||||
// }
|
|
||||||
|
|
||||||
Future<List<Transaksi>> listTransaksi() async {
|
Future<List<Transaksi>> listTransaksi() async {
|
||||||
final fields = ['tanggal', 'tipe', 'saham_id', 'volume', 'harga', 'fee', 'total'];
|
final fields = ['tanggal', 'tipe', 'saham_id', 'volume', 'harga', 'fee', 'total'];
|
||||||
final res = await client.executeSearchReadPayload("saat.transaksi", fields);
|
final res = await client.executeSearchReadPayload("saat.transaksi", fields);
|
||||||
|
|||||||
Reference in New Issue
Block a user