fix: исправлен расчет даты переноса задач с периодами повторения
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 54s

- Добавлена поддержка сокращенных форм единиц времени (mons, min, hrs, wks, yrs и т.д.)
- Исправлена обработка недель, которые PostgreSQL возвращает как дни (7 days вместо 1 week)
- Добавлено приведение repetition_period к тексту при чтении из БД
- Обновлена версия до 3.8.6
This commit is contained in:
poignatov
2026-01-11 15:09:32 +03:00
parent 29cf05a3c3
commit f3a7d1c503
5 changed files with 64 additions and 13 deletions

View File

@@ -1 +1 @@
3.8.5
3.8.6

View File

@@ -374,7 +374,8 @@ func calculateNextShowAtFromRepetitionDate(repetitionDate string, fromDate time.
}
// calculateNextShowAtFromRepetitionPeriod calculates the next show date by adding repetition_period to fromDate
// Format: PostgreSQL INTERVAL string (e.g., "1 day", "2 weeks", "3 months")
// Format: PostgreSQL INTERVAL string (e.g., "1 day", "2 weeks", "3 months" or "3 mons")
// Note: PostgreSQL may return weeks as days (e.g., "7 days" instead of "1 week")
func calculateNextShowAtFromRepetitionPeriod(repetitionPeriod string, fromDate time.Time) *time.Time {
if repetitionPeriod == "" {
return nil
@@ -382,33 +383,45 @@ func calculateNextShowAtFromRepetitionPeriod(repetitionPeriod string, fromDate t
parts := strings.Fields(strings.TrimSpace(repetitionPeriod))
if len(parts) < 2 {
log.Printf("calculateNextShowAtFromRepetitionPeriod: invalid format, parts=%v", parts)
return nil
}
value, err := strconv.Atoi(parts[0])
if err != nil {
log.Printf("calculateNextShowAtFromRepetitionPeriod: failed to parse value '%s': %v", parts[0], err)
return nil
}
unit := strings.ToLower(parts[1])
log.Printf("calculateNextShowAtFromRepetitionPeriod: value=%d, unit='%s'", value, unit)
// Start from fromDate at midnight
nextDate := time.Date(fromDate.Year(), fromDate.Month(), fromDate.Day(), 0, 0, 0, 0, fromDate.Location())
switch unit {
case "minute", "minutes":
case "minute", "minutes", "mins", "min":
nextDate = nextDate.Add(time.Duration(value) * time.Minute)
case "hour", "hours":
case "hour", "hours", "hrs", "hr":
nextDate = nextDate.Add(time.Duration(value) * time.Hour)
case "day", "days":
// PostgreSQL может возвращать недели как дни (например, "7 days" вместо "1 week")
// Если количество дней кратно 7, обрабатываем как недели
if value%7 == 0 && value >= 7 {
weeks := value / 7
nextDate = nextDate.AddDate(0, 0, weeks*7)
} else {
nextDate = nextDate.AddDate(0, 0, value)
case "week", "weeks":
}
case "week", "weeks", "wks", "wk":
nextDate = nextDate.AddDate(0, 0, value*7)
case "month", "months":
case "month", "months", "mons", "mon":
nextDate = nextDate.AddDate(0, value, 0)
case "year", "years":
log.Printf("calculateNextShowAtFromRepetitionPeriod: added %d months, result=%v", value, nextDate)
case "year", "years", "yrs", "yr":
nextDate = nextDate.AddDate(value, 0, 0)
default:
log.Printf("calculateNextShowAtFromRepetitionPeriod: unknown unit '%s'", unit)
return nil
}
@@ -7503,7 +7516,7 @@ func (a *App) completeTaskHandler(w http.ResponseWriter, r *http.Request) {
var ownerID int
err = a.DB.QueryRow(`
SELECT id, name, reward_message, progression_base, repetition_period, repetition_date, user_id
SELECT id, name, reward_message, progression_base, repetition_period::text, repetition_date, user_id
FROM tasks
WHERE id = $1 AND deleted = FALSE
`, taskID).Scan(&task.ID, &task.Name, &rewardMessage, &progressionBase, &repetitionPeriod, &repetitionDate, &ownerID)
@@ -7797,14 +7810,17 @@ func (a *App) completeTaskHandler(w http.ResponseWriter, r *http.Request) {
// Обычный период: обновляем счетчик и last_completed_at, вычисляем next_show_at
// next_show_at = last_completed_at + repetition_period
now := time.Now()
log.Printf("Calculating next_show_at for task %d: repetition_period='%s', fromDate=%v", taskID, repetitionPeriod.String, now)
nextShowAt := calculateNextShowAtFromRepetitionPeriod(repetitionPeriod.String, now)
if nextShowAt != nil {
log.Printf("Calculated next_show_at for task %d: %v", taskID, *nextShowAt)
_, err = a.DB.Exec(`
UPDATE tasks
SET completed = completed + 1, last_completed_at = NOW(), next_show_at = $2
WHERE id = $1
`, taskID, nextShowAt)
} else {
log.Printf("Failed to calculate next_show_at for task %d: repetition_period='%s' returned nil", taskID, repetitionPeriod.String)
// Если не удалось вычислить дату, обновляем как обычно
_, err = a.DB.Exec(`
UPDATE tasks
@@ -7896,7 +7912,7 @@ func (a *App) completeAndDeleteTaskHandler(w http.ResponseWriter, r *http.Reques
var ownerID int
err = a.DB.QueryRow(`
SELECT id, name, reward_message, progression_base, repetition_period, repetition_date, user_id
SELECT id, name, reward_message, progression_base, repetition_period::text, repetition_date, user_id
FROM tasks
WHERE id = $1 AND deleted = FALSE
`, taskID).Scan(&task.ID, &task.Name, &rewardMessage, &progressionBase, &repetitionPeriod, &repetitionDate, &ownerID)

View File

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

View File

@@ -99,6 +99,7 @@ const calculateNextDateFromRepetitionDate = (repetitionDateStr) => {
}
// Функция для вычисления следующей даты по repetition_period
// Поддерживает сокращенные формы единиц времени (например, "mons" для месяцев)
const calculateNextDateFromRepetitionPeriod = (repetitionPeriodStr) => {
if (!repetitionPeriodStr) return null
@@ -117,26 +118,43 @@ const calculateNextDateFromRepetitionPeriod = (repetitionPeriodStr) => {
switch (unit) {
case 'minute':
case 'minutes':
case 'mins':
case 'min':
nextDate.setMinutes(nextDate.getMinutes() + value)
break
case 'hour':
case 'hours':
case 'hrs':
case 'hr':
nextDate.setHours(nextDate.getHours() + value)
break
case 'day':
case 'days':
// PostgreSQL может возвращать недели как дни (например, "7 days" вместо "1 week")
// Если количество дней кратно 7, обрабатываем как недели
if (value % 7 === 0 && value >= 7) {
const weeks = value / 7
nextDate.setDate(nextDate.getDate() + weeks * 7)
} else {
nextDate.setDate(nextDate.getDate() + value)
}
break
case 'week':
case 'weeks':
case 'wks':
case 'wk':
nextDate.setDate(nextDate.getDate() + value * 7)
break
case 'month':
case 'months':
case 'mons':
case 'mon':
nextDate.setMonth(nextDate.getMonth() + value)
break
case 'year':
case 'years':
case 'yrs':
case 'yr':
nextDate.setFullYear(nextDate.getFullYear() + value)
break
default:

View File

@@ -139,26 +139,43 @@ function TaskList({ onNavigate, data, loading, backgroundLoading, onRefresh }) {
switch (unit) {
case 'minute':
case 'minutes':
case 'mins':
case 'min':
nextDate.setMinutes(nextDate.getMinutes() + value)
break
case 'hour':
case 'hours':
case 'hrs':
case 'hr':
nextDate.setHours(nextDate.getHours() + value)
break
case 'day':
case 'days':
// PostgreSQL может возвращать недели как дни (например, "7 days" вместо "1 week")
// Если количество дней кратно 7, обрабатываем как недели
if (value % 7 === 0 && value >= 7) {
const weeks = value / 7
nextDate.setDate(nextDate.getDate() + weeks * 7)
} else {
nextDate.setDate(nextDate.getDate() + value)
}
break
case 'week':
case 'weeks':
case 'wks':
case 'wk':
nextDate.setDate(nextDate.getDate() + value * 7)
break
case 'month':
case 'months':
case 'mons':
case 'mon':
nextDate.setMonth(nextDate.getMonth() + value)
break
case 'year':
case 'years':
case 'yrs':
case 'yr':
nextDate.setFullYear(nextDate.getFullYear() + value)
break
default: