4.19.0: Добавлены позиции подзадач
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 1m34s
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 1m34s
This commit is contained in:
@@ -284,6 +284,7 @@ type Task struct {
|
||||
WishlistID *int `json:"wishlist_id,omitempty"`
|
||||
ConfigID *int `json:"config_id,omitempty"`
|
||||
RewardPolicy *string `json:"reward_policy,omitempty"` // "personal" или "general" для задач, связанных с желаниями
|
||||
Position *int `json:"position,omitempty"` // Position for subtasks
|
||||
// Дополнительные поля для списка задач (без omitempty чтобы всегда передавались)
|
||||
ProjectNames []string `json:"project_names"`
|
||||
SubtasksCount int `json:"subtasks_count"`
|
||||
@@ -335,6 +336,7 @@ type SubtaskRequest struct {
|
||||
ID *int `json:"id,omitempty"`
|
||||
Name *string `json:"name,omitempty"`
|
||||
RewardMessage *string `json:"reward_message,omitempty"`
|
||||
Position *int `json:"position,omitempty"`
|
||||
Rewards []RewardRequest `json:"rewards,omitempty"`
|
||||
}
|
||||
|
||||
@@ -7370,10 +7372,10 @@ func (a *App) getTaskDetailHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// Получаем подзадачи
|
||||
subtasks := make([]Subtask, 0)
|
||||
subtaskRows, err := a.DB.Query(`
|
||||
SELECT id, name, completed, last_completed_at, reward_message, progression_base
|
||||
SELECT id, name, completed, last_completed_at, reward_message, progression_base, position
|
||||
FROM tasks
|
||||
WHERE parent_task_id = $1 AND deleted = FALSE
|
||||
ORDER BY id
|
||||
ORDER BY COALESCE(position, id)
|
||||
`, taskID)
|
||||
|
||||
if err != nil {
|
||||
@@ -7388,10 +7390,12 @@ func (a *App) getTaskDetailHandler(w http.ResponseWriter, r *http.Request) {
|
||||
var subtaskRewardMessage sql.NullString
|
||||
var subtaskProgressionBase sql.NullFloat64
|
||||
var subtaskLastCompletedAt sql.NullString
|
||||
var subtaskPosition sql.NullInt64
|
||||
|
||||
err := subtaskRows.Scan(
|
||||
&subtaskTask.ID, &subtaskTask.Name, &subtaskTask.Completed,
|
||||
&subtaskLastCompletedAt, &subtaskRewardMessage, &subtaskProgressionBase,
|
||||
&subtaskPosition,
|
||||
)
|
||||
if err != nil {
|
||||
log.Printf("Error scanning subtask: %v", err)
|
||||
@@ -7407,6 +7411,10 @@ func (a *App) getTaskDetailHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if subtaskLastCompletedAt.Valid {
|
||||
subtaskTask.LastCompletedAt = &subtaskLastCompletedAt.String
|
||||
}
|
||||
if subtaskPosition.Valid {
|
||||
pos := int(subtaskPosition.Int64)
|
||||
subtaskTask.Position = &pos
|
||||
}
|
||||
|
||||
subtaskIDs = append(subtaskIDs, subtaskTask.ID)
|
||||
subtask := Subtask{
|
||||
@@ -7944,10 +7952,11 @@ func (a *App) createTaskHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// Создаем подзадачи
|
||||
for _, subtaskReq := range req.Subtasks {
|
||||
for index, subtaskReq := range req.Subtasks {
|
||||
var subtaskName sql.NullString
|
||||
var subtaskRewardMessage sql.NullString
|
||||
var subtaskProgressionBase sql.NullFloat64
|
||||
var subtaskPosition sql.NullInt64
|
||||
|
||||
if subtaskReq.Name != nil && strings.TrimSpace(*subtaskReq.Name) != "" {
|
||||
subtaskName = sql.NullString{String: strings.TrimSpace(*subtaskReq.Name), Valid: true}
|
||||
@@ -7958,13 +7967,19 @@ func (a *App) createTaskHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if req.ProgressionBase != nil {
|
||||
subtaskProgressionBase = sql.NullFloat64{Float64: *req.ProgressionBase, Valid: true}
|
||||
}
|
||||
// Используем position из запроса, если указан, иначе используем индекс в массиве
|
||||
if subtaskReq.Position != nil {
|
||||
subtaskPosition = sql.NullInt64{Int64: int64(*subtaskReq.Position), Valid: true}
|
||||
} else {
|
||||
subtaskPosition = sql.NullInt64{Int64: int64(index), Valid: true}
|
||||
}
|
||||
|
||||
var subtaskID int
|
||||
err = tx.QueryRow(`
|
||||
INSERT INTO tasks (user_id, name, parent_task_id, reward_message, progression_base, completed, deleted)
|
||||
VALUES ($1, $2, $3, $4, $5, 0, FALSE)
|
||||
INSERT INTO tasks (user_id, name, parent_task_id, reward_message, progression_base, completed, deleted, position)
|
||||
VALUES ($1, $2, $3, $4, $5, 0, FALSE, $6)
|
||||
RETURNING id
|
||||
`, userID, subtaskName, taskID, subtaskRewardMessage, subtaskProgressionBase).Scan(&subtaskID)
|
||||
`, userID, subtaskName, taskID, subtaskRewardMessage, subtaskProgressionBase, subtaskPosition).Scan(&subtaskID)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("Error creating subtask: %v", err)
|
||||
@@ -8363,7 +8378,7 @@ func (a *App) updateTaskHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// Обрабатываем подзадачи из запроса
|
||||
subtaskIDsInRequest := make(map[int]bool)
|
||||
for _, subtaskReq := range req.Subtasks {
|
||||
for index, subtaskReq := range req.Subtasks {
|
||||
if subtaskReq.ID != nil {
|
||||
subtaskIDsInRequest[*subtaskReq.ID] = true
|
||||
|
||||
@@ -8371,6 +8386,7 @@ func (a *App) updateTaskHandler(w http.ResponseWriter, r *http.Request) {
|
||||
var subtaskName sql.NullString
|
||||
var subtaskRewardMessage sql.NullString
|
||||
var subtaskProgressionBase sql.NullFloat64
|
||||
var subtaskPosition sql.NullInt64
|
||||
|
||||
if subtaskReq.Name != nil && strings.TrimSpace(*subtaskReq.Name) != "" {
|
||||
subtaskName = sql.NullString{String: strings.TrimSpace(*subtaskReq.Name), Valid: true}
|
||||
@@ -8381,12 +8397,18 @@ func (a *App) updateTaskHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if req.ProgressionBase != nil {
|
||||
subtaskProgressionBase = sql.NullFloat64{Float64: *req.ProgressionBase, Valid: true}
|
||||
}
|
||||
// Используем position из запроса, если указан, иначе используем индекс в массиве
|
||||
if subtaskReq.Position != nil {
|
||||
subtaskPosition = sql.NullInt64{Int64: int64(*subtaskReq.Position), Valid: true}
|
||||
} else {
|
||||
subtaskPosition = sql.NullInt64{Int64: int64(index), Valid: true}
|
||||
}
|
||||
|
||||
_, err = tx.Exec(`
|
||||
UPDATE tasks
|
||||
SET name = $1, reward_message = $2, progression_base = $3
|
||||
WHERE id = $4 AND parent_task_id = $5
|
||||
`, subtaskName, subtaskRewardMessage, subtaskProgressionBase, *subtaskReq.ID, taskID)
|
||||
SET name = $1, reward_message = $2, progression_base = $3, position = $4
|
||||
WHERE id = $5 AND parent_task_id = $6
|
||||
`, subtaskName, subtaskRewardMessage, subtaskProgressionBase, subtaskPosition, *subtaskReq.ID, taskID)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("Error updating subtask: %v", err)
|
||||
@@ -8432,6 +8454,7 @@ func (a *App) updateTaskHandler(w http.ResponseWriter, r *http.Request) {
|
||||
var subtaskName sql.NullString
|
||||
var subtaskRewardMessage sql.NullString
|
||||
var subtaskProgressionBase sql.NullFloat64
|
||||
var subtaskPosition sql.NullInt64
|
||||
|
||||
if subtaskReq.Name != nil && strings.TrimSpace(*subtaskReq.Name) != "" {
|
||||
subtaskName = sql.NullString{String: strings.TrimSpace(*subtaskReq.Name), Valid: true}
|
||||
@@ -8442,13 +8465,19 @@ func (a *App) updateTaskHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if req.ProgressionBase != nil {
|
||||
subtaskProgressionBase = sql.NullFloat64{Float64: *req.ProgressionBase, Valid: true}
|
||||
}
|
||||
// Используем position из запроса, если указан, иначе используем индекс в массиве
|
||||
if subtaskReq.Position != nil {
|
||||
subtaskPosition = sql.NullInt64{Int64: int64(*subtaskReq.Position), Valid: true}
|
||||
} else {
|
||||
subtaskPosition = sql.NullInt64{Int64: int64(index), Valid: true}
|
||||
}
|
||||
|
||||
var subtaskID int
|
||||
err = tx.QueryRow(`
|
||||
INSERT INTO tasks (user_id, name, parent_task_id, reward_message, progression_base, completed, deleted)
|
||||
VALUES ($1, $2, $3, $4, $5, 0, FALSE)
|
||||
INSERT INTO tasks (user_id, name, parent_task_id, reward_message, progression_base, completed, deleted, position)
|
||||
VALUES ($1, $2, $3, $4, $5, 0, FALSE, $6)
|
||||
RETURNING id
|
||||
`, userID, subtaskName, taskID, subtaskRewardMessage, subtaskProgressionBase).Scan(&subtaskID)
|
||||
`, userID, subtaskName, taskID, subtaskRewardMessage, subtaskProgressionBase, subtaskPosition).Scan(&subtaskID)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("Error creating subtask: %v", err)
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
-- Migration: Remove position field from tasks table
|
||||
-- Date: 2026-02-02
|
||||
--
|
||||
-- This migration removes the position field from tasks table.
|
||||
|
||||
DROP INDEX IF EXISTS idx_tasks_parent_position;
|
||||
|
||||
ALTER TABLE tasks
|
||||
DROP COLUMN IF EXISTS position;
|
||||
@@ -0,0 +1,49 @@
|
||||
-- Migration: Add position field to tasks table for subtasks ordering
|
||||
-- Date: 2026-02-02
|
||||
--
|
||||
-- This migration adds position field to tasks table to allow
|
||||
-- custom ordering of subtasks. The field is NULL for regular tasks
|
||||
-- and contains position number for subtasks (tasks with parent_task_id).
|
||||
|
||||
-- Добавляем поле position
|
||||
ALTER TABLE tasks
|
||||
ADD COLUMN position INTEGER;
|
||||
|
||||
-- Заполняем позиции для всех существующих подзадач
|
||||
-- Позиции присваиваются по порядку id в рамках каждой родительской задачи
|
||||
DO $$
|
||||
DECLARE
|
||||
parent_record RECORD;
|
||||
subtask_record RECORD;
|
||||
pos INTEGER;
|
||||
BEGIN
|
||||
-- Для каждой родительской задачи
|
||||
FOR parent_record IN
|
||||
SELECT DISTINCT parent_task_id
|
||||
FROM tasks
|
||||
WHERE parent_task_id IS NOT NULL
|
||||
ORDER BY parent_task_id
|
||||
LOOP
|
||||
pos := 0;
|
||||
-- Обновляем подзадачи этой родительской задачи
|
||||
FOR subtask_record IN
|
||||
SELECT id
|
||||
FROM tasks
|
||||
WHERE parent_task_id = parent_record.parent_task_id
|
||||
AND deleted = FALSE
|
||||
ORDER BY id
|
||||
LOOP
|
||||
UPDATE tasks
|
||||
SET position = pos
|
||||
WHERE id = subtask_record.id;
|
||||
|
||||
pos := pos + 1;
|
||||
END LOOP;
|
||||
END LOOP;
|
||||
END $$;
|
||||
|
||||
-- Создаем индекс для быстрой сортировки подзадач
|
||||
CREATE INDEX idx_tasks_parent_position ON tasks(parent_task_id, position)
|
||||
WHERE parent_task_id IS NOT NULL AND deleted = FALSE;
|
||||
|
||||
COMMENT ON COLUMN tasks.position IS 'Position of subtask within parent task. NULL for regular tasks.';
|
||||
Reference in New Issue
Block a user