feat(expiry): 完成 #21 物品与设置模型实现

This commit is contained in:
root
2026-03-04 17:07:30 +08:00
parent f092019c61
commit 83fbbe7189
2 changed files with 162 additions and 10 deletions
+82 -10
View File
@@ -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)
}
+80
View File
@@ -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)
}
})
}
}