当前顾客模块已经做过一轮实现,但起点选错了:
用户已经明确要求:
backend/docs/数据库设计.md 中 customer 表定义为唯一事实来源本次设计用于重做顾客模块最小闭环:
顾客详情页仍然是下一个 case,本次不实现。
customer 表对应的 Prisma 模型与 migrationPOST /api/v1/customersGET /api/v1/customersGET /api/v1/customers/summarytype 固定为 avatar本次顾客模块的结构定义,唯一以 数据库设计.md 为准。
这意味着:
当前仓库里已经存在一套错误起点的字段模型,例如:
kanaavatar_urlcontact_typeline_statuslast_visit_datenext_visit_datevisit_cyclehair_typehair_conditionconcerndesired_imageformula_noteprivate_notefollow_statusfollow_timingfollow_message这些字段不再作为顾客主模型继续保留。
处理原则:
type 固定为 avatar默认头像 URL:
https://storage.googleapis.com/aitori-public/style/sample/066c7ba22ea149268444bc2d919d5b97.png
page + pageSizetotal 表示当前筛选条件下的真实总数customer 表字段本次实现必须与以下字段语义对齐:
idnamename_pronunciationphoneemailbirthdayavatarexpect_contacthair_lengthhair_texturenoticeallergyinner_remarknext_follow_daterecommend_visit_datefirst_suggest_hairplan_dateis_first_registervisit_purposetroubleconsult_contentexpect_lookcreate_timeupdate_timeis_deleted当前数据库设计文档中的 customer 表没有 hairstylist_id 字段,但业务约束要求所有顾客数据按 JWT 中的发型师隔离。
因此本次需要做一个明确落地:
hairstylist_id 字段到真实表结构原因:
hairstylist_id,无法满足“所有顾客数据必须按当前发型师隔离”的硬约束hairstylist_id 做隔离因此最终真实落地的 customer 表应视为:
hairstylist_id 这一业务必需字段这是本次唯一允许的结构性补充,其余字段语义不另起新模型。
建议 Prisma 侧按以下枚举建模:
expect_contact: LINE | PHONE | EMAIL | NONEhair_length: SHORT | MEDIUM | LONG | UNCONFIRMhair_texture: UNCONFIRM | THIN | NORMAL | HARD_CRUDE | CURLYfirst_suggest_hair: AUTO | LONG | MEDIUM | SHORTvisit_purpose: CUT_DYE | CUT | DYE | CONSULT_SIM前端字段与数据库字段重新对齐如下:
name -> namekana -> name_pronunciationphone -> phoneemail -> emailbirthday -> birthdayimage -> avatarcontact -> expect_contacthairLength -> hair_lengthhairType -> hair_textureformulaNote / hairCondition -> 不再保留旧字段语义allergyNote -> allergyprivateNote -> inner_remarkfollowTiming -> next_follow_datenextVisit -> recommend_visit_datefollowStatus -> 不再直存数据库枚举,改由日期业务推导visitCycle -> 不再作为独立列,按需要映射到 visit_purposelastVisit -> 本次不作为主字段落库concern -> troubledesiredImage -> expect_lookfollowMessage -> consult_content 或 notice 不合适,故本次应从前端表单中移除旧语义字段lineStatus -> 本次没有对应列,应从当前顾客主表单移除notice 新增独立承接“发型护理注意事项”planDate 对应 plan_dateisFirstRegister 对应 is_first_registervisitPurpose 对应 visit_purpose本次不是简单改字段名,而是把前端表单也重新收口到真实业务语义:
POST /api/v1/customers
请求体以真实业务字段为中心,建议形态:
type CreateCustomerRequest = {
name: string
namePronunciation?: string
phone: string
email?: string
birthday?: string
avatar?: string
expectContact?: 'LINE' | 'PHONE' | 'EMAIL' | 'NONE'
hairLength?: 'SHORT' | 'MEDIUM' | 'LONG' | 'UNCONFIRM'
hairTexture?: 'UNCONFIRM' | 'THIN' | 'NORMAL' | 'HARD_CRUDE' | 'CURLY'
notice?: string
allergy?: string
innerRemark?: string
nextFollowDate?: string
recommendVisitDate?: string
firstSuggestHair?: 'AUTO' | 'LONG' | 'MEDIUM' | 'SHORT'
planDate?: string
isFirstRegister?: boolean
visitPurpose?: 'CUT_DYE' | 'CUT' | 'DYE' | 'CONSULT_SIM'
trouble?: string
consultContent?: string
expectLook?: string
}
规则:
name 必填phone 必填avatar 非必填avatar 未传,则后端自动写默认头像 URLhairstylist_id 不从前端传,后端从 JWT 中写入GET /api/v1/customers
查询参数:
type ListCustomersQuery = {
page?: number
pageSize?: number
q?: string
visitStatus?: 'ALL' | 'TODAY' | 'THIS_WEEK'
followStatus?: 'ALL' | 'NEED_FOLLOW_UP' | 'NO_NEED_FOLLOW_UP'
hairLength?: 'ALL' | 'SHORT' | 'MEDIUM' | 'LONG' | 'UNCONFIRM'
}
返回:
type ListCustomersResponse = {
items: CustomerListItem[]
total: number
page: number
pageSize: number
}
GET /api/v1/customers/summary
返回:
type CustomerSummaryResponse = {
total: number
today: number
followUp: number
simulated: number
}
q模糊查询以下字段:
namephoneinner_remarktroubleconsult_contentvisitStatus按 plan_date 判断:
TODAY: plan_date = 今天THIS_WEEK: plan_date 落在本周这里明确结论:
last_visit_date 的实现followStatus当前 SQL 设计没有 follow_status 枚举列,因此本次改为业务推导:
NEED_FOLLOW_UP: next_follow_date 非空,且日期小于等于今天NO_NEED_FOLLOW_UP: 其余情况这也意味着:
hairLength精确匹配 hair_length
is_deleted = 0plan_date = 今天next_follow_date 非空next_follow_date <= 今天ai_hair_simulation.customer_id 非空customer_id 去重列表页只返回当前页面真正需要的字段:
idnamephoneavatarplanDatetroublehairLengthfollowState其中:
followState 是接口返回的展示语义字段,不要求数据库存在同名列所有顾客接口必须按 JWT 中的 hairstylist_id 隔离。
规则:
hairstylist_idhairstylist_id 过滤hairstylist_id 过滤本次重做:
Customer modelCustomersService.create()CustomersService.list()CustomersService.getSummary()本次不做:
getById()update()本次重做:
本次不做:
开发完成后,旧文档必须同步替换,避免误导:
backend/docs/数据库设计.md 中与顾客模块相关的内容,要更新为最终真实落地版本文档目标是:
至少覆盖:
hairstylist_idplan_date / next_follow_date / hair_length 正确构造至少覆盖:
401至少覆盖:
需要明确删减旧字段,并补上新字段,不做“看起来能填,实际硬映射”的假适配。
30日以上未到店 依赖 last_visit_date,而目标 SQL 中没有这个字段。本次必须同步调整前端筛选项和后端查询语义,不能保留错误筛选。
实施时要以“替换错误模型”为目标,不做双轨兼容。
本次 case 的正确完成定义为:
customer 相关实现整体回到 数据库设计.md 的字段语义hairstylist_id 用于数据隔离