import 'dart:convert';
import 'package:flutter/foundation.dart';
import '../../../core/auth/auth_store.dart';
import '../../../core/storage/local_storage.dart';
import '../../../core/sync/master_data_sync_repository.dart';
import '../../../core/web/web_office_api.dart';
class Customer {
const Customer({
required this.id,
required this.name,
required this.contact,
required this.email,
required this.phone,
required this.address,
this.rowVersion = 0,
this.syncStatus = EntitySyncStatus.localOnly,
this.syncError,
});
final String id;
final String name;
final String contact;
final String email;
final String phone;
final String address;
final int rowVersion;
final EntitySyncStatus syncStatus;
final String? syncError;
Map<String, Object?> toJson() => {
'id': id,
'name': name,
'contact': contact,
'email': email,
'phone': phone,
'address': address,
'row_version': rowVersion,
'sync_status': syncStatus.value,
'sync_error': syncError,
};
factory Customer.fromJson(Map<String, dynamic> json) => Customer(
id: json['id'] as String,
name: json['name'] as String,
contact: json['contact'] as String? ?? '',
email: json['email'] as String? ?? '',
phone: json['phone'] as String? ?? '',
address: json['address'] as String? ?? '',
rowVersion: json['row_version'] as int? ?? 0,
syncStatus: EntitySyncStatusValue.parse(json['sync_status']),
syncError: json['sync_error'] as String?,
);
}
class CustomerStore extends ChangeNotifier {
CustomerStore._();
static final instance = CustomerStore._();
static const _key = 'machweis_customers_v1';
final List<Customer> _customers = [];
List<Customer> get customers => List.unmodifiable(_customers);
Future<void> initialize() async {
if (kIsWeb && AuthStore.instance.session != null) {
try {
final rows = await WebOfficeApi.instance.listCustomers();
_customers
..clear()
..addAll(rows.map(Customer.fromJson));
} on Object {
// Keep the last loaded web snapshot visible on transient errors.
}
notifyListeners();
return;
}
if (LocalStorage.instance.usesRelationalStore) {
final repository = LocalCustomerRepository();
final stored = await repository.readAll();
if (stored.isNotEmpty || await repository.hasAnyRows()) {
_customers
..clear()
..addAll(stored);
notifyListeners();
return;
}
if (AuthStore.instance.session != null) {
_customers.clear();
notifyListeners();
return;
}
await _migrateLegacy(repository);
notifyListeners();
return;
}
final stored = await LocalStorage.instance.getString(_key);
if (stored == null) return;
try {
final values = jsonDecode(stored) as List<dynamic>;
_customers
..clear()
..addAll(
values.map(
(value) => Customer.fromJson(value as Map<String, dynamic>),
),
);
} on FormatException {
await LocalStorage.instance.remove(_key);
}
}
Future<void> add(Customer customer) async {
if (kIsWeb) {
await WebOfficeApi.instance.createCustomer(
Map<String, dynamic>.from(customer.toJson()),
);
await initialize();
return;
}
_customers.insert(0, customer);
notifyListeners();
await _persist(customer);
if (LocalStorage.instance.usesRelationalStore) await initialize();
notifyListeners();
}
Future<void> remove(String id) async {
if (kIsWeb) {
await WebOfficeApi.instance.deleteCustomer(id);
await initialize();
return;
}
_customers.removeWhere((customer) => customer.id == id);
notifyListeners();
if (LocalStorage.instance.usesRelationalStore) {
await LocalCustomerRepository().remove(id);
} else {
await _persist();
}
}
Future<void> update(Customer customer) async {
if (kIsWeb) {
await WebOfficeApi.instance.updateCustomer(
customer.id,
customer.toJson().map((key, value) => MapEntry(key, value)),
);
await initialize();
return;
}
final index = _customers.indexWhere((item) => item.id == customer.id);
if (index == -1) return;
_customers[index] = customer;
notifyListeners();
await _persist(customer);
if (LocalStorage.instance.usesRelationalStore) await initialize();
notifyListeners();
}
Future<void> _migrateLegacy(LocalCustomerRepository repository) async {
final stored = await LocalStorage.instance.getString(_key);
if (stored == null) return;
try {
final values = jsonDecode(stored) as List<dynamic>;
final migrated = values
.map((value) => Customer.fromJson(value as Map<String, dynamic>))
.toList();
for (final customer in migrated) {
await repository.save(customer, legacy: true);
}
_customers
..clear()
..addAll(migrated);
} on Object {
// Retain unreadable legacy data rather than deleting user data.
}
}
Future<void> _persist([Customer? customer]) async {
if (LocalStorage.instance.usesRelationalStore) {
if (customer != null) await LocalCustomerRepository().save(customer);
return;
}
await LocalStorage.instance.setString(
_key,
jsonEncode(_customers.map((customer) => customer.toJson()).toList()),
);
}
}
class LocalCustomerRepository {
final SqliteMasterDataSyncRepository _sync = SqliteMasterDataSyncRepository();
Future<List<Customer>> readAll() async {
final tenant = AuthStore.instance.session?.tenantId;
return (await LocalStorage.instance.mobileQuery(
'customers',
where: tenant == null
? 'deleted_at IS NULL AND tenant_id IS NULL'
: 'deleted_at IS NULL AND tenant_id = ?',
whereArgs: tenant == null ? null : [tenant],
orderBy: 'updated_at DESC',
)).map((row) => Customer.fromJson(row)).toList();
}
Future<bool> hasAnyRows() async {
final tenant = AuthStore.instance.session?.tenantId;
return (await LocalStorage.instance.mobileQuery(
'customers',
where: tenant == null ? 'tenant_id IS NULL' : 'tenant_id = ?',
whereArgs: tenant == null ? null : [tenant],
)).isNotEmpty;
}
Future<void> save(Customer customer, {bool legacy = false}) {
final row = customer.toJson();
if (legacy) return _sync.saveLegacy('customers', row);
return _sync.saveLocalChange(
tenantId: AuthStore.instance.session?.tenantId,
entityType: 'customers',
row: row,
payload: {
'name': customer.name,
'contact': customer.contact,
'email': customer.email,
'phone': customer.phone,
'address': customer.address,
},
);
}
Future<void> remove(String id) => _sync.deleteLocal(
tenantId: AuthStore.instance.session?.tenantId,
entityType: 'customers',
entityId: id,
);
}
