feat: optimize background tasks and fix warehouse pricelist workflow

Optimize task retention from 5 minutes to 30 seconds to reduce polling overhead since toast notifications are shown only once. Add conditional warehouse pricelist creation via checkbox. Fix category storage in warehouse pricelists to properly load from lot table. Replace SSE with task polling for all long operations. Add comprehensive logging for debugging while minimizing noise from polling endpoints.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-16 11:08:10 +03:00
parent c9f7f4e908
commit f64c4fd6b2
17 changed files with 346 additions and 133 deletions

View File

@@ -3,6 +3,7 @@ package tasks
import (
"context"
"fmt"
"log/slog"
"sync"
"time"
@@ -47,10 +48,13 @@ func (m *Manager) Submit(taskType TaskType, fn TaskFunc) string {
m.tasks[taskID] = task
m.mu.Unlock()
slog.Info("Task submitted", "task_id", taskID, "type", taskType)
// Start task in goroutine with panic recovery
go func() {
defer func() {
if r := recover(); r != nil {
slog.Error("Task panicked", "task_id", taskID, "type", taskType, "panic", r)
m.updateTask(taskID, func(t *Task) {
t.Status = TaskStatusError
t.Error = fmt.Sprintf("Task panicked: %v", r)
@@ -62,6 +66,7 @@ func (m *Manager) Submit(taskType TaskType, fn TaskFunc) string {
// Create progress callback
progressCb := func(progress int, message string) {
slog.Debug("Task progress", "task_id", taskID, "progress", progress, "message", message)
m.updateTask(taskID, func(t *Task) {
t.Progress = progress
t.Message = message
@@ -69,6 +74,7 @@ func (m *Manager) Submit(taskType TaskType, fn TaskFunc) string {
}
// Execute task
slog.Info("Task starting execution", "task_id", taskID, "type", taskType)
ctx := context.Background()
result, err := fn(ctx, progressCb)
@@ -79,9 +85,11 @@ func (m *Manager) Submit(taskType TaskType, fn TaskFunc) string {
t.Progress = 100
if err != nil {
slog.Error("Task failed", "task_id", taskID, "type", taskType, "error", err)
t.Status = TaskStatusError
t.Error = err.Error()
} else {
slog.Info("Task completed successfully", "task_id", taskID, "type", taskType, "result", result)
t.Status = TaskStatusCompleted
t.Result = result
if t.Message == "Starting..." {
@@ -94,16 +102,17 @@ func (m *Manager) Submit(taskType TaskType, fn TaskFunc) string {
return taskID
}
// List returns running tasks and completed tasks from the last 5 minutes
// List returns running tasks and recently completed tasks (last 30 seconds)
func (m *Manager) List() []*Task {
m.mu.RLock()
defer m.mu.RUnlock()
cutoff := time.Now().Add(-5 * time.Minute)
// Keep completed tasks only for 30 seconds (enough to show toast notification)
cutoff := time.Now().Add(-30 * time.Second)
tasks := make([]*Task, 0, len(m.tasks))
for _, task := range m.tasks {
// Include running tasks or recently completed tasks
// Include running tasks or recently completed tasks (within 30 seconds)
if task.Status == TaskStatusRunning ||
(task.DoneAt != nil && task.DoneAt.After(cutoff)) {
tasks = append(tasks, task)
@@ -155,12 +164,12 @@ func (m *Manager) cleanupLoop() {
}
}
// cleanup removes tasks completed more than 10 minutes ago
// cleanup removes tasks completed more than 1 minute ago
func (m *Manager) cleanup() {
m.mu.Lock()
defer m.mu.Unlock()
cutoff := time.Now().Add(-10 * time.Minute)
cutoff := time.Now().Add(-1 * time.Minute)
for id, task := range m.tasks {
if task.DoneAt != nil && task.DoneAt.Before(cutoff) {