diff --git a/internal/expiry/model.go b/internal/expiry/model.go index 2fe889d..02429d2 100644 --- a/internal/expiry/model.go +++ b/internal/expiry/model.go @@ -1,19 +1,91 @@ package expiry -import "time" +import ( + "time" -// ExpiryItem 表示保质期物品。 -// 详细字段会在后续 issue 中完善。 + "gorm.io/gorm" +) + +const ( + // 物品分类 + CategoryFood = "food" + CategoryMedicine = "medicine" + CategoryCosmetic = "cosmetic" + CategoryOther = "other" +) + +const ( + // 物品状态 + StatusNormal = "normal" + StatusExpiring = "expiring" + StatusExpired = "expired" + StatusUsed = "used" + StatusDiscarded = "discarded" +) + +var defaultRemindDays = []int{7, 3, 1} + +// ExpiryItem 表示保质期物品数据模型。 type ExpiryItem struct { - ID uint `json:"id"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` + ID uint `json:"id" gorm:"primaryKey;comment:主键ID"` + UserID uint `json:"user_id" gorm:"not null;index:idx_user_expiry,priority:1;index:idx_user_category,priority:1;index:idx_user_status,priority:1;comment:用户ID"` + MiniProgramID uint `json:"mini_program_id" gorm:"not null;comment:小程序ID"` + Name string `json:"name" gorm:"size:100;not null;comment:物品名称"` + Category string `json:"category" gorm:"size:20;not null;index:idx_user_category,priority:2;comment:分类"` + ProductionDate *time.Time `json:"production_date" gorm:"type:date;comment:生产日期"` + ExpiryDate time.Time `json:"expiry_date" gorm:"type:date;not null;index:idx_user_expiry,priority:2;comment:过期日期"` + ShelfLifeDays *int `json:"shelf_life_days" gorm:"comment:保质期天数"` + Quantity int `json:"quantity" gorm:"default:1;comment:数量"` + Location string `json:"location" gorm:"size:50;comment:存放位置"` + Remark string `json:"remark" gorm:"size:255;comment:备注"` + Status string `json:"status" gorm:"size:20;default:'normal';index:idx_user_status,priority:2;comment:状态"` + CreatedAt time.Time `json:"created_at" gorm:"comment:创建时间"` + UpdatedAt time.Time `json:"updated_at" gorm:"comment:更新时间"` + DeletedAt gorm.DeletedAt `json:"-" gorm:"index:idx_deleted_at;comment:删除时间"` +} + +func (ExpiryItem) TableName() string { + return "expiry_items" +} + +// CalculateDaysLeft 计算当前日期与过期日期之间的天数差。 +func (item *ExpiryItem) CalculateDaysLeft() int { + now := dateOnly(time.Now()) + expiry := dateOnly(item.ExpiryDate) + return int(expiry.Sub(now).Hours() / 24) +} + +// CalculateStatus 根据过期日期动态计算状态。 +// 若已被业务标记为 used/discarded,则保留该状态。 +func (item *ExpiryItem) CalculateStatus() string { + if item.Status == StatusUsed || item.Status == StatusDiscarded { + return item.Status + } + + daysLeft := item.CalculateDaysLeft() + if daysLeft < 0 { + return StatusExpired + } + if daysLeft <= 7 { + return StatusExpiring + } + return StatusNormal } // ExpiryUserSettings 表示用户提醒设置。 -// 详细字段会在后续 issue 中完善。 type ExpiryUserSettings struct { - ID uint `json:"id"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` + ID uint `json:"id" gorm:"primaryKey;comment:主键ID"` + UserID uint `json:"user_id" gorm:"not null;uniqueIndex;comment:用户ID"` + RemindDays []int `json:"remind_days" gorm:"type:json;serializer:json;comment:提醒天数"` + CreatedAt time.Time `json:"created_at" gorm:"comment:创建时间"` + UpdatedAt time.Time `json:"updated_at" gorm:"comment:更新时间"` +} + +func (ExpiryUserSettings) TableName() string { + return "expiry_user_settings" +} + +func dateOnly(t time.Time) time.Time { + year, month, day := t.In(time.Local).Date() + return time.Date(year, month, day, 0, 0, 0, 0, time.Local) } diff --git a/internal/expiry/model_test.go b/internal/expiry/model_test.go new file mode 100644 index 0000000..a1e1e9c --- /dev/null +++ b/internal/expiry/model_test.go @@ -0,0 +1,80 @@ +package expiry + +import ( + "testing" + "time" +) + +func TestCalculateDaysLeft(t *testing.T) { + base := dateOnly(time.Now()) + item := &ExpiryItem{ + ExpiryDate: base.AddDate(0, 0, 5), + } + + if daysLeft := item.CalculateDaysLeft(); daysLeft != 5 { + t.Fatalf("expected daysLeft=5, got %d", daysLeft) + } +} + +func TestCalculateStatus(t *testing.T) { + base := dateOnly(time.Now()) + tests := []struct { + name string + item ExpiryItem + wantStatus string + }{ + { + name: "已过期", + item: ExpiryItem{ + ExpiryDate: base.AddDate(0, 0, -1), + }, + wantStatus: StatusExpired, + }, + { + name: "今天过期", + item: ExpiryItem{ + ExpiryDate: base, + }, + wantStatus: StatusExpiring, + }, + { + name: "7天内即将过期", + item: ExpiryItem{ + ExpiryDate: base.AddDate(0, 0, 7), + }, + wantStatus: StatusExpiring, + }, + { + name: "正常状态", + item: ExpiryItem{ + ExpiryDate: base.AddDate(0, 0, 8), + }, + wantStatus: StatusNormal, + }, + { + name: "保留已使用状态", + item: ExpiryItem{ + ExpiryDate: base.AddDate(0, 0, -10), + Status: StatusUsed, + }, + wantStatus: StatusUsed, + }, + { + name: "保留已丢弃状态", + item: ExpiryItem{ + ExpiryDate: base.AddDate(0, 0, -10), + Status: StatusDiscarded, + }, + wantStatus: StatusDiscarded, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := tt.item.CalculateStatus() + if got != tt.wantStatus { + t.Fatalf("expected status=%s, got %s", tt.wantStatus, got) + } + }) + } +}