1
0
forked from BRT/arc

Force portrait orientation on connected devices, remove x100 heuristic from transaction matching, and add secondary text search filter in monitoring app.

This commit is contained in:
slava 2026-06-08 21:37:46 +05:00
parent 2c2d58021f
commit 2fb6146fd9
4 changed files with 58 additions and 19 deletions

View File

@ -369,6 +369,19 @@ bool BankProfileDAO::deactivateAppsForDevice(const QString &deviceId) {
return true;
}
bool BankProfileDAO::deactivateAllApps() {
QSqlQuery query(DatabaseManager::instance().database());
query.prepare(R"(
UPDATE bank_profiles SET status = 'off'
WHERE install = 'yes'
)");
if (!query.exec()) {
qWarning() << "Ошибка при деактивации всех приложений:" << query.lastError().text();
return false;
}
return true;
}
bool BankProfileDAO::checkInstalledApps(const QString &deviceId, const QSet<QString> &apps) {
QSqlQuery query(DatabaseManager::instance().database());
const QSettings settings("config.ini", QSettings::IniFormat);

View File

@ -56,6 +56,10 @@ public:
[[nodiscard]] static bool deactivateAppsForDevice(const QString &deviceId);
// Деактивирует ВСЕ установленные профили (status='off'). Используется при
// выключении приложения (graceful shutdown) — гасим профили и локально.
[[nodiscard]] static bool deactivateAllApps();
[[nodiscard]] static bool migrateDeviceId(const QString &oldDeviceId, const QString &newDeviceId);
[[nodiscard]] static bool deleteProfile(const QString &deviceId, const QString &appCode);

View File

@ -32,12 +32,19 @@ Pull с сервера (`syncBankProfilesFromServer`) используется
## Основные правила
### 1. Смена онлайн-статуса устройства
- **Device → OFFLINE:** bank_profile этого устройства выключается **и локально, и на сервере** (`PATCH bank_profile/{id}/false`). Материалы локально не трогаем — они остаются в том состоянии, в котором были (см. правило 4-А ниже).
- **Device → ONLINE:** локальный статус **не меняется**. На сервер пушится текущее локальное состояние bank_profile и всех его материалов, если оно расходится с серверным.
- **Device → OFFLINE:** bank_profile этого устройства выключается **только на сервере** (`PATCH bank_profile/{id}/false`). Локальный статус **не трогаем** — это намерение пользователя; его меняет лишь ручной тумблер или выключение приложения (см. правило 1-А). Дисконнект — не «выключение пользователем». Материалы локально не трогаем (см. правило 4-А ниже).
- **Device → ONLINE:** локальный статус **не меняется**. На сервер пушится текущее локальное состояние bank_profile и всех его материалов, если оно расходится с серверным. Т.к. при дисконнекте локаль осталась `active`, профиль снова включается на сервере (`PATCH .../true`).
> Концептуально: серверное состояние bank_profile = `(локально active) AND (устройство online)`. Локаль хранит намерение пользователя, сервер дополнительно отражает достижимость устройства.
### 1-А. Выключение приложения (graceful shutdown)
- При штатном выключении приложения (`NetworkService::stop`) профили гасятся **и на сервере, и локально** (`BankProfileDAO::deactivateAllApps`). Это единственный, кроме ручного тумблера, путь, который ставит локальный `off`.
- Отличие от паузы обновления: пауза (`disableAllProfilesOnServer`) гасит **только на сервере**, локаль остаётся `active`, чтобы профили воскресли после апдейта.
### 2. Периодическая синхронизация
- Каждые **60 секунд** запускается фоновая задача, которая сравнивает локальные статусы bank_profile и материалов с серверными и отправляет `PATCH` только для расходящихся.
- Локаль не трогается. Цель — компенсировать сетевые сбои, перезапуски сервера и т.п.
- Каждые **60 секунд** запускается фоновая задача, которая сравнивает **желаемое** серверное состояние с фактическим и отправляет `PATCH` только для расходящихся.
- Желаемое серверное состояние bank_profile = `(локально active) AND (устройство online)`: профиль офлайн-устройства держится `disabled` и **не реактивируется**, пока устройство не вернётся онлайн. Материалы при этом не пушатся (гард, правило 4-B).
- Локаль не трогается. Цель — компенсировать сетевые сбои, перезапуски сервера, непрошедшие PATCH и т.п.
### 3. Обработка ошибок при включении
- Когда пользователь включает bank_profile или material и `PATCH .../true` падает (network error / HTTP не-2xx / `success:false`):

View File

@ -1085,21 +1085,29 @@ bool NetworkService::syncCurrentProfile() {
// Идём по локальным устройствам и их bank_profiles → пушим diff.
const QList<DeviceInfo> allDevices = DeviceDAO::getAllDevices(false);
for (const DeviceInfo &dev : allDevices) {
// Серверное состояние профиля = (локально active) AND (устройство online).
// Офлайн-устройство держим disabled на сервере и НЕ реактивируем, даже
// если локально профиль остался active (правило 1/2: server отражает
// намерение пользователя И достижимость устройства).
const bool online = (dev.status == "device");
const QList<BankProfileInfo> apps = BankProfileDAO::getApplicationsByDeviceId(dev.id);
for (const BankProfileInfo &app : apps) {
if (app.bankProfileId.isEmpty()) continue; // не синхронизирован с сервером
const bool desiredEnabled = (app.status == "active") && online;
QSharedPointer<QMutex> mtx = keyMutex(dev.id, app.bankProfileId);
QMutexLocker locker(mtx.data());
// bank_profile diff
if (serverProfileState.contains(app.bankProfileId)) {
const QString localBpState = (app.status == "active") ? "enabled" : "disabled";
const QString srvBpState = serverProfileState.value(app.bankProfileId);
if (srvBpState != localBpState) {
const QString enableStr = (app.status == "active") ? "true" : "false";
const QString desiredBpState = desiredEnabled ? "enabled" : "disabled";
const QString srvBpState = serverProfileState.value(app.bankProfileId);
if (srvBpState != desiredBpState) {
const QString enableStr = desiredEnabled ? "true" : "false";
qDebug() << "[syncCurrentProfile] pushing bank_profile" << app.bankProfileId
<< "" << enableStr << "(local=" << localBpState << "srv=" << srvBpState << ")";
<< "" << enableStr << "(desired=" << desiredBpState << "srv=" << srvBpState
<< "local=" << app.status << "online=" << online << ")";
patchJson(m_apiBase + m_pathBankProfile + "/" + app.bankProfileId + "/" + enableStr,
QJsonObject(), true);
++pushedProfiles;
@ -1110,8 +1118,10 @@ bool NetworkService::syncCurrentProfile() {
continue;
}
// Гард #3 (rule 4-B): материалы не пушим при локально выключенном профиле.
if (app.status != "active") continue;
// Гард #3 (rule 4-B): материалы не пушим, если профиль эффективно
// disabled на сервере (локально off ИЛИ устройство offline) — иначе
// бесконечный diff-цикл из-за инварианта material.state ≤ bank_profile.state.
if (!desiredEnabled) continue;
const QList<MaterialInfo> materials = MaterialDAO::getAccounts(dev.id, app.code);
for (const MaterialInfo &mat : materials) {
@ -1936,10 +1946,11 @@ QSharedPointer<QMutex> NetworkService::keyMutex(const QString &deviceId, const Q
}
void NetworkService::onDeviceWentOffline(const QString &deviceId) {
// Правило #1 (device→OFFLINE): выключаем bank_profile и локально, и на сервере.
// Материалы локально не трогаем (правило 4-A), но на сервере удаляем все
// синхронизированные материалы (инвариант: материал живёт на сервере только
// при локально active профиле).
// Правило #1 (device→OFFLINE): выключаем bank_profile ТОЛЬКО на сервере.
// Локальный статус НЕ трогаем — это намерение пользователя, его меняет лишь
// ручной тумблер или выключение приложения (см. stop()). Когда устройство
// вернётся онлайн, профиль снова включится на сервере (onDeviceWentOnline +
// периодический sync). Материалы локально не трогаем (правило 4-A).
if (deviceId.isEmpty()) return;
if (!ensureValidToken()) return;
@ -1981,10 +1992,8 @@ void NetworkService::onDeviceWentOffline(const QString &deviceId) {
<< app.bankProfileId << "(" << app.code << ")";
}
// 2) Локально: все профили устройства → off.
if (!BankProfileDAO::deactivateAppsForDevice(deviceId)) {
qWarning() << "[NetworkService] onDeviceWentOffline: failed to deactivate local apps for device" << deviceId;
}
// Локальный статус НАМЕРЕННО не меняем: дисконнект устройства не должен
// выключать профиль локально (см. комментарий в начале функции).
}
void NetworkService::onDeviceWentOnline(const QString &deviceId) {
@ -2141,7 +2150,13 @@ void NetworkService::disableAllProfilesOnServer() {
void NetworkService::stop() {
m_running = false;
QMetaObject::invokeMethod(this, [this]() {
// При выключении приложения гасим профили и на сервере, и локально.
// (В отличие от дисконнекта устройства и паузы обновления, где локаль
// не трогаем — disableAllProfilesOnServer переиспользуется и там.)
disableAllProfilesOnServer();
if (!BankProfileDAO::deactivateAllApps()) {
qWarning() << "[NetworkService] stop: failed to deactivate local profiles";
}
emit finished();
}, Qt::QueuedConnection);
}