Files
sundynix-pets/pets-be/internal/model/care_template.go
T
Blizzard 8b1b4cde02 refactor(be): 今日任务并进计划节点,养护方案改成可选、按重复规则配
## 三个问题,一起解决

你问「模版要不要做成可选」,摸下来发现底下有三个更硬的:

**① 根本没有多套模版可选。** 模板表唯一索引是 (species, stage) ——
同一个「猫 × 幼年期」只能有一套。「让用户选模版」在数据模型上做不到。
去掉唯一索引,加 Name / Summary / Sort / Enabled。

**② 今日任务和计划节点是两套,而且是我上一轮改出来的。**
  DailyTask  模板 kind=task 生成,ensureDayTasks 每天把昨天那份复制到今天
  PlanTask   模板 kind=plan 生成,Day 偏移
计划改成月度管理 + 加了「应用到每天」之后,两者做的是同一件事,
但首页读 DailyTask、计划页管 PlanTask —— 用户在计划页加一条「每天喂药」,
首页今日任务里看不到。

DailyTask 退役。task.go 重写成 PlanTask 的一层「按日期取」的门面,
/pets/:id/tasks 路径不变,小程序端不用为这次合并改调用。
PlanTask 加 Priority(首页那份要能标出哪条重要)。

顺手删掉重复的 CreateTask/UpdateTask/DeleteTask —— /plan-tasks 那套已经
覆盖了,留着两套会出现「首页管理弹层加的任务」和「计划页加的节点」进不同的表。

**③ 模板的 Day 偏移和月度计划对不上。** 月底建档的话 day=14/21/30 全落到
下个月,用户建完档打开计划页发现本月是空的。

改成重复规则:once(偏移 N 天,疫苗序列要用)/ daily / weekly(星期几)
/ monthly(几号)。套用 = 展开成目标月份的具体日期。
「每月 31 号」在 2 月按月末算,不然那个月直接没有。

列名用 repeat_rule 不用 repeat —— repeat 是 MySQL 保留字(和 group_key 同理)。

## 展开成实体行,不是「存规则读时展开」
勾选状态是按天的。存规则得再开一张表记「哪天打过勾」,而且用户把
「每周二梳毛」这周挪到周三就要引入例外记录。一次展开成行,增删改和打勾全免费。

代价是套用后模板和计划断开:后台改模板不影响已套用的宠物。这是有意的——
用户已经在自己的计划上勾了几十个勾,后台一改就被覆盖才是灾难。

## 建档不再自动铺
seedPetDefaults 只建必要提醒(疫苗、驱虫)。漏针是真会出事的,而且它们是
「到期提醒」不占计划页的格子。其余交给用户挑方案。

自动铺的问题是:用户还没看懂这个 App 是干什么的,首页已经躺着十几条他没
答应过的任务,第一反应是「这些是什么」而不是「我该做什么」。

**改完发现还有第二条自动铺的路径**:newStagePlan 在建计划壳子时顺手把模板
铺成节点。所以我改完 seedPetDefaults 之后,建档后首页仍然躺着 5 条任务。
惰性生成尤其难查——不是建档时发生的,是第一次读计划才发生的。

## 方案摘要按重复方式分开报数
一开始只报一个总数,「10 条」套用一整月展开成 130 条,用户会以为出 bug。
现在分成 每天 4 · 每周 0 · 每月 0 · 一次性 6 · 提醒 4。

## 数据迁移(预生产库,已执行)
  模板明细 kind=task → plan + repeat=daily     28 条
  模板明细 kind=plan 补 repeat=once            44 条
  8 套方案补名字和摘要
  DailyTask → PlanTask                        34 条,跳过 0
DailyTask 表的行**没删**,先留着当备份。

迁移第一版又踩了「列还没 AutoMigrate 出来就跑」那个坑:Count 静默返回 0,
我以为「没有需要补的」。第二版加了 HasColumn 前置检查才发现 44 条没转。
和上次 record_types.fields 回填是同一个错。

## 验证(预生产库实跑)
  今日任务/计划页今天  4 条 vs 4 条,一致
  建档后             0 任务 / 0 计划节点 / 4 条必要提醒
  套用本月           从今天到月末,4 条(7/30-7/31 两天)
  重复套用           0 条新增(按「日期+标题」去重)
  套用下月           130 条覆盖 31 天,每天 4.2 条
  全部完成           5 → 0 未做

