4.5.0: Улучшена работа с задачами желаний
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 1m25s
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 1m25s
This commit is contained in:
@@ -362,6 +362,7 @@ type WishlistItem struct {
|
||||
MoreLockedConditions int `json:"more_locked_conditions,omitempty"`
|
||||
UnlockConditions []UnlockConditionDisplay `json:"unlock_conditions,omitempty"`
|
||||
LinkedTask *LinkedTask `json:"linked_task,omitempty"`
|
||||
TasksCount int `json:"tasks_count,omitempty"` // Количество задач для этого желания
|
||||
}
|
||||
|
||||
type UnlockConditionDisplay struct {
|
||||
@@ -7170,12 +7171,14 @@ func (a *App) createTaskHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// Проверяем, что нет другой задачи с таким wishlist_id
|
||||
// Проверяем, что нет другой активной (не удаленной и не выполненной) задачи с таким wishlist_id для этого пользователя
|
||||
// Если задача была выполнена (completed > 0) или удалена, можно создать новую
|
||||
var existingTaskID int
|
||||
var existingTaskCompleted int
|
||||
err = a.DB.QueryRow(`
|
||||
SELECT id FROM tasks
|
||||
WHERE wishlist_id = $1 AND deleted = FALSE
|
||||
`, *req.WishlistID).Scan(&existingTaskID)
|
||||
SELECT id, completed FROM tasks
|
||||
WHERE wishlist_id = $1 AND user_id = $2 AND deleted = FALSE
|
||||
`, *req.WishlistID, userID).Scan(&existingTaskID, &existingTaskCompleted)
|
||||
|
||||
if err != sql.ErrNoRows {
|
||||
if err != nil {
|
||||
@@ -7183,8 +7186,24 @@ func (a *App) createTaskHandler(w http.ResponseWriter, r *http.Request) {
|
||||
sendErrorWithCORS(w, fmt.Sprintf("Error checking existing task: %v", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
sendErrorWithCORS(w, "Task already exists for this wishlist item", http.StatusBadRequest)
|
||||
return
|
||||
// Если задача была выполнена (completed > 0), можно создать новую
|
||||
if existingTaskCompleted > 0 {
|
||||
log.Printf("Existing task %d for wishlist %d was completed (%d times), marking as deleted and allowing new task creation", existingTaskID, *req.WishlistID, existingTaskCompleted)
|
||||
// Помечаем старую выполненную задачу как удаленную, чтобы избежать конфликта с уникальным индексом
|
||||
_, err = a.DB.Exec(`
|
||||
UPDATE tasks
|
||||
SET deleted = TRUE
|
||||
WHERE id = $1
|
||||
`, existingTaskID)
|
||||
if err != nil {
|
||||
log.Printf("Error marking existing completed task as deleted: %v", err)
|
||||
sendErrorWithCORS(w, fmt.Sprintf("Error marking existing task as deleted: %v", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
sendErrorWithCORS(w, "Task already exists for this wishlist item", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Если название задачи не указано или пустое, используем название желания
|
||||
@@ -7340,6 +7359,11 @@ func (a *App) createTaskHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
if err != nil {
|
||||
log.Printf("Error creating task: %v", err)
|
||||
// Проверяем, не является ли это ошибкой уникального индекса
|
||||
if strings.Contains(err.Error(), "unique") || strings.Contains(err.Error(), "duplicate") {
|
||||
sendErrorWithCORS(w, "Task already exists for this wishlist item", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
sendErrorWithCORS(w, fmt.Sprintf("Error creating task: %v", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
@@ -8697,7 +8721,8 @@ func (a *App) executeTask(taskID int, userID int, req CompleteTaskRequest) error
|
||||
} else {
|
||||
log.Printf("Wishlist item %d completed automatically after task %d completion", wishlistID.Int64, taskID)
|
||||
// Обрабатываем политику награждения для всех задач, связанных с этим желанием
|
||||
a.processWishlistRewardPolicy(int(wishlistID.Int64), userID)
|
||||
// Исключаем задачу, которая была закрыта (taskID), чтобы не обрабатывать её повторно
|
||||
a.processWishlistRewardPolicy(int(wishlistID.Int64), taskID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9513,16 +9538,16 @@ func (a *App) getWishlistItemsWithConditions(userID int, includeCompleted bool)
|
||||
}
|
||||
}
|
||||
|
||||
// Загружаем связанную задачу, если есть
|
||||
// Загружаем связанную задачу текущего пользователя, если есть
|
||||
var linkedTaskID, linkedTaskCompleted, linkedTaskUserID sql.NullInt64
|
||||
var linkedTaskName sql.NullString
|
||||
var linkedTaskNextShowAt sql.NullTime
|
||||
linkedTaskErr := a.DB.QueryRow(`
|
||||
SELECT t.id, t.name, t.completed, t.next_show_at, t.user_id
|
||||
FROM tasks t
|
||||
WHERE t.wishlist_id = $1 AND t.deleted = FALSE
|
||||
WHERE t.wishlist_id = $1 AND t.user_id = $2 AND t.deleted = FALSE
|
||||
LIMIT 1
|
||||
`, item.ID).Scan(&linkedTaskID, &linkedTaskName, &linkedTaskCompleted, &linkedTaskNextShowAt, &linkedTaskUserID)
|
||||
`, item.ID, userID).Scan(&linkedTaskID, &linkedTaskName, &linkedTaskCompleted, &linkedTaskNextShowAt, &linkedTaskUserID)
|
||||
|
||||
if linkedTaskErr == nil && linkedTaskID.Valid {
|
||||
linkedTask := &LinkedTask{
|
||||
@@ -9544,6 +9569,31 @@ func (a *App) getWishlistItemsWithConditions(userID int, includeCompleted bool)
|
||||
// Не возвращаем ошибку, просто не устанавливаем linked_task
|
||||
}
|
||||
|
||||
// Подсчитываем общее количество не закрытых задач для этого желания (всех пользователей)
|
||||
// Исключаем linked_task из подсчета, если она есть
|
||||
// Учитываем только не закрытые задачи (completed = 0)
|
||||
var tasksCount int
|
||||
if linkedTaskID.Valid {
|
||||
// Если есть linked_task, исключаем её из подсчета
|
||||
err = a.DB.QueryRow(`
|
||||
SELECT COUNT(*)
|
||||
FROM tasks t
|
||||
WHERE t.wishlist_id = $1 AND t.deleted = FALSE AND t.completed = 0 AND t.id != $2
|
||||
`, item.ID, linkedTaskID.Int64).Scan(&tasksCount)
|
||||
} else {
|
||||
// Если нет linked_task, считаем все не закрытые задачи
|
||||
err = a.DB.QueryRow(`
|
||||
SELECT COUNT(*)
|
||||
FROM tasks t
|
||||
WHERE t.wishlist_id = $1 AND t.deleted = FALSE AND t.completed = 0
|
||||
`, item.ID).Scan(&tasksCount)
|
||||
}
|
||||
if err != nil {
|
||||
log.Printf("Error counting tasks for wishlist %d: %v", item.ID, err)
|
||||
tasksCount = 0
|
||||
}
|
||||
item.TasksCount = tasksCount
|
||||
|
||||
items = append(items, *item)
|
||||
}
|
||||
|
||||
@@ -10236,16 +10286,16 @@ func (a *App) getWishlistItemHandler(w http.ResponseWriter, r *http.Request) {
|
||||
item.Unlocked = unlocked
|
||||
}
|
||||
|
||||
// Загружаем связанную задачу, если есть
|
||||
// Загружаем связанную задачу текущего пользователя, если есть
|
||||
var linkedTaskID, linkedTaskCompleted, linkedTaskUserID sql.NullInt64
|
||||
var linkedTaskName sql.NullString
|
||||
var linkedTaskNextShowAt sql.NullTime
|
||||
err = a.DB.QueryRow(`
|
||||
SELECT t.id, t.name, t.completed, t.next_show_at, t.user_id
|
||||
FROM tasks t
|
||||
WHERE t.wishlist_id = $1 AND t.deleted = FALSE
|
||||
WHERE t.wishlist_id = $1 AND t.user_id = $2 AND t.deleted = FALSE
|
||||
LIMIT 1
|
||||
`, itemID).Scan(&linkedTaskID, &linkedTaskName, &linkedTaskCompleted, &linkedTaskNextShowAt, &linkedTaskUserID)
|
||||
`, itemID, userID).Scan(&linkedTaskID, &linkedTaskName, &linkedTaskCompleted, &linkedTaskNextShowAt, &linkedTaskUserID)
|
||||
|
||||
if err == nil && linkedTaskID.Valid {
|
||||
linkedTask := &LinkedTask{
|
||||
@@ -10267,6 +10317,31 @@ func (a *App) getWishlistItemHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// Не возвращаем ошибку, просто не устанавливаем linked_task
|
||||
}
|
||||
|
||||
// Подсчитываем общее количество не закрытых задач для этого желания (всех пользователей)
|
||||
// Исключаем linked_task из подсчета, если она есть
|
||||
// Учитываем только не закрытые задачи (completed = 0)
|
||||
var tasksCount int
|
||||
if linkedTaskID.Valid {
|
||||
// Если есть linked_task, исключаем её из подсчета
|
||||
err = a.DB.QueryRow(`
|
||||
SELECT COUNT(*)
|
||||
FROM tasks t
|
||||
WHERE t.wishlist_id = $1 AND t.deleted = FALSE AND t.completed = 0 AND t.id != $2
|
||||
`, itemID, linkedTaskID.Int64).Scan(&tasksCount)
|
||||
} else {
|
||||
// Если нет linked_task, считаем все не закрытые задачи
|
||||
err = a.DB.QueryRow(`
|
||||
SELECT COUNT(*)
|
||||
FROM tasks t
|
||||
WHERE t.wishlist_id = $1 AND t.deleted = FALSE AND t.completed = 0
|
||||
`, itemID).Scan(&tasksCount)
|
||||
}
|
||||
if err != nil {
|
||||
log.Printf("Error counting tasks for wishlist %d: %v", itemID, err)
|
||||
tasksCount = 0
|
||||
}
|
||||
item.TasksCount = tasksCount
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(item)
|
||||
}
|
||||
@@ -10780,8 +10855,26 @@ func (a *App) completeWishlistHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// Находим задачу пользователя для этого желания, чтобы исключить её из обработки
|
||||
// (так же, как при закрытии через задачу)
|
||||
var userTaskID int
|
||||
err = a.DB.QueryRow(`
|
||||
SELECT id FROM tasks
|
||||
WHERE wishlist_id = $1 AND user_id = $2 AND deleted = FALSE
|
||||
LIMIT 1
|
||||
`, itemID, userID).Scan(&userTaskID)
|
||||
|
||||
// Если задача не найдена, используем 0 (не будет исключена, но это нормально, если задачи нет)
|
||||
if err == sql.ErrNoRows {
|
||||
userTaskID = 0
|
||||
} else if err != nil {
|
||||
log.Printf("Error finding user task for wishlist item %d: %v", itemID, err)
|
||||
userTaskID = 0
|
||||
}
|
||||
|
||||
// Обрабатываем политику награждения для всех задач, связанных с этим желанием
|
||||
a.processWishlistRewardPolicy(itemID, userID)
|
||||
// Исключаем задачу пользователя, который закрыл желание (если она есть)
|
||||
a.processWishlistRewardPolicy(itemID, userTaskID)
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
@@ -10791,12 +10884,26 @@ func (a *App) completeWishlistHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// processWishlistRewardPolicy обрабатывает политику награждения для всех задач, связанных с желанием
|
||||
func (a *App) processWishlistRewardPolicy(wishlistItemID int, completingUserID int) {
|
||||
rows, err := a.DB.Query(`
|
||||
SELECT id, user_id, reward_policy
|
||||
FROM tasks
|
||||
WHERE wishlist_id = $1 AND deleted = FALSE
|
||||
`, wishlistItemID)
|
||||
// completedTaskID - ID задачи, которая была закрыта (исключается из обработки). Если 0, задача не найдена, но это нормально
|
||||
func (a *App) processWishlistRewardPolicy(wishlistItemID int, completedTaskID int) {
|
||||
var rows *sql.Rows
|
||||
var err error
|
||||
if completedTaskID == 0 {
|
||||
// Если задача не найдена (желание закрывается напрямую, но у пользователя нет задачи),
|
||||
// обрабатываем все задачи
|
||||
rows, err = a.DB.Query(`
|
||||
SELECT id, user_id, reward_policy
|
||||
FROM tasks
|
||||
WHERE wishlist_id = $1 AND deleted = FALSE
|
||||
`, wishlistItemID)
|
||||
} else {
|
||||
// Исключаем задачу, которая была закрыта (через задачу или найдена при прямом закрытии желания)
|
||||
rows, err = a.DB.Query(`
|
||||
SELECT id, user_id, reward_policy
|
||||
FROM tasks
|
||||
WHERE wishlist_id = $1 AND deleted = FALSE AND id != $2
|
||||
`, wishlistItemID, completedTaskID)
|
||||
}
|
||||
if err != nil {
|
||||
log.Printf("Error querying tasks for wishlist item %d: %v", wishlistItemID, err)
|
||||
return
|
||||
@@ -10818,34 +10925,19 @@ func (a *App) processWishlistRewardPolicy(wishlistItemID int, completingUserID i
|
||||
}
|
||||
|
||||
if policy == "personal" {
|
||||
// Личная политика: задача выполняется только если пользователь сам завершил желание
|
||||
if taskUserID == completingUserID {
|
||||
// Пользователь завершил желание сам - помечаем задачу как выполненную
|
||||
_, err = a.DB.Exec(`
|
||||
UPDATE tasks
|
||||
SET completed = completed + 1, last_completed_at = NOW()
|
||||
WHERE id = $1
|
||||
`, taskID)
|
||||
if err != nil {
|
||||
log.Printf("Error completing task %d: %v", taskID, err)
|
||||
} else {
|
||||
log.Printf("Task %d completed automatically after wishlist item %d completion (personal policy)", taskID, wishlistItemID)
|
||||
}
|
||||
// Личная политика: при закрытии задачи-желания другим пользователем, личная задача удаляется
|
||||
_, err = a.DB.Exec(`
|
||||
UPDATE tasks
|
||||
SET deleted = TRUE
|
||||
WHERE id = $1
|
||||
`, taskID)
|
||||
if err != nil {
|
||||
log.Printf("Error deleting task %d: %v", taskID, err)
|
||||
} else {
|
||||
// Другой пользователь завершил желание - помечаем задачу как удалённую
|
||||
_, err = a.DB.Exec(`
|
||||
UPDATE tasks
|
||||
SET deleted = TRUE
|
||||
WHERE id = $1
|
||||
`, taskID)
|
||||
if err != nil {
|
||||
log.Printf("Error deleting task %d: %v", taskID, err)
|
||||
} else {
|
||||
log.Printf("Task %d deleted because wishlist item %d was completed by another user (personal policy)", taskID, wishlistItemID)
|
||||
}
|
||||
log.Printf("Task %d deleted because wishlist item %d was completed by another user (personal policy)", taskID, wishlistItemID)
|
||||
}
|
||||
} else if policy == "general" {
|
||||
// Общая политика: задача выполняется независимо от того, кто завершил желание
|
||||
// Общая политика: при закрытии задачи-желания другим пользователем, общая задача закрывается
|
||||
_, err = a.DB.Exec(`
|
||||
UPDATE tasks
|
||||
SET completed = completed + 1, last_completed_at = NOW()
|
||||
@@ -12418,6 +12510,63 @@ func (a *App) getWishlistItemsByBoard(boardID int, userID int) ([]WishlistItem,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Загружаем связанную задачу текущего пользователя, если есть
|
||||
var linkedTaskID, linkedTaskCompleted, linkedTaskUserID sql.NullInt64
|
||||
var linkedTaskName sql.NullString
|
||||
var linkedTaskNextShowAt sql.NullTime
|
||||
linkedTaskErr := a.DB.QueryRow(`
|
||||
SELECT t.id, t.name, t.completed, t.next_show_at, t.user_id
|
||||
FROM tasks t
|
||||
WHERE t.wishlist_id = $1 AND t.user_id = $2 AND t.deleted = FALSE
|
||||
LIMIT 1
|
||||
`, item.ID, userID).Scan(&linkedTaskID, &linkedTaskName, &linkedTaskCompleted, &linkedTaskNextShowAt, &linkedTaskUserID)
|
||||
|
||||
if linkedTaskErr == nil && linkedTaskID.Valid {
|
||||
linkedTask := &LinkedTask{
|
||||
ID: int(linkedTaskID.Int64),
|
||||
Name: linkedTaskName.String,
|
||||
Completed: int(linkedTaskCompleted.Int64),
|
||||
}
|
||||
if linkedTaskNextShowAt.Valid {
|
||||
nextShowAtStr := linkedTaskNextShowAt.Time.Format(time.RFC3339)
|
||||
linkedTask.NextShowAt = &nextShowAtStr
|
||||
}
|
||||
if linkedTaskUserID.Valid {
|
||||
userIDVal := int(linkedTaskUserID.Int64)
|
||||
linkedTask.UserID = &userIDVal
|
||||
}
|
||||
item.LinkedTask = linkedTask
|
||||
} else if linkedTaskErr != sql.ErrNoRows {
|
||||
log.Printf("Error loading linked task for wishlist %d: %v", item.ID, linkedTaskErr)
|
||||
// Не возвращаем ошибку, просто не устанавливаем linked_task
|
||||
}
|
||||
|
||||
// Подсчитываем общее количество не закрытых задач для этого желания (всех пользователей)
|
||||
// Исключаем linked_task из подсчета, если она есть
|
||||
// Учитываем только не закрытые задачи (completed = 0)
|
||||
var tasksCount int
|
||||
if linkedTaskID.Valid {
|
||||
// Если есть linked_task, исключаем её из подсчета
|
||||
err = a.DB.QueryRow(`
|
||||
SELECT COUNT(*)
|
||||
FROM tasks t
|
||||
WHERE t.wishlist_id = $1 AND t.deleted = FALSE AND t.completed = 0 AND t.id != $2
|
||||
`, item.ID, linkedTaskID.Int64).Scan(&tasksCount)
|
||||
} else {
|
||||
// Если нет linked_task, считаем все не закрытые задачи
|
||||
err = a.DB.QueryRow(`
|
||||
SELECT COUNT(*)
|
||||
FROM tasks t
|
||||
WHERE t.wishlist_id = $1 AND t.deleted = FALSE AND t.completed = 0
|
||||
`, item.ID).Scan(&tasksCount)
|
||||
}
|
||||
if err != nil {
|
||||
log.Printf("Error counting tasks for wishlist %d: %v", item.ID, err)
|
||||
tasksCount = 0
|
||||
}
|
||||
item.TasksCount = tasksCount
|
||||
|
||||
items = append(items, *item)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user