feat: 每日任务顺延与增删改 + 计划到期续期 + 靠谱养护模板 + 图片URL修复
后端 - 每日任务惰性生成:某天为空则从最近一天顺延复制(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>
This commit is contained in:
@@ -205,72 +205,131 @@ func careReminder(rtype, title string, offset int, freq string) model.CareTempla
|
||||
return model.CareTemplateItem{Kind: model.CareKindReminder, ReminderType: rtype, Title: title, OffsetDays: offset, Frequency: freq}
|
||||
}
|
||||
|
||||
// builtinCareItems 内置默认模板(8 套),既作开箱数据也作兜底
|
||||
// builtinCareItems 内置默认模板(猫/狗 × 4 阶段,共 8 套)。
|
||||
// 兽医常识向:狗每个阶段都含遛狗/牵引,猫含猫砂盆/梳毛/饮水;疫苗驱虫节奏按物种阶段区分。
|
||||
func builtinCareItems(species, stage string) []model.CareTemplateItem {
|
||||
isDog := species == model.SpeciesDog
|
||||
dewormAdult := 90
|
||||
if isDog {
|
||||
dewormAdult = 60 // 狗驱虫间隔通常比猫短
|
||||
if species == model.SpeciesDog {
|
||||
return builtinDog(stage)
|
||||
}
|
||||
return builtinCat(stage)
|
||||
}
|
||||
|
||||
func builtinCat(stage string) []model.CareTemplateItem {
|
||||
switch stage {
|
||||
case "刚到家 0-30 天":
|
||||
return []model.CareTemplateItem{
|
||||
careTask("记录一次体重", "到家后每周记录,建立基线", "重要", "weight"),
|
||||
careTask("观察饮水与排便", "换环境应激常见软便,连续观察几天", "", "poop"),
|
||||
careTask("布置安静休息角", "固定饮食、厕所与休息区,减少应激", "", ""),
|
||||
carePlan(0, "今天", "安顿与环境适应", "固定饮食、厕所、休息区位置", "taskDetail"),
|
||||
carePlan(3, "第3天", "观察排便与食欲", "记录应激缓解情况", "poop"),
|
||||
careTask("记录一次体重", "到家先称个底,之后每周一次看增长", "重要", "weight"),
|
||||
careTask("清理猫砂盆并观察", "换环境应激常见软便,留意颜色和次数", "", "poop"),
|
||||
careTask("陪玩 15 分钟", "逗猫棒建立信任,缓解应激,别强抱", "", ""),
|
||||
carePlan(0, "今天", "安顿与环境适应", "猫砂盆、食盆、水碗、猫窝固定位置,给它安静空间", "taskDetail"),
|
||||
carePlan(3, "第3天", "观察进食与排便", "食欲和便便恢复正常说明适应良好", "poop"),
|
||||
carePlan(7, "第7天", "第一次体重记录", "作为后续趋势基线", "weight"),
|
||||
carePlan(14, "第14天", "预约首次体检/疫苗", "确认医院与时间", "vaccine"),
|
||||
carePlan(14, "第14天", "预约首次疫苗/体检", "确认医院、驱虫和免疫计划", "vaccine"),
|
||||
careReminder("vaccine", "首次疫苗接种", 14, ""),
|
||||
careReminder("deworm", "首次体内外驱虫", 7, ""),
|
||||
careReminder("weight", "体重记录", 0, "每周一次"),
|
||||
careReminder("monthlyReport", "月度报告", 0, "每月 1 日"),
|
||||
}
|
||||
case "幼年期":
|
||||
vaccineTitle := "第 2 针疫苗"
|
||||
if isDog {
|
||||
vaccineTitle = "下一针联苗"
|
||||
}
|
||||
return []model.CareTemplateItem{
|
||||
careTask("记录一次体重", "幼年期每周至少记录 2 次", "重要", "weight"),
|
||||
careTask("观察饮水与排便", "换粮、应激都可能影响排便状态", "", "poop"),
|
||||
careTask("检查疫苗预约", "按免疫程序别漏针", "重要", "vaccine"),
|
||||
carePlan(0, "今天", "观察排便状态", "记录颜色、形态、次数", "taskDetail"),
|
||||
carePlan(1, "明天", "检查疫苗预约", "确认下一针医院与时间", "vaccine"),
|
||||
carePlan(7, "第7天", "体重趋势检查", "每周称重,观察稳定增长", "weight"),
|
||||
carePlan(14, "第14天", "复盘饮食与便便", "换粮记录与异常一起看", "taskDetail"),
|
||||
careReminder("vaccine", vaccineTitle, 18, ""),
|
||||
careReminder("deworm", "体内外驱虫", 12, ""),
|
||||
careTask("记录一次体重", "幼猫长得快,每周记 2 次", "重要", "weight"),
|
||||
careTask("梳毛并陪玩", "每天梳毛+两次互动,帮助社会化、减少毛球", "", ""),
|
||||
careTask("检查疫苗计划", "幼猫三针联苗按时打,别漏针", "重要", "vaccine"),
|
||||
carePlan(0, "今天", "确认免疫与驱虫计划", "对照月龄核对疫苗针次和驱虫时间", "vaccine"),
|
||||
carePlan(3, "第3天", "观察饮水与排便", "换粮期留意软便,7 天过渡换粮", "poop"),
|
||||
carePlan(7, "第7天", "体重趋势检查", "每周称重,稳定增长是关键指标", "weight"),
|
||||
carePlan(14, "第14天", "驱虫与饮食复盘", "确认驱虫到期、复盘换粮情况", "medicine"),
|
||||
careReminder("vaccine", "第 2 针疫苗", 18, ""),
|
||||
careReminder("deworm", "体内外驱虫", 30, ""),
|
||||
careReminder("weight", "体重记录", 0, "每周二、周五"),
|
||||
careReminder("monthlyReport", "月度报告", 0, "每月 1 日"),
|
||||
}
|
||||
case "老年期":
|
||||
return []model.CareTemplateItem{
|
||||
careTask("记录一次体重", "老年期体重波动要重视", "重要", "weight"),
|
||||
careTask("观察精神与食欲", "明显变差要尽快就医", "重要", "symptom"),
|
||||
careTask("关注饮水量", "多饮多尿可能是慢病信号", "", ""),
|
||||
carePlan(0, "今天", "老年健康自查", "精神、食欲、饮水一起看", "taskDetail"),
|
||||
carePlan(7, "第7天", "体重与饮水记录", "留意慢病早期信号", "weight"),
|
||||
carePlan(14, "第14天", "用药与体检回顾", "慢病管理与用药记录", "medicine"),
|
||||
carePlan(30, "第30天", "生成月度报告", "整理给医生看的记录", "report"),
|
||||
careTask("记录一次体重", "老年猫消瘦要警惕,别忽视", "重要", "weight"),
|
||||
careTask("关注饮水量与排尿", "多饮多尿常是肾病/糖尿信号,异常尽快就医", "重要", "symptom"),
|
||||
careTask("清理猫砂盆并观察", "关注尿量、尿色和排尿是否困难", "", "poop"),
|
||||
carePlan(0, "今天", "老年健康自查", "精神、食欲、饮水、体重整体过一遍", "taskDetail"),
|
||||
carePlan(7, "第7天", "体重与饮水记录", "两项一起看,慢病早发现", "weight"),
|
||||
carePlan(14, "第14天", "用药与慢病回顾", "整理长期用药和体检结果", "medicine"),
|
||||
carePlan(30, "第30天", "月度报告 / 建议体检", "老年猫建议每半年一次体检", "report"),
|
||||
careReminder("vaccine", "年度疫苗加强", 30, ""),
|
||||
careReminder("deworm", "体内外驱虫", dewormAdult, ""),
|
||||
careReminder("deworm", "体内外驱虫", 90, ""),
|
||||
careReminder("weight", "体重记录", 0, "每两周一次"),
|
||||
careReminder("monthlyReport", "月度报告 / 建议体检", 0, "每月 1 日"),
|
||||
careReminder("monthlyReport", "月度报告 / 半年体检", 0, "每月 1 日"),
|
||||
}
|
||||
default: // 成年期
|
||||
return []model.CareTemplateItem{
|
||||
careTask("记录一次体重", "成年期每月称重一次即可", "", "weight"),
|
||||
careTask("观察饮水与排便", "发现软便可连续观察几天", "", "poop"),
|
||||
careTask("梳毛与口腔检查", "关注牙结石与皮肤状态", "", ""),
|
||||
carePlan(0, "今天", "全面健康自查", "体重、精神、食欲、被毛过一遍", "taskDetail"),
|
||||
carePlan(7, "第7天", "体重趋势检查", "对比上月,观察是否稳定", "weight"),
|
||||
careTask("记录一次体重", "成年猫每月一次,防止肥胖", "", "weight"),
|
||||
careTask("清理猫砂盆并观察", "泌尿问题早发现,公猫尤其注意尿闭", "", "poop"),
|
||||
careTask("梳毛与口腔检查", "关注毛球、牙结石和皮肤状态", "", ""),
|
||||
carePlan(0, "今天", "全面健康自查", "体重、精神、食欲、饮水、被毛过一遍", "taskDetail"),
|
||||
carePlan(7, "第7天", "体重趋势检查", "对比上月,稳定为宜", "weight"),
|
||||
carePlan(14, "第14天", "驱虫记录复盘", "确认体内外驱虫是否到期", "medicine"),
|
||||
carePlan(30, "第30天", "生成月度报告", "回顾本月记录,导出成长卡片", "report"),
|
||||
careReminder("vaccine", "年度疫苗加强", 30, ""),
|
||||
careReminder("deworm", "体内外驱虫", dewormAdult, ""),
|
||||
careReminder("deworm", "体内外驱虫", 90, ""),
|
||||
careReminder("weight", "体重记录", 0, "每月一次"),
|
||||
careReminder("monthlyReport", "月度报告", 0, "每月 1 日"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func builtinDog(stage string) []model.CareTemplateItem {
|
||||
switch stage {
|
||||
case "刚到家 0-30 天":
|
||||
return []model.CareTemplateItem{
|
||||
careTask("记录一次体重", "到家先称个底,之后每周一次", "重要", "weight"),
|
||||
careTask("定点排便训练", "固定厕所位置,成功就表扬,别惩罚", "", "poop"),
|
||||
careTask("室内活动 / 自家院子", "疫苗未打全前少去人多狗多的地方,先在家熟悉环境", "", ""),
|
||||
carePlan(0, "今天", "安顿与环境适应", "食盆、水碗、狗窝、厕所固定位置", "taskDetail"),
|
||||
carePlan(3, "第3天", "观察进食与排便", "应激期常见软便,留意精神状态", "poop"),
|
||||
carePlan(7, "第7天", "第一次体重记录", "作为后续趋势基线", "weight"),
|
||||
carePlan(14, "第14天", "预约疫苗 / 体检", "确认联苗、狂犬和驱虫计划", "vaccine"),
|
||||
careReminder("vaccine", "首针疫苗接种", 14, ""),
|
||||
careReminder("deworm", "首次体内外驱虫", 7, ""),
|
||||
careReminder("weight", "体重记录", 0, "每周一次"),
|
||||
careReminder("monthlyReport", "月度报告", 0, "每月 1 日"),
|
||||
}
|
||||
case "幼年期":
|
||||
return []model.CareTemplateItem{
|
||||
careTask("遛狗与基础训练", "每天 2 次,牵引、坐下、召回,社会化关键期", "重要", ""),
|
||||
careTask("记录一次体重", "幼犬长得快,每周记 2 次", "重要", "weight"),
|
||||
careTask("观察饮水与排便", "换粮、驱虫期留意软便", "", "poop"),
|
||||
carePlan(0, "今天", "确认免疫与驱虫计划", "联苗+狂犬按月龄打,别漏针", "vaccine"),
|
||||
carePlan(1, "明天", "定点排便 + 牵引训练", "打好如厕和牵引基础", "taskDetail"),
|
||||
carePlan(7, "第7天", "体重趋势检查", "每周称重,稳定增长", "weight"),
|
||||
carePlan(14, "第14天", "驱虫与社会化复盘", "确认驱虫到期、多接触人和环境", "medicine"),
|
||||
careReminder("vaccine", "下一针联苗", 18, ""),
|
||||
careReminder("deworm", "体内外驱虫", 30, ""),
|
||||
careReminder("weight", "体重记录", 0, "每周二、周五"),
|
||||
careReminder("monthlyReport", "月度报告", 0, "每月 1 日"),
|
||||
}
|
||||
case "老年期":
|
||||
return []model.CareTemplateItem{
|
||||
careTask("温和遛狗", "缩短时长、避免剧烈跳跃,保护关节", "重要", ""),
|
||||
careTask("记录一次体重", "骤增骤减都要重视", "重要", "weight"),
|
||||
careTask("观察关节 / 精神 / 饮水", "后腿无力、多饮多尿要及时就医", "", "symptom"),
|
||||
carePlan(0, "今天", "老年健康自查", "精神、食欲、饮水、关节活动一起看", "taskDetail"),
|
||||
carePlan(7, "第7天", "体重与饮水记录", "慢病早发现", "weight"),
|
||||
carePlan(14, "第14天", "用药与慢病回顾", "整理长期用药和体检结果", "medicine"),
|
||||
carePlan(30, "第30天", "月度报告 / 建议体检", "老年犬建议每半年一次体检", "report"),
|
||||
careReminder("vaccine", "年度疫苗 + 狂犬加强", 30, ""),
|
||||
careReminder("deworm", "体内外驱虫", 60, ""),
|
||||
careReminder("weight", "体重记录", 0, "每两周一次"),
|
||||
careReminder("monthlyReport", "月度报告 / 半年体检", 0, "每月 1 日"),
|
||||
}
|
||||
default: // 成年期
|
||||
return []model.CareTemplateItem{
|
||||
careTask("遛狗", "每天 2 次共 30-60 分钟,消耗精力、防拆家", "重要", ""),
|
||||
careTask("记录一次体重", "每月一次,控制体重防肥胖", "", "weight"),
|
||||
careTask("刷牙 / 口腔检查", "每周 2-3 次,狗牙结石高发", "", ""),
|
||||
carePlan(0, "今天", "全面健康自查", "体重、精神、食欲、被毛、指甲过一遍", "taskDetail"),
|
||||
carePlan(7, "第7天", "体重趋势检查", "对比上月,稳定为宜", "weight"),
|
||||
carePlan(14, "第14天", "驱虫记录复盘", "遛狗接触多,确认驱虫是否到期", "medicine"),
|
||||
carePlan(30, "第30天", "生成月度报告", "回顾本月记录,导出成长卡片", "report"),
|
||||
careReminder("vaccine", "年度疫苗 + 狂犬加强", 30, ""),
|
||||
careReminder("deworm", "体内外驱虫", 60, ""),
|
||||
careReminder("weight", "体重记录", 0, "每月一次"),
|
||||
careReminder("monthlyReport", "月度报告", 0, "每月 1 日"),
|
||||
}
|
||||
|
||||
@@ -27,9 +27,10 @@ func (s *Service) UploadFile(reader io.Reader, filename, contentType string) (*m
|
||||
sum := md5.Sum(data)
|
||||
md5hex := hex.EncodeToString(sum[:])
|
||||
|
||||
// 已有相同内容 → 直接复用
|
||||
// 已有相同内容 → 直接复用(URL 按当前配置重建,避免旧地址)
|
||||
var existing model.File
|
||||
if err := s.db.Where("md5 = ?", md5hex).First(&existing).Error; err == nil {
|
||||
existing.URL = s.storage.PublicURL(existing.ObjectName)
|
||||
return &existing, nil
|
||||
}
|
||||
|
||||
@@ -55,16 +56,16 @@ func (s *Service) UploadFile(reader io.Reader, filename, contentType string) (*m
|
||||
return &f, nil
|
||||
}
|
||||
|
||||
// fileURL 按 file_id 取 URL(空 id 或不存在返回空串)
|
||||
// fileURL 按 file_id 取 URL(由 object_name + 当前配置拼出,空 id 或不存在返回空串)
|
||||
func (s *Service) fileURL(id string) string {
|
||||
if id == "" {
|
||||
return ""
|
||||
}
|
||||
var f model.File
|
||||
if err := s.db.Select("url").First(&f, "id = ?", id).Error; err != nil {
|
||||
if err := s.db.Select("object_name").First(&f, "id = ?", id).Error; err != nil {
|
||||
return ""
|
||||
}
|
||||
return f.URL
|
||||
return s.storage.PublicURL(f.ObjectName)
|
||||
}
|
||||
|
||||
// fileURLs 批量按 file_id 取 URL
|
||||
@@ -80,9 +81,9 @@ func (s *Service) fileURLs(ids []string) map[string]string {
|
||||
return out
|
||||
}
|
||||
var files []model.File
|
||||
s.db.Where("id IN ?", clean).Find(&files)
|
||||
s.db.Select("id, object_name").Where("id IN ?", clean).Find(&files)
|
||||
for _, f := range files {
|
||||
out[f.ID] = f.URL
|
||||
out[f.ID] = s.storage.PublicURL(f.ObjectName)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -24,14 +24,52 @@ func (s *Service) TogglePlanTask(userID, taskID string) error {
|
||||
return s.db.Model(&model.PlanTask{}).Where("id = ?", taskID).Update("done", !pt.Done).Error
|
||||
}
|
||||
|
||||
// GetPlan 取宠物的 30 天计划(含明细)
|
||||
// newStagePlan 按宠物当前阶段的养护模板生成一份 30 天计划(未保存)
|
||||
func (s *Service) newStagePlan(pet *model.Pet, start time.Time) model.Plan {
|
||||
var planTasks []model.PlanTask
|
||||
for _, it := range s.careTemplateItems(SpeciesOf(pet.Type), pet.Stage) {
|
||||
if it.Kind == model.CareKindPlan {
|
||||
planTasks = append(planTasks, model.PlanTask{
|
||||
Day: it.Day, DayLabel: it.DayLabel, Title: it.Title, Description: it.Description, SheetType: it.SheetType,
|
||||
})
|
||||
}
|
||||
}
|
||||
end := start.AddDate(0, 0, 30)
|
||||
return model.Plan{
|
||||
PetID: pet.ID, UserID: pet.UserID, Kind: model.PlanThirtyDay, Stage: pet.Stage,
|
||||
StartDate: &start, EndDate: &end, CompletionPct: 0, Status: "active", Tasks: planTasks,
|
||||
}
|
||||
}
|
||||
|
||||
// ensurePlan 计划到期自动续期:active 计划过期则归档并按当前阶段生成新一轮;无 active 则补建
|
||||
func (s *Service) ensurePlan(pet *model.Pet) {
|
||||
now := time.Now()
|
||||
var plan model.Plan
|
||||
err := s.db.Where("pet_id = ? AND kind = ? AND status = ?", pet.ID, model.PlanThirtyDay, "active").
|
||||
Order("id desc").First(&plan).Error
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
p := s.newStagePlan(pet, now)
|
||||
s.db.Create(&p)
|
||||
return
|
||||
}
|
||||
if err == nil && plan.EndDate != nil && plan.EndDate.Before(now) {
|
||||
s.db.Model(&model.Plan{}).Where("id = ?", plan.ID).Update("status", "archived")
|
||||
p := s.newStagePlan(pet, now)
|
||||
s.db.Create(&p)
|
||||
}
|
||||
}
|
||||
|
||||
// GetPlan 取宠物的 30 天计划(含明细);过期自动续期
|
||||
func (s *Service) GetPlan(userID, petID string) (*model.Plan, error) {
|
||||
if _, err := s.ownedPet(userID, petID); err != nil {
|
||||
pet, err := s.ownedPet(userID, petID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.ensurePlan(pet)
|
||||
var plan model.Plan
|
||||
err := s.db.Preload("Tasks").
|
||||
err = s.db.Preload("Tasks").
|
||||
Where("pet_id = ? AND kind = ?", petID, model.PlanThirtyDay).
|
||||
Where("status = ?", "active").
|
||||
Order("id desc").First(&plan).Error
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, ErrNotFound
|
||||
|
||||
@@ -39,18 +39,18 @@ func (s *Service) ListRecords(userID, petID string, recordType string, offset, l
|
||||
}
|
||||
var records []model.HealthRecord
|
||||
err := q.Order("occurred_at desc, id desc").Offset(offset).Limit(limit).Find(&records).Error
|
||||
// 用 file_id 回填图片 URL
|
||||
// 有 file_id 的记录,图片 URL 一律按当前配置重建(避免存旧地址)
|
||||
ids := make([]string, 0)
|
||||
for i := range records {
|
||||
if records[i].ImageFileID != "" && records[i].ImageURL == "" {
|
||||
if records[i].ImageFileID != "" {
|
||||
ids = append(ids, records[i].ImageFileID)
|
||||
}
|
||||
}
|
||||
if len(ids) > 0 {
|
||||
urls := s.fileURLs(ids)
|
||||
for i := range records {
|
||||
if records[i].ImageURL == "" {
|
||||
records[i].ImageURL = urls[records[i].ImageFileID]
|
||||
if u := urls[records[i].ImageFileID]; u != "" {
|
||||
records[i].ImageURL = u
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,20 +8,132 @@ import (
|
||||
"github.com/sundynix/pets-be/internal/model"
|
||||
)
|
||||
|
||||
// ListTasks 列出宠物任务(date 为空则取今天)
|
||||
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
|
||||
}
|
||||
q := s.db.Where("pet_id = ?", petID)
|
||||
if date != nil {
|
||||
start := time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, date.Location())
|
||||
end := start.AddDate(0, 0, 1)
|
||||
q = q.Where("task_date >= ? AND task_date < ?", start, end)
|
||||
day := time.Now()
|
||||
if in.Date != nil {
|
||||
day = *in.Date
|
||||
}
|
||||
var tasks []model.DailyTask
|
||||
err := q.Order("id asc").Find(&tasks).Error
|
||||
return tasks, err
|
||||
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 切换任务完成状态
|
||||
|
||||
Reference in New Issue
Block a user