## 还没做(下一步)
前端:建档第 4 步选方案、计划页空月份的套用入口、首页「管理」改跳计划页、
弹层删 manageTasks;后台模板页要支持多套 + 重复规则编辑。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 11:50:54 +08:00

78 lines
3.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package model
// 养护方案:一套「这个物种、这个阶段该做哪些事」的配置。
// 后台可以给同一个 (species, stage) 配多套,建档时让用户挑一套套用,
// 也可以不套用、自己排。原来是一个组合只能有一套(唯一索引卡着),
// 「让用户选模版」这件事在数据模型上做不到。
// 物种取值
const (
SpeciesCat = "cat"
SpeciesDog = "dog"
)
// 明细的类别。原来还有个 task(今日任务),已经和 plan 合并——
// 「今天要做的事」就是「今天那一天的计划节点」,两套并存的结果是
// 用户在计划页加的「每天喂药」在首页看不到。
const (
CareKindPlan = "plan" // 计划节点(含每天/每周/每月重复)
CareKindReminder = "reminder" // 到期提醒(疫苗、驱虫这类有固定周期的)
)
// 重复规则。月度计划要的是「多久做一次」,不是「第几天做」——
// 原来只有 Day 偏移,月底建档的话 day=14/21/30 全落到下个月,
// 用户建完档打开计划页发现本月是空的。
const (
RepeatOnce = "once" // 一次性,按 Day 偏移(到家第 N 天,疫苗序列要用)
RepeatDaily = "daily" // 每天
RepeatWeekly = "weekly" // 每周某几天,看 Weekdays
RepeatMonthly = "monthly" // 每月某天,看 MonthDay
)
// CareTemplate 一套方案
type CareTemplate struct {
Base
Species string `gorm:"size:16;index" json:"species"` // cat | dog
Stage string `gorm:"size:48;index" json:"stage"` // 与 onboarding 阶段一致
// Name 方案名。建档时用户看到的就是这个,要能一眼分辨
// (「幼猫标准照护」/「幼猫省心版」)
Name string `gorm:"size:64" json:"name"`
// Summary 一句话摘要,选择列表里展示
Summary string `gorm:"size:255" json:"summary"`
Sort int `json:"sort"`
Enabled bool `gorm:"default:true" json:"enabled"`
Items []CareTemplateItem `gorm:"foreignKey:TemplateID" json:"items"`
}
// CareTemplateItem 明细,kind 区分两类;不同类只用到部分字段
type CareTemplateItem struct {
Base
TemplateID string `gorm:"size:24;index" json:"template_id"`
Kind string `gorm:"size:16;index" json:"kind"` // plan | reminder
Sort int `json:"sort"`
Title string `gorm:"size:128" json:"title"`
Description string `gorm:"size:512" json:"description"`
// plan 专用
Priority string `gorm:"size:16" json:"priority"` // 重要 / 普通(空)
SheetType string `gorm:"size:32" json:"sheet_type"` // 关联记录类型 codeweight/poop/bath...
// Repeat 列名用 repeat_rulerepeat 是 MySQL 保留字,GORM 虽然会加反引号,
// 但手写 SQL 排查时很容易踩(和 group_key 同样的理由)
Repeat string `gorm:"column:repeat_rule;size:16" json:"repeat"` // once | daily | weekly | monthly
Day int `json:"day"` // once:从套用日起第 N 天(0=当天)
// DayLabel 留着:后台的 AI 生成模板那条链路在产出它。月度计划里日期是绝对的
// 不需要「第 N 天」标签,但删它要连着改 AI 解析,不在这次范围内
DayLabel string `gorm:"size:32" json:"day_label"`
// Weekdays weekly 用:逗号分隔的 1-7(1=周一)。
// 存字符串不另开表——一条模板明细最多 7 个数字,开表换来的只是查询灵活性,
// 而这份数据从来只整条读出来展开
Weekdays string `gorm:"size:16" json:"weekdays"`
MonthDay int `json:"month_day"` // monthly 用:几号(1-31),超出当月天数按月末算
// reminder 专用
ReminderType string `gorm:"size:32" json:"reminder_type"` // vaccine/deworm/weight/monthlyReport
OffsetDays int `json:"offset_days"` // 到期 = 今天 + N 天;<=0 表示无固定到期,用频率
Frequency string `gorm:"size:64" json:"frequency"` // 频率描述,如「每周二、周五」
}