feat: sync_log таблица и список прайслистов в Support Bundle
- Добавлена таблица sync_log (до 100 записей на тип): фиксирует каждый запуск синхронизации с типом, статусом, ошибкой, кол-вом и временем - AppendSyncLog вызывается из SyncComponents, SyncPricelists (service и handler), SyncAll и SyncComponentsIfEmpty - Bundle теперь включает sync_log.json (200 последних записей) и pricelists.json (все скачанные прайслисты, сгруппированные по source) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -120,6 +120,38 @@ func (h *SupportBundleHandler) DownloadBundle(c *gin.Context) {
|
||||
// system_metrics.json
|
||||
writeJSON("system_metrics.json", collectSystemMetrics())
|
||||
|
||||
// sync_log.json — history of sync operations
|
||||
if entries, err := h.localDB.GetSyncLog(200); err == nil {
|
||||
writeJSON("sync_log.json", entries)
|
||||
}
|
||||
|
||||
// pricelists.json — downloaded pricelists grouped by source
|
||||
if pricelists, err := h.localDB.GetLocalPricelists(); err == nil {
|
||||
type plEntry struct {
|
||||
ServerID uint `json:"server_id"`
|
||||
Source string `json:"source"`
|
||||
Version string `json:"version"`
|
||||
Name string `json:"name,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
SyncedAt time.Time `json:"synced_at"`
|
||||
IsUsed bool `json:"is_used"`
|
||||
}
|
||||
bySource := map[string][]plEntry{}
|
||||
for _, pl := range pricelists {
|
||||
e := plEntry{
|
||||
ServerID: pl.ServerID,
|
||||
Source: pl.Source,
|
||||
Version: pl.Version,
|
||||
Name: pl.Name,
|
||||
CreatedAt: pl.CreatedAt,
|
||||
SyncedAt: pl.SyncedAt,
|
||||
IsUsed: pl.IsUsed,
|
||||
}
|
||||
bySource[pl.Source] = append(bySource[pl.Source], e)
|
||||
}
|
||||
writeJSON("pricelists.json", bySource)
|
||||
}
|
||||
|
||||
// schema_migrations.json
|
||||
var migrations []localdb.LocalSchemaMigration
|
||||
_ = h.localDB.DB().Order("applied_at ASC").Find(&migrations).Error
|
||||
|
||||
@@ -191,6 +191,7 @@ func (h *SyncHandler) SyncComponents(c *gin.Context) {
|
||||
result, err := h.localDB.SyncComponents(mariaDB)
|
||||
if err != nil {
|
||||
_ = h.localDB.SetComponentSyncResult("error", err.Error(), now)
|
||||
h.localDB.AppendSyncLog("components", "error", err.Error(), 0, now, time.Since(now).Milliseconds())
|
||||
slog.Error("component sync failed", "error", err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"success": false,
|
||||
@@ -200,6 +201,7 @@ func (h *SyncHandler) SyncComponents(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
_ = h.localDB.SetComponentSyncResult("ok", "", now)
|
||||
h.localDB.AppendSyncLog("components", "ok", "", result.TotalSynced, now, result.Duration.Milliseconds())
|
||||
|
||||
c.JSON(http.StatusOK, SyncResultResponse{
|
||||
Success: true,
|
||||
@@ -219,6 +221,7 @@ func (h *SyncHandler) SyncPricelists(c *gin.Context) {
|
||||
startTime := time.Now()
|
||||
synced, err := h.syncService.SyncPricelists()
|
||||
if err != nil {
|
||||
h.localDB.AppendSyncLog("pricelists", "error", err.Error(), 0, startTime, time.Since(startTime).Milliseconds())
|
||||
slog.Error("pricelist sync failed", "error", err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"success": false,
|
||||
@@ -227,6 +230,7 @@ func (h *SyncHandler) SyncPricelists(c *gin.Context) {
|
||||
_ = c.Error(err)
|
||||
return
|
||||
}
|
||||
h.localDB.AppendSyncLog("pricelists", "ok", "", synced, startTime, time.Since(startTime).Milliseconds())
|
||||
|
||||
c.JSON(http.StatusOK, SyncResultResponse{
|
||||
Success: true,
|
||||
@@ -320,6 +324,7 @@ func (h *SyncHandler) SyncAll(c *gin.Context) {
|
||||
compResult, err := h.localDB.SyncComponents(mariaDB)
|
||||
if err != nil {
|
||||
_ = h.localDB.SetComponentSyncResult("error", err.Error(), compNow)
|
||||
h.localDB.AppendSyncLog("components", "error", err.Error(), 0, compNow, time.Since(compNow).Milliseconds())
|
||||
slog.Error("component sync failed during full sync", "error", err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"success": false,
|
||||
@@ -329,11 +334,14 @@ func (h *SyncHandler) SyncAll(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
_ = h.localDB.SetComponentSyncResult("ok", "", compNow)
|
||||
h.localDB.AppendSyncLog("components", "ok", "", compResult.TotalSynced, compNow, compResult.Duration.Milliseconds())
|
||||
componentsSynced = compResult.TotalSynced
|
||||
|
||||
// Sync pricelists
|
||||
plNow := time.Now()
|
||||
pricelistsSynced, err = h.syncService.SyncPricelists()
|
||||
if err != nil {
|
||||
h.localDB.AppendSyncLog("pricelists", "error", err.Error(), 0, plNow, time.Since(plNow).Milliseconds())
|
||||
slog.Error("pricelist sync failed during full sync", "error", err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"success": false,
|
||||
@@ -344,6 +352,7 @@ func (h *SyncHandler) SyncAll(c *gin.Context) {
|
||||
_ = c.Error(err)
|
||||
return
|
||||
}
|
||||
h.localDB.AppendSyncLog("pricelists", "ok", "", pricelistsSynced, plNow, time.Since(plNow).Milliseconds())
|
||||
|
||||
projectsResult, err := h.syncService.ImportProjectsToLocal()
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user