5.1.2: Достижение цели Fitbit для задачи без подзадачи
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 1m20s

This commit is contained in:
poignatov
2026-02-09 17:40:29 +03:00
parent 9cfb988960
commit 76049b3da5
4 changed files with 60 additions and 6 deletions

View File

@@ -1 +1 @@
5.1.1 5.1.2

View File

@@ -11125,12 +11125,22 @@ func (a *App) syncFitbitData(userID int, date time.Time) error {
if err := a.saveFitbitSubtaskDraft(userID, int(stepsGoalTaskID.Int64), int(stepsGoalSubtaskID.Int64), goalReached); err != nil { if err := a.saveFitbitSubtaskDraft(userID, int(stepsGoalTaskID.Int64), int(stepsGoalSubtaskID.Int64), goalReached); err != nil {
log.Printf("Error saving steps goal subtask draft: %v", err) log.Printf("Error saving steps goal subtask draft: %v", err)
} }
} else if stepsGoalTaskID.Valid {
goalReached := steps >= goalSteps
if err := a.setFitbitTaskDraftAutoComplete(userID, int(stepsGoalTaskID.Int64), goalReached); err != nil {
log.Printf("Error setting steps goal task draft auto_complete: %v", err)
}
} }
if floorsGoalTaskID.Valid && floorsGoalSubtaskID.Valid { if floorsGoalTaskID.Valid && floorsGoalSubtaskID.Valid {
goalReached := floors >= goalFloors goalReached := floors >= goalFloors
if err := a.saveFitbitSubtaskDraft(userID, int(floorsGoalTaskID.Int64), int(floorsGoalSubtaskID.Int64), goalReached); err != nil { if err := a.saveFitbitSubtaskDraft(userID, int(floorsGoalTaskID.Int64), int(floorsGoalSubtaskID.Int64), goalReached); err != nil {
log.Printf("Error saving floors goal subtask draft: %v", err) log.Printf("Error saving floors goal subtask draft: %v", err)
} }
} else if floorsGoalTaskID.Valid {
goalReached := floors >= goalFloors
if err := a.setFitbitTaskDraftAutoComplete(userID, int(floorsGoalTaskID.Int64), goalReached); err != nil {
log.Printf("Error setting floors goal task draft auto_complete: %v", err)
}
} }
log.Printf("Fitbit data synced for user_id=%d, date=%s: steps=%d, floors=%d, goalSteps=%d, goalFloors=%d", log.Printf("Fitbit data synced for user_id=%d, date=%s: steps=%d, floors=%d, goalSteps=%d, goalFloors=%d",
@@ -11248,6 +11258,50 @@ func (a *App) saveFitbitSubtaskDraft(userID int, taskID int, subtaskID int, chec
return nil return nil
} }
// setFitbitTaskDraftAutoComplete создаёт или обновляет драфт задачи, выставляя только флаг «Выполнить в конце дня».
// Используется для достижения цели по шагам/этажам, когда выбрана задача без подзадачи.
func (a *App) setFitbitTaskDraftAutoComplete(userID int, taskID int, autoComplete bool) error {
var exists bool
err := a.DB.QueryRow(`
SELECT EXISTS(SELECT 1 FROM tasks WHERE id = $1 AND user_id = $2 AND deleted = FALSE)
`, taskID, userID).Scan(&exists)
if err != nil || !exists {
return fmt.Errorf("task %d not found or not owned by user", taskID)
}
var draftID int
err = a.DB.QueryRow("SELECT id FROM task_drafts WHERE task_id = $1", taskID).Scan(&draftID)
if err == sql.ErrNoRows {
if !autoComplete {
return nil
}
_, err = a.DB.Exec(`
INSERT INTO task_drafts (task_id, user_id, auto_complete, created_at, updated_at)
VALUES ($1, $2, TRUE, NOW(), NOW())
`, taskID, userID)
if err != nil {
return fmt.Errorf("failed to create draft: %w", err)
}
log.Printf("Fitbit: created task draft for task_id=%d, auto_complete=true", taskID)
return nil
}
if err != nil {
return fmt.Errorf("failed to get draft: %w", err)
}
_, err = a.DB.Exec(`
UPDATE task_drafts SET auto_complete = $1, updated_at = NOW() WHERE id = $2
`, autoComplete, draftID)
if err != nil {
return fmt.Errorf("failed to update draft: %w", err)
}
if !autoComplete {
_, _ = a.DB.Exec("DELETE FROM task_draft_subtasks WHERE task_draft_id = $1", draftID)
}
log.Printf("Fitbit: set task draft auto_complete for task_id=%d to %v", taskID, autoComplete)
return nil
}
// fitbitSyncHandler выполняет ручную синхронизацию данных Fitbit // fitbitSyncHandler выполняет ручную синхронизацию данных Fitbit
func (a *App) fitbitSyncHandler(w http.ResponseWriter, r *http.Request) { func (a *App) fitbitSyncHandler(w http.ResponseWriter, r *http.Request) {
if r.Method == "OPTIONS" { if r.Method == "OPTIONS" {

View File

@@ -1,6 +1,6 @@
{ {
"name": "play-life-web", "name": "play-life-web",
"version": "5.1.1", "version": "5.1.2",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",

View File

@@ -437,12 +437,12 @@ function FitbitIntegration({ onNavigate }) {
className="w-full px-3 py-2 border border-gray-300 rounded-lg disabled:bg-gray-100" className="w-full px-3 py-2 border border-gray-300 rounded-lg disabled:bg-gray-100"
> >
<option value="">{loadingTasks ? 'Загрузка...' : 'Не выбрано'}</option> <option value="">{loadingTasks ? 'Загрузка...' : 'Не выбрано'}</option>
{!loadingTasks && getParentTasks().map(task => ( {!loadingTasks && tasks.map(task => (
<option key={task.id} value={task.id}>{task.name}</option> <option key={task.id} value={task.id}>{task.name}</option>
))} ))}
</select> </select>
</div> </div>
{editedBindings.steps_goal_task_id && ( {editedBindings.steps_goal_task_id && stepsGoalSubtasks.length > 0 && (
<div> <div>
<label className="block text-sm text-gray-600 mb-1">Подзадача</label> <label className="block text-sm text-gray-600 mb-1">Подзадача</label>
<select <select
@@ -531,12 +531,12 @@ function FitbitIntegration({ onNavigate }) {
className="w-full px-3 py-2 border border-gray-300 rounded-lg disabled:bg-gray-100" className="w-full px-3 py-2 border border-gray-300 rounded-lg disabled:bg-gray-100"
> >
<option value="">{loadingTasks ? 'Загрузка...' : 'Не выбрано'}</option> <option value="">{loadingTasks ? 'Загрузка...' : 'Не выбрано'}</option>
{!loadingTasks && getParentTasks().map(task => ( {!loadingTasks && tasks.map(task => (
<option key={task.id} value={task.id}>{task.name}</option> <option key={task.id} value={task.id}>{task.name}</option>
))} ))}
</select> </select>
</div> </div>
{editedBindings.floors_goal_task_id && ( {editedBindings.floors_goal_task_id && floorsGoalSubtasks.length > 0 && (
<div> <div>
<label className="block text-sm text-gray-600 mb-1">Подзадача</label> <label className="block text-sm text-gray-600 mb-1">Подзадача</label>
<select <select