28b95a6468
后端 - 每日任务惰性生成:某天为空则从最近一天顺延复制(done 重置),首天用模板 - 任务增删改 API(POST /pets/:id/tasks、PUT/DELETE /tasks/:id),改动自动顺延 - 30 天计划到期自动归档并按当前阶段生成新一轮 - 内置养护模板重写为兽医常识向:狗每阶段含遛狗/牵引/狂犬,猫含猫砂/梳毛/饮水 - 文件 URL 改为按 object_name + 当前配置动态拼接,换 IP/域名不再有旧地址 小程序 - 首页今日任务加「管理」入口:增删改任务弹层 - 记录时间轴显示照片缩略图(可全屏预览)+ 拍照成功提示 - 社区发布条精简为单个「发布图文」按钮,去掉头像/输入框误触 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
176 lines
5.3 KiB
Go
176 lines
5.3 KiB
Go
package service
|
|
|
|
import (
|
|
"time"
|
|
|
|
"gorm.io/gorm"
|
|
|
|
"github.com/sundynix/pets-be/internal/model"
|
|
)
|
|
|
|
func dayStart(t time.Time) time.Time {
|
|
return time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location())
|
|
}
|
|
|
|
// ensureDayTasks 惰性生成某天的任务:今天/未来若为空,则从「最近一次有任务的那天」顺延复制,
|
|
// 无历史则用养护模板。过去的日子不补(保留历史真实)。
|
|
func (s *Service) ensureDayTasks(pet *model.Pet, day time.Time) {
|
|
start := dayStart(day)
|
|
if start.Before(dayStart(time.Now())) {
|
|
return
|
|
}
|
|
end := start.AddDate(0, 0, 1)
|
|
var cnt int64
|
|
s.db.Model(&model.DailyTask{}).Where("pet_id = ? AND task_date >= ? AND task_date < ?", pet.ID, start, end).Count(&cnt)
|
|
if cnt > 0 {
|
|
return
|
|
}
|
|
|
|
var defs []model.DailyTask
|
|
var latest model.DailyTask
|
|
if err := s.db.Where("pet_id = ? AND task_date < ?", pet.ID, start).Order("task_date desc, id desc").First(&latest).Error; err == nil {
|
|
ls := dayStart(latest.TaskDate)
|
|
var prev []model.DailyTask
|
|
s.db.Where("pet_id = ? AND task_date >= ? AND task_date < ?", pet.ID, ls, ls.AddDate(0, 0, 1)).Order("id asc").Find(&prev)
|
|
for _, t := range prev {
|
|
defs = append(defs, model.DailyTask{
|
|
PetID: pet.ID, UserID: pet.UserID, TaskDate: start,
|
|
Title: t.Title, Description: t.Description, Priority: t.Priority, SheetType: t.SheetType,
|
|
})
|
|
}
|
|
} else {
|
|
for _, it := range s.careTemplateItems(SpeciesOf(pet.Type), pet.Stage) {
|
|
if it.Kind == model.CareKindTask {
|
|
defs = append(defs, model.DailyTask{
|
|
PetID: pet.ID, UserID: pet.UserID, TaskDate: start,
|
|
Title: it.Title, Description: it.Description, Priority: it.Priority, SheetType: it.SheetType,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
if len(defs) > 0 {
|
|
s.db.Create(&defs)
|
|
}
|
|
}
|
|
|
|
// ListTasks 列出宠物某天任务(date 为空则取今天);今天/未来为空会惰性生成
|
|
func (s *Service) ListTasks(userID, petID string, date *time.Time) ([]model.DailyTask, error) {
|
|
pet, err := s.ownedPet(userID, petID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
target := time.Now()
|
|
if date != nil {
|
|
target = *date
|
|
}
|
|
s.ensureDayTasks(pet, target)
|
|
|
|
start := dayStart(target)
|
|
end := start.AddDate(0, 0, 1)
|
|
var tasks []model.DailyTask
|
|
err = s.db.Where("pet_id = ? AND task_date >= ? AND task_date < ?", petID, start, end).
|
|
Order("id asc").Find(&tasks).Error
|
|
return tasks, err
|
|
}
|
|
|
|
// TaskInput 增/改任务入参
|
|
type TaskInput struct {
|
|
Title string
|
|
Description string
|
|
Priority string
|
|
SheetType string
|
|
Date *time.Time // 仅新增用,默认今天
|
|
}
|
|
|
|
// CreateTask 为某天新增一条任务(默认今天)
|
|
func (s *Service) CreateTask(userID, petID string, in TaskInput) (*model.DailyTask, error) {
|
|
if _, err := s.ownedPet(userID, petID); err != nil {
|
|
return nil, err
|
|
}
|
|
day := time.Now()
|
|
if in.Date != nil {
|
|
day = *in.Date
|
|
}
|
|
t := model.DailyTask{
|
|
PetID: petID, UserID: userID, TaskDate: dayStart(day),
|
|
Title: in.Title, Description: in.Description, Priority: in.Priority, SheetType: in.SheetType,
|
|
}
|
|
if err := s.db.Create(&t).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return &t, nil
|
|
}
|
|
|
|
// UpdateTask 修改任务文案/优先级/关联弹层
|
|
func (s *Service) UpdateTask(userID, taskID string, in TaskInput) (*model.DailyTask, error) {
|
|
var t model.DailyTask
|
|
if err := s.db.Where("id = ? AND user_id = ?", taskID, userID).First(&t).Error; err != nil {
|
|
return nil, ErrNotFound
|
|
}
|
|
if err := s.db.Model(&model.DailyTask{}).Where("id = ?", taskID).Updates(map[string]any{
|
|
"title": in.Title, "description": in.Description, "priority": in.Priority, "sheet_type": in.SheetType,
|
|
}).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return s.UpdateTaskReload(taskID)
|
|
}
|
|
|
|
// UpdateTaskReload 重新读取任务
|
|
func (s *Service) UpdateTaskReload(taskID string) (*model.DailyTask, error) {
|
|
var t model.DailyTask
|
|
if err := s.db.First(&t, "id = ?", taskID).Error; err != nil {
|
|
return nil, ErrNotFound
|
|
}
|
|
return &t, nil
|
|
}
|
|
|
|
// DeleteTask 删除任务
|
|
func (s *Service) DeleteTask(userID, taskID string) error {
|
|
res := s.db.Where("id = ? AND user_id = ?", taskID, userID).Delete(&model.DailyTask{})
|
|
if res.Error != nil {
|
|
return res.Error
|
|
}
|
|
if res.RowsAffected == 0 {
|
|
return ErrNotFound
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ToggleTask 切换任务完成状态
|
|
func (s *Service) ToggleTask(userID, taskID string) (*model.DailyTask, error) {
|
|
var task model.DailyTask
|
|
if err := s.db.Where("id = ? AND user_id = ?", taskID, userID).First(&task).Error; err != nil {
|
|
if err == gorm.ErrRecordNotFound {
|
|
return nil, ErrNotFound
|
|
}
|
|
return nil, err
|
|
}
|
|
task.Done = !task.Done
|
|
if task.Done {
|
|
now := time.Now()
|
|
task.CompletedAt = &now
|
|
} else {
|
|
task.CompletedAt = nil
|
|
}
|
|
if err := s.db.Save(&task).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return &task, nil
|
|
}
|
|
|
|
// CompleteAllTasks 完成宠物今日全部任务
|
|
func (s *Service) CompleteAllTasks(userID, petID string) ([]model.DailyTask, error) {
|
|
if _, err := s.ownedPet(userID, petID); err != nil {
|
|
return nil, err
|
|
}
|
|
now := time.Now()
|
|
start := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
|
|
end := start.AddDate(0, 0, 1)
|
|
if err := s.db.Model(&model.DailyTask{}).
|
|
Where("pet_id = ? AND task_date >= ? AND task_date < ?", petID, start, end).
|
|
Updates(map[string]any{"done": true, "completed_at": now}).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return s.ListTasks(userID, petID, &now)
|
|
}
|