@@ -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,9 +7186,25 @@ func (a *App) createTaskHandler(w http.ResponseWriter, r *http.Request) {
sendErrorWithCORS ( w , fmt . Sprintf ( "Error checking existing task: %v" , err ) , http . StatusInternalServerError )
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
}
}
// Если название задачи не указано или пустое, используем название желания
if strings . TrimSpace ( req . Name ) == "" {
@@ -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 ( `
// 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,21 +10925,7 @@ 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 )
}
} else {
// Другой пользователь завершил желание - помечаем задачу как удалённую
// Личная политика: при закрытии задачи-желания другим пользователем, личная задача удаляется
_ , err = a . DB . Exec ( `
UPDATE tasks
SET deleted = TRUE
@@ -10843,9 +10936,8 @@ func (a *App) processWishlistRewardPolicy(wishlistItemID int, completingUserID i
} else {
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 )
}