fix logout

This commit is contained in:
2026-09-06 04:31:16 +07:00
parent 47c8688884
commit 286b207f27
6 changed files with 165 additions and 98 deletions

View File

@@ -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}');
}
}
} }

View File

@@ -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) => '.');
} }

View File

@@ -49,8 +49,31 @@ class _RootScreenState extends State<RootScreen> {
} }
Future<void> _logout() async { Future<void> _logout() async {
await Session.clear(); // try {
setState(() => _check = Session.load()); // 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();
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);

View File

@@ -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,

View File

@@ -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);
}
} }

View File

@@ -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);