refactor: убрать qt_pricelist_sync_status, lot_log и лишние права БД

- Удалить все записи в qt_pricelist_sync_status (RecordSyncHeartbeat и
  ensureUserSyncStatusTable); ListUserSyncStatuses переведён на
  qt_client_schema_state
- Подавлять устаревший OFFLINE_UNVERIFIED_SCHEMA в UI когда соединение
  уже восстановлено
- Удалить все обращения к lot_log: repository/price.go, сортировка
  quote_count в component.go, UpdatePopularityScores в stats.go,
  модель LotLog

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-02 16:18:52 +03:00
parent 1de66d6f33
commit 3992dbf919
7 changed files with 22 additions and 251 deletions

View File

@@ -427,7 +427,6 @@ func (s *Service) SyncPricelists() (int, error) {
now := time.Now()
s.localDB.SetLastSyncTime(now)
s.recordPricelistSyncSuccess(now)
s.RecordSyncHeartbeat()
s.localDB.AppendSyncLog("pricelists", "ok", "", synced, plSyncStart, time.Since(plSyncStart).Milliseconds())
slog.Info("pricelist sync completed", "synced", synced, "total", len(serverPricelists))
@@ -612,58 +611,29 @@ func (s *Service) backfillUsedPricelistItemCategories(pricelistRepo *repository.
}
}
// RecordSyncHeartbeat updates shared sync heartbeat for current DB user.
// Only users with write rights are expected to be able to update this table.
func (s *Service) RecordSyncHeartbeat() {
username := strings.TrimSpace(s.localDB.GetDBUser())
if username == "" {
return
}
mariaDB, err := s.getDB()
if err != nil || mariaDB == nil {
return
}
if err := ensureUserSyncStatusTable(mariaDB); err != nil {
slog.Warn("sync heartbeat: failed to ensure table", "error", err)
return
}
now := time.Now().UTC()
if err := mariaDB.Exec(`
INSERT INTO qt_pricelist_sync_status (username, last_sync_at, updated_at, app_version)
VALUES (?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
last_sync_at = VALUES(last_sync_at),
updated_at = VALUES(updated_at),
app_version = VALUES(app_version)
`, username, now, now, appmeta.Version()).Error; err != nil {
slog.Debug("sync heartbeat: skipped", "username", username, "error", err)
}
}
// ListUserSyncStatuses returns users who have recorded sync heartbeat.
// ListUserSyncStatuses returns users who have recorded a client schema state check.
func (s *Service) ListUserSyncStatuses(onlineThreshold time.Duration) ([]UserSyncStatus, error) {
mariaDB, err := s.getDB()
if err != nil || mariaDB == nil {
return nil, ErrOffline
}
if err := ensureUserSyncStatusTable(mariaDB); err != nil {
return nil, fmt.Errorf("ensure sync status table: %w", err)
}
type row struct {
Username string `gorm:"column:username"`
LastSyncAt time.Time `gorm:"column:last_sync_at"`
AppVersion string `gorm:"column:app_version"`
Username string `gorm:"column:username"`
LastCheckedAt time.Time `gorm:"column:last_checked_at"`
AppVersion string `gorm:"column:app_version"`
}
var rows []row
if err := mariaDB.Raw(`
SELECT username, last_sync_at, COALESCE(app_version, '') AS app_version
FROM qt_pricelist_sync_status
ORDER BY last_sync_at DESC, username ASC
SELECT s.username, s.last_checked_at, COALESCE(s.app_version, '') AS app_version
FROM qt_client_schema_state s
INNER JOIN (
SELECT username, MAX(last_checked_at) AS max_checked
FROM qt_client_schema_state
GROUP BY username
) latest ON s.username = latest.username AND s.last_checked_at = latest.max_checked
GROUP BY s.username
ORDER BY s.last_checked_at DESC, s.username ASC
`).Scan(&rows).Error; err != nil {
return nil, fmt.Errorf("load sync status rows: %w", err)
}
@@ -683,7 +653,7 @@ func (s *Service) ListUserSyncStatuses(onlineThreshold time.Duration) ([]UserSyn
continue
}
isOnline := now.Sub(r.LastSyncAt) <= onlineThreshold
isOnline := now.Sub(r.LastCheckedAt) <= onlineThreshold
if _, connected := activeUsers[username]; connected {
isOnline = true
delete(activeUsers, username)
@@ -693,7 +663,7 @@ func (s *Service) ListUserSyncStatuses(onlineThreshold time.Duration) ([]UserSyn
result = append(result, UserSyncStatus{
Username: username,
LastSyncAt: r.LastSyncAt,
LastSyncAt: r.LastCheckedAt,
AppVersion: appVersion,
IsOnline: isOnline,
})
@@ -747,36 +717,6 @@ func (s *Service) listConnectedDBUsers(mariaDB *gorm.DB) (map[string]struct{}, e
return users, nil
}
func ensureUserSyncStatusTable(db *gorm.DB) error {
// Check if table exists instead of trying to create (avoids permission issues)
if !tableExists(db, "qt_pricelist_sync_status") {
if err := db.Exec(`
CREATE TABLE IF NOT EXISTS qt_pricelist_sync_status (
username VARCHAR(100) NOT NULL,
last_sync_at DATETIME NOT NULL,
updated_at DATETIME NOT NULL,
app_version VARCHAR(64) NULL,
PRIMARY KEY (username),
INDEX idx_qt_pricelist_sync_status_last_sync (last_sync_at)
)
`).Error; err != nil {
return fmt.Errorf("create qt_pricelist_sync_status table: %w", err)
}
}
// Backward compatibility for environments where table was created without app_version.
// Only try to add column if table exists.
if tableExists(db, "qt_pricelist_sync_status") {
if err := db.Exec(`
ALTER TABLE qt_pricelist_sync_status
ADD COLUMN IF NOT EXISTS app_version VARCHAR(64) NULL
`).Error; err != nil {
// Log but don't fail if alter fails (column might already exist)
slog.Debug("failed to add app_version column", "error", err)
}
}
return nil
}
// SyncPricelistItems synchronizes items for a specific pricelist
func (s *Service) SyncPricelistItems(localPricelistID uint) (int, error) {

View File

@@ -100,8 +100,5 @@ func (w *Worker) runSync() {
return
}
// Mark user's sync heartbeat (used for online/offline status in UI).
w.service.RecordSyncHeartbeat()
w.logger.Info("background sync cycle completed")
}