diff --git a/internal/expiry/expiry.go b/internal/expiry/expiry.go new file mode 100644 index 0000000..57b9c19 --- /dev/null +++ b/internal/expiry/expiry.go @@ -0,0 +1,133 @@ +package expiry + +import ( + "time" + + "github.com/gin-gonic/gin" + "github.com/redis/go-redis/v9" + "gorm.io/gorm" + + expiryhandler "wx_service/internal/expiry/handler" + expirymodel "wx_service/internal/expiry/model" + expiryrepo "wx_service/internal/expiry/repository" + expiryservice "wx_service/internal/expiry/service" +) + +const ( + CategoryFood = expirymodel.CategoryFood + CategoryMedicine = expirymodel.CategoryMedicine + CategoryCosmetic = expirymodel.CategoryCosmetic + CategoryOther = expirymodel.CategoryOther + + StatusNormal = expirymodel.StatusNormal + StatusExpiring = expirymodel.StatusExpiring + StatusExpired = expirymodel.StatusExpired + StatusUsed = expirymodel.StatusUsed + StatusDiscarded = expirymodel.StatusDiscarded +) + +var defaultRemindDays = expirymodel.DefaultRemindDays + +type ExpiryItem = expirymodel.ExpiryItem +type ExpiryUserSettings = expirymodel.ExpiryUserSettings + +type Repository = expiryrepo.Repository +type Service = expiryservice.Service +type Handler = expiryhandler.Handler +type SummaryCache = expiryservice.SummaryCache + +type CreateItemRequest = expiryservice.CreateItemRequest +type UpdateItemRequest = expiryservice.UpdateItemRequest +type ItemFilters = expiryservice.ItemFilters +type ItemView = expiryservice.ItemView +type ItemListResponse = expiryservice.ItemListResponse +type SummaryResponse = expiryservice.SummaryResponse +type SettingsResponse = expiryservice.SettingsResponse + +var ( + ErrExpiryItemNotFound = expiryrepo.ErrExpiryItemNotFound + + ErrExpiryNameInvalid = expiryservice.ErrExpiryNameInvalid + ErrExpiryCategoryInvalid = expiryservice.ErrExpiryCategoryInvalid + ErrExpiryMiniProgramRequired = expiryservice.ErrExpiryMiniProgramRequired + ErrExpiryQuantityInvalid = expiryservice.ErrExpiryQuantityInvalid + ErrExpiryLocationTooLong = expiryservice.ErrExpiryLocationTooLong + ErrExpiryRemarkTooLong = expiryservice.ErrExpiryRemarkTooLong + ErrExpiryDateRequired = expiryservice.ErrExpiryDateRequired + ErrExpiryShelfLifeDaysInvalid = expiryservice.ErrExpiryShelfLifeDaysInvalid + ErrExpiryFilterStatusInvalid = expiryservice.ErrExpiryFilterStatusInvalid + ErrExpiryFilterCategoryInvalid = expiryservice.ErrExpiryFilterCategoryInvalid + ErrExpiryFilterSortInvalid = expiryservice.ErrExpiryFilterSortInvalid + ErrExpiryStatusInvalid = expiryservice.ErrExpiryStatusInvalid + ErrExpiryRemindDaysInvalid = expiryservice.ErrExpiryRemindDaysInvalid +) + +func NewRepository(db *gorm.DB) *Repository { + return expiryrepo.NewRepository(db) +} + +func NewService(repo *Repository) *Service { + return expiryservice.NewService(repo) +} + +func NewHandler(service *Service) *Handler { + return expiryhandler.NewHandler(service) +} + +func NewSummaryCache(rc *redis.Client, keyPrefix string, ttl time.Duration) *SummaryCache { + return expiryservice.NewSummaryCache(rc, keyPrefix, ttl) +} + +func dateOnly(t time.Time) time.Time { + return expirymodel.DateOnly(t) +} + +func normalizeFilters(filters ItemFilters) ItemFilters { + return expiryservice.NormalizeFilters(filters) +} + +func validateFilters(filters ItemFilters) error { + return expiryservice.ValidateFilters(filters) +} + +func validateBaseFields(name, category string, quantity int, location, remark string) error { + return expiryservice.ValidateBaseFields(name, category, quantity, location, remark) +} + +func applyExpiryDate(item *ExpiryItem, expiryDate *time.Time, shelfLifeDays *int) error { + return expiryservice.ApplyExpiryDate(item, expiryDate, shelfLifeDays) +} + +func isValidCategory(category string) bool { + return expiryservice.IsValidCategory(category) +} + +func validateRemindDays(days []int) ([]int, error) { + return expiryservice.ValidateRemindDays(days) +} + +func toItemView(item ExpiryItem) ItemView { + return expiryservice.ToItemView(item) +} + +func writeExpirySuccess(c *gin.Context, message string, data interface{}) { + resp := gin.H{ + "code": 0, + "message": message, + } + if data != nil { + resp["data"] = data + } + c.JSON(200, resp) +} + +func writeExpiryError(c *gin.Context, code int, message string) { + c.JSON(code, gin.H{ + "code": code, + "message": message, + }) +} + +func writeExpiryServerError(c *gin.Context) { + writeExpiryError(c, 500, "服务器错误") +} diff --git a/internal/expiry/handler.go b/internal/expiry/handler/handler.go similarity index 81% rename from internal/expiry/handler.go rename to internal/expiry/handler/handler.go index 1702b20..3e93641 100644 --- a/internal/expiry/handler.go +++ b/internal/expiry/handler/handler.go @@ -1,4 +1,4 @@ -package expiry +package handler import ( "errors" @@ -10,15 +10,17 @@ import ( "github.com/gin-gonic/gin" + expiryrepo "wx_service/internal/expiry/repository" + expiryservice "wx_service/internal/expiry/service" "wx_service/internal/middleware" ) // Handler 负责 HTTP 层处理。 type Handler struct { - service *Service + service *expiryservice.Service } -func NewHandler(service *Service) *Handler { +func NewHandler(service *expiryservice.Service) *Handler { return &Handler{service: service} } @@ -72,7 +74,7 @@ func (h *Handler) GetItems(c *gin.Context) { page := parseIntWithDefault(c.Query("page"), 1) pageSize := parseIntWithDefault(c.Query("page_size"), 20) - filters := ItemFilters{ + filters := expiryservice.ItemFilters{ Status: strings.TrimSpace(c.DefaultQuery("status", "all")), Category: strings.TrimSpace(c.DefaultQuery("category", "all")), Sort: strings.TrimSpace(c.DefaultQuery("sort", "expiry_date")), @@ -119,7 +121,7 @@ func (h *Handler) CreateItem(c *gin.Context) { return } - writeExpirySuccess(c, "添加成功", toItemView(*item)) + writeExpirySuccess(c, "添加成功", expiryservice.ToItemView(*item)) } // UpdateItem 更新物品。 @@ -144,7 +146,7 @@ func (h *Handler) UpdateItem(c *gin.Context) { item, err := h.service.UpdateItem(id, user.ID, serviceReq) if err != nil { - if errors.Is(err, ErrExpiryItemNotFound) { + if errors.Is(err, expiryrepo.ErrExpiryItemNotFound) { writeExpiryError(c, http.StatusNotFound, "物品不存在") return } @@ -156,7 +158,7 @@ func (h *Handler) UpdateItem(c *gin.Context) { return } - writeExpirySuccess(c, "更新成功", toItemView(*item)) + writeExpirySuccess(c, "更新成功", expiryservice.ToItemView(*item)) } // DeleteItem 删除物品。 @@ -169,7 +171,7 @@ func (h *Handler) DeleteItem(c *gin.Context) { err := h.service.DeleteItem(id, user.ID) if err != nil { - if errors.Is(err, ErrExpiryItemNotFound) { + if errors.Is(err, expiryrepo.ErrExpiryItemNotFound) { writeExpiryError(c, http.StatusNotFound, "物品不存在") return } @@ -195,7 +197,7 @@ func (h *Handler) UpdateStatus(c *gin.Context) { } if err := h.service.UpdateItemStatus(id, user.ID, strings.TrimSpace(req.Status)); err != nil { - if errors.Is(err, ErrExpiryItemNotFound) { + if errors.Is(err, expiryrepo.ErrExpiryItemNotFound) { writeExpiryError(c, http.StatusNotFound, "物品不存在") return } @@ -256,15 +258,15 @@ func (h *Handler) UpdateSettings(c *gin.Context) { writeExpirySuccess(c, "更新成功", resp) } -func (h *Handler) toCreateItemRequest(miniProgramID uint, req createOrUpdateItemRequest) (CreateItemRequest, error) { +func (h *Handler) toCreateItemRequest(miniProgramID uint, req createOrUpdateItemRequest) (expiryservice.CreateItemRequest, error) { productionDate, err := parseDateString(req.ProductionDate) if err != nil { - return CreateItemRequest{}, errors.New("production_date 格式错误,应为 YYYY-MM-DD") + return expiryservice.CreateItemRequest{}, errors.New("production_date 格式错误,应为 YYYY-MM-DD") } expiryDate, err := parseDateString(req.ExpiryDate) if err != nil { - return CreateItemRequest{}, errors.New("expiry_date 格式错误,应为 YYYY-MM-DD") + return expiryservice.CreateItemRequest{}, errors.New("expiry_date 格式错误,应为 YYYY-MM-DD") } quantity := 1 @@ -272,7 +274,7 @@ func (h *Handler) toCreateItemRequest(miniProgramID uint, req createOrUpdateItem quantity = *req.Quantity } - return CreateItemRequest{ + return expiryservice.CreateItemRequest{ MiniProgramID: miniProgramID, Name: req.Name, Category: req.Category, @@ -318,19 +320,19 @@ func parseItemID(c *gin.Context) (uint, bool) { } func isExpiryBadRequestError(err error) bool { - return errors.Is(err, ErrExpiryNameInvalid) || - errors.Is(err, ErrExpiryCategoryInvalid) || - errors.Is(err, ErrExpiryMiniProgramRequired) || - errors.Is(err, ErrExpiryQuantityInvalid) || - errors.Is(err, ErrExpiryLocationTooLong) || - errors.Is(err, ErrExpiryRemarkTooLong) || - errors.Is(err, ErrExpiryDateRequired) || - errors.Is(err, ErrExpiryShelfLifeDaysInvalid) || - errors.Is(err, ErrExpiryFilterStatusInvalid) || - errors.Is(err, ErrExpiryFilterCategoryInvalid) || - errors.Is(err, ErrExpiryFilterSortInvalid) || - errors.Is(err, ErrExpiryStatusInvalid) || - errors.Is(err, ErrExpiryRemindDaysInvalid) + return errors.Is(err, expiryservice.ErrExpiryNameInvalid) || + errors.Is(err, expiryservice.ErrExpiryCategoryInvalid) || + errors.Is(err, expiryservice.ErrExpiryMiniProgramRequired) || + errors.Is(err, expiryservice.ErrExpiryQuantityInvalid) || + errors.Is(err, expiryservice.ErrExpiryLocationTooLong) || + errors.Is(err, expiryservice.ErrExpiryRemarkTooLong) || + errors.Is(err, expiryservice.ErrExpiryDateRequired) || + errors.Is(err, expiryservice.ErrExpiryShelfLifeDaysInvalid) || + errors.Is(err, expiryservice.ErrExpiryFilterStatusInvalid) || + errors.Is(err, expiryservice.ErrExpiryFilterCategoryInvalid) || + errors.Is(err, expiryservice.ErrExpiryFilterSortInvalid) || + errors.Is(err, expiryservice.ErrExpiryStatusInvalid) || + errors.Is(err, expiryservice.ErrExpiryRemindDaysInvalid) } func writeExpirySuccess(c *gin.Context, message string, data interface{}) { diff --git a/internal/expiry/model.go b/internal/expiry/model/model.go similarity index 95% rename from internal/expiry/model.go rename to internal/expiry/model/model.go index 02429d2..83794fd 100644 --- a/internal/expiry/model.go +++ b/internal/expiry/model/model.go @@ -1,4 +1,4 @@ -package expiry +package model import ( "time" @@ -23,7 +23,7 @@ const ( StatusDiscarded = "discarded" ) -var defaultRemindDays = []int{7, 3, 1} +var DefaultRemindDays = []int{7, 3, 1} // ExpiryItem 表示保质期物品数据模型。 type ExpiryItem struct { @@ -50,8 +50,8 @@ func (ExpiryItem) TableName() string { // CalculateDaysLeft 计算当前日期与过期日期之间的天数差。 func (item *ExpiryItem) CalculateDaysLeft() int { - now := dateOnly(time.Now()) - expiry := dateOnly(item.ExpiryDate) + now := DateOnly(time.Now()) + expiry := DateOnly(item.ExpiryDate) return int(expiry.Sub(now).Hours() / 24) } @@ -85,7 +85,7 @@ func (ExpiryUserSettings) TableName() string { return "expiry_user_settings" } -func dateOnly(t time.Time) time.Time { +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/repository.go b/internal/expiry/repository/repository.go similarity index 75% rename from internal/expiry/repository.go rename to internal/expiry/repository/repository.go index 3e02ffc..e9ff7ed 100644 --- a/internal/expiry/repository.go +++ b/internal/expiry/repository/repository.go @@ -1,10 +1,12 @@ -package expiry +package repository import ( "errors" "fmt" "time" + expirymodel "wx_service/internal/expiry/model" + "gorm.io/gorm" ) @@ -22,7 +24,7 @@ func NewRepository(db *gorm.DB) *Repository { } // Create 创建物品。 -func (r *Repository) Create(item *ExpiryItem) error { +func (r *Repository) Create(item *expirymodel.ExpiryItem) error { if err := r.db.Create(item).Error; err != nil { return fmt.Errorf("create expiry item: %w", err) } @@ -30,7 +32,7 @@ func (r *Repository) Create(item *ExpiryItem) error { } // Update 更新物品。 -func (r *Repository) Update(item *ExpiryItem) error { +func (r *Repository) Update(item *expirymodel.ExpiryItem) error { updates := map[string]interface{}{ "mini_program_id": item.MiniProgramID, "name": item.Name, @@ -44,7 +46,7 @@ func (r *Repository) Update(item *ExpiryItem) error { "status": item.Status, } - tx := r.db.Model(&ExpiryItem{}). + tx := r.db.Model(&expirymodel.ExpiryItem{}). Where("id = ? AND user_id = ?", item.ID, item.UserID). Updates(updates) if tx.Error != nil { @@ -58,7 +60,7 @@ func (r *Repository) Update(item *ExpiryItem) error { // Delete 软删除物品。 func (r *Repository) Delete(id, userID uint) error { - tx := r.db.Where("id = ? AND user_id = ?", id, userID).Delete(&ExpiryItem{}) + tx := r.db.Where("id = ? AND user_id = ?", id, userID).Delete(&expirymodel.ExpiryItem{}) if tx.Error != nil { return fmt.Errorf("delete expiry item: %w", tx.Error) } @@ -69,8 +71,8 @@ func (r *Repository) Delete(id, userID uint) error { } // FindByID 根据 ID 查询单个物品。 -func (r *Repository) FindByID(id, userID uint) (*ExpiryItem, error) { - var item ExpiryItem +func (r *Repository) FindByID(id, userID uint) (*expirymodel.ExpiryItem, error) { + var item expirymodel.ExpiryItem err := r.db.Where("id = ? AND user_id = ?", id, userID).First(&item).Error if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { @@ -86,7 +88,7 @@ func (r *Repository) FindByUser( userID uint, filters map[string]interface{}, page, pageSize int, -) ([]ExpiryItem, int64, error) { +) ([]expirymodel.ExpiryItem, int64, error) { if page <= 0 { page = 1 } @@ -97,22 +99,22 @@ func (r *Repository) FindByUser( pageSize = 100 } - query := r.db.Model(&ExpiryItem{}).Where("user_id = ?", userID) - now := dateOnly(time.Now()) + query := r.db.Model(&expirymodel.ExpiryItem{}).Where("user_id = ?", userID) + now := expirymodel.DateOnly(time.Now()) sevenDaysLater := now.AddDate(0, 0, 7) if status, ok := filters["status"].(string); ok && status != "" && status != "all" { switch status { - case StatusExpiring: - query = query.Where("status NOT IN ?", []string{StatusUsed, StatusDiscarded}). + case expirymodel.StatusExpiring: + query = query.Where("status NOT IN ?", []string{expirymodel.StatusUsed, expirymodel.StatusDiscarded}). Where("expiry_date BETWEEN ? AND ?", now, sevenDaysLater) - case StatusExpired: - query = query.Where("status NOT IN ?", []string{StatusUsed, StatusDiscarded}). + case expirymodel.StatusExpired: + query = query.Where("status NOT IN ?", []string{expirymodel.StatusUsed, expirymodel.StatusDiscarded}). Where("expiry_date < ?", now) - case StatusNormal: - query = query.Where("status NOT IN ?", []string{StatusUsed, StatusDiscarded}). + case expirymodel.StatusNormal: + query = query.Where("status NOT IN ?", []string{expirymodel.StatusUsed, expirymodel.StatusDiscarded}). Where("expiry_date > ?", sevenDaysLater) - case StatusUsed, StatusDiscarded: + case expirymodel.StatusUsed, expirymodel.StatusDiscarded: query = query.Where("status = ?", status) } } @@ -137,7 +139,7 @@ func (r *Repository) FindByUser( return nil, 0, fmt.Errorf("count expiry items: %w", err) } - var items []ExpiryItem + var items []expirymodel.ExpiryItem offset := (page - 1) * pageSize if err := query.Offset(offset).Limit(pageSize).Find(&items).Error; err != nil { return nil, 0, fmt.Errorf("list expiry items: %w", err) @@ -148,11 +150,11 @@ func (r *Repository) FindByUser( // GetSummary 获取首页汇总统计数据。 func (r *Repository) GetSummary(userID uint) (map[string]int, error) { - now := dateOnly(time.Now()) + now := expirymodel.DateOnly(time.Now()) sevenDaysLater := now.AddDate(0, 0, 7) base := func() *gorm.DB { - return r.db.Model(&ExpiryItem{}).Where("user_id = ?", userID) + return r.db.Model(&expirymodel.ExpiryItem{}).Where("user_id = ?", userID) } count := func(tx *gorm.DB) (int64, error) { @@ -163,35 +165,35 @@ func (r *Repository) GetSummary(userID uint) (map[string]int, error) { return v, nil } - totalItems, err := count(base().Where("status NOT IN ?", []string{StatusUsed, StatusDiscarded})) + totalItems, err := count(base().Where("status NOT IN ?", []string{expirymodel.StatusUsed, expirymodel.StatusDiscarded})) if err != nil { return nil, fmt.Errorf("count total items: %w", err) } - expiringSoon, err := count(base().Where("status NOT IN ?", []string{StatusUsed, StatusDiscarded}). + expiringSoon, err := count(base().Where("status NOT IN ?", []string{expirymodel.StatusUsed, expirymodel.StatusDiscarded}). Where("expiry_date BETWEEN ? AND ?", now, sevenDaysLater)) if err != nil { return nil, fmt.Errorf("count expiring items: %w", err) } - expired, err := count(base().Where("status NOT IN ?", []string{StatusUsed, StatusDiscarded}). + expired, err := count(base().Where("status NOT IN ?", []string{expirymodel.StatusUsed, expirymodel.StatusDiscarded}). Where("expiry_date < ?", now)) if err != nil { return nil, fmt.Errorf("count expired items: %w", err) } - normal, err := count(base().Where("status NOT IN ?", []string{StatusUsed, StatusDiscarded}). + normal, err := count(base().Where("status NOT IN ?", []string{expirymodel.StatusUsed, expirymodel.StatusDiscarded}). Where("expiry_date > ?", sevenDaysLater)) if err != nil { return nil, fmt.Errorf("count normal items: %w", err) } - used, err := count(base().Where("status = ?", StatusUsed)) + used, err := count(base().Where("status = ?", expirymodel.StatusUsed)) if err != nil { return nil, fmt.Errorf("count used items: %w", err) } - discarded, err := count(base().Where("status = ?", StatusDiscarded)) + discarded, err := count(base().Where("status = ?", expirymodel.StatusDiscarded)) if err != nil { return nil, fmt.Errorf("count discarded items: %w", err) } @@ -208,7 +210,7 @@ func (r *Repository) GetSummary(userID uint) (map[string]int, error) { // UpdateStatus 更新物品状态(used/discarded)。 func (r *Repository) UpdateStatus(id, userID uint, status string) error { - tx := r.db.Model(&ExpiryItem{}). + tx := r.db.Model(&expirymodel.ExpiryItem{}). Where("id = ? AND user_id = ?", id, userID). Update("status", status) if tx.Error != nil { @@ -221,8 +223,8 @@ func (r *Repository) UpdateStatus(id, userID uint, status string) error { } // GetSettings 查询用户提醒设置。 -func (r *Repository) GetSettings(userID uint) (*ExpiryUserSettings, error) { - var settings ExpiryUserSettings +func (r *Repository) GetSettings(userID uint) (*expirymodel.ExpiryUserSettings, error) { + var settings expirymodel.ExpiryUserSettings err := r.db.Where("user_id = ?", userID).First(&settings).Error if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { @@ -234,14 +236,14 @@ func (r *Repository) GetSettings(userID uint) (*ExpiryUserSettings, error) { } // UpdateSettings 更新用户提醒设置(不存在则创建)。 -func (r *Repository) UpdateSettings(userID uint, remindDays []int) (*ExpiryUserSettings, error) { +func (r *Repository) UpdateSettings(userID uint, remindDays []int) (*expirymodel.ExpiryUserSettings, error) { settings, err := r.GetSettings(userID) if err != nil { return nil, err } if settings == nil { - settings = &ExpiryUserSettings{ + settings = &expirymodel.ExpiryUserSettings{ UserID: userID, RemindDays: copyIntSlice(remindDays), } diff --git a/internal/expiry/cache.go b/internal/expiry/service/cache.go similarity index 99% rename from internal/expiry/cache.go rename to internal/expiry/service/cache.go index 5d119b0..4e2532f 100644 --- a/internal/expiry/cache.go +++ b/internal/expiry/service/cache.go @@ -1,4 +1,4 @@ -package expiry +package service import ( "context" diff --git a/internal/expiry/service.go b/internal/expiry/service/service.go similarity index 83% rename from internal/expiry/service.go rename to internal/expiry/service/service.go index 645e6d4..3346f7b 100644 --- a/internal/expiry/service.go +++ b/internal/expiry/service/service.go @@ -1,10 +1,13 @@ -package expiry +package service import ( "context" "errors" "strings" "time" + + expirymodel "wx_service/internal/expiry/model" + expiryrepo "wx_service/internal/expiry/repository" ) var ( @@ -25,7 +28,7 @@ var ( // Service 封装保质期模块业务逻辑。 type Service struct { - repo *Repository + repo *expiryrepo.Repository summaryCache *SummaryCache } @@ -94,7 +97,7 @@ type SettingsResponse struct { RemindDays []int `json:"remind_days"` } -func NewService(repo *Repository) *Service { +func NewService(repo *expiryrepo.Repository) *Service { return &Service{repo: repo} } @@ -104,7 +107,7 @@ func (s *Service) BindSummaryCache(cache *SummaryCache) { } // CreateItem 创建物品,并处理过期日期自动计算。 -func (s *Service) CreateItem(userID uint, req CreateItemRequest) (*ExpiryItem, error) { +func (s *Service) CreateItem(userID uint, req CreateItemRequest) (*expirymodel.ExpiryItem, error) { if req.MiniProgramID == 0 { return nil, ErrExpiryMiniProgramRequired } @@ -115,7 +118,7 @@ func (s *Service) CreateItem(userID uint, req CreateItemRequest) (*ExpiryItem, e return nil, ErrExpiryShelfLifeDaysInvalid } - item := &ExpiryItem{ + item := &expirymodel.ExpiryItem{ UserID: userID, MiniProgramID: req.MiniProgramID, Name: strings.TrimSpace(req.Name), @@ -123,7 +126,7 @@ func (s *Service) CreateItem(userID uint, req CreateItemRequest) (*ExpiryItem, e Quantity: req.Quantity, Location: strings.TrimSpace(req.Location), Remark: strings.TrimSpace(req.Remark), - Status: StatusNormal, + Status: expirymodel.StatusNormal, } if item.Quantity == 0 { @@ -131,7 +134,7 @@ func (s *Service) CreateItem(userID uint, req CreateItemRequest) (*ExpiryItem, e } if req.ProductionDate != nil { - pd := dateOnly(*req.ProductionDate) + pd := expirymodel.DateOnly(*req.ProductionDate) item.ProductionDate = &pd } @@ -149,7 +152,7 @@ func (s *Service) CreateItem(userID uint, req CreateItemRequest) (*ExpiryItem, e } // UpdateItem 更新物品。 -func (s *Service) UpdateItem(id, userID uint, req UpdateItemRequest) (*ExpiryItem, error) { +func (s *Service) UpdateItem(id, userID uint, req UpdateItemRequest) (*expirymodel.ExpiryItem, error) { item, err := s.repo.FindByID(id, userID) if err != nil { return nil, err @@ -178,7 +181,7 @@ func (s *Service) UpdateItem(id, userID uint, req UpdateItemRequest) (*ExpiryIte item.Quantity = 1 } if req.ProductionDate != nil { - pd := dateOnly(*req.ProductionDate) + pd := expirymodel.DateOnly(*req.ProductionDate) item.ProductionDate = &pd } @@ -187,8 +190,8 @@ func (s *Service) UpdateItem(id, userID uint, req UpdateItemRequest) (*ExpiryIte } // 已标记为 used/discarded 的物品保留状态,其余回归可计算状态。 - if item.Status != StatusUsed && item.Status != StatusDiscarded { - item.Status = StatusNormal + if item.Status != expirymodel.StatusUsed && item.Status != expirymodel.StatusDiscarded { + item.Status = expirymodel.StatusNormal } if err := s.repo.Update(item); err != nil { @@ -210,7 +213,7 @@ func (s *Service) DeleteItem(id, userID uint) error { } // GetItem 获取单个物品。 -func (s *Service) GetItem(id, userID uint) (*ExpiryItem, error) { +func (s *Service) GetItem(id, userID uint) (*expirymodel.ExpiryItem, error) { item, err := s.repo.FindByID(id, userID) if err != nil { return nil, err @@ -237,7 +240,7 @@ func (s *Service) GetItems(userID uint, filters ItemFilters) (*ItemListResponse, result := make([]ItemView, 0, len(items)) for _, item := range items { - result = append(result, toItemView(item)) + result = append(result, ToItemView(item)) } return &ItemListResponse{ @@ -275,7 +278,7 @@ func (s *Service) GetSummary(userID uint) (*SummaryResponse, error) { // UpdateItemStatus 标记物品状态。 func (s *Service) UpdateItemStatus(id, userID uint, status string) error { status = strings.TrimSpace(status) - if status != StatusUsed && status != StatusDiscarded { + if status != expirymodel.StatusUsed && status != expirymodel.StatusDiscarded { return ErrExpiryStatusInvalid } if err := s.repo.UpdateStatus(id, userID, status); err != nil { @@ -294,7 +297,7 @@ func (s *Service) GetSettings(userID uint) (*SettingsResponse, error) { if settings == nil || len(settings.RemindDays) == 0 { return &SettingsResponse{ - RemindDays: copyIntSlice(defaultRemindDays), + RemindDays: copyIntSlice(expirymodel.DefaultRemindDays), }, nil } @@ -344,16 +347,16 @@ func validateBaseFields(name, category string, quantity int, location, remark st return nil } -func applyExpiryDate(item *ExpiryItem, expiryDate *time.Time, shelfLifeDays *int) error { +func applyExpiryDate(item *expirymodel.ExpiryItem, expiryDate *time.Time, shelfLifeDays *int) error { // 若提供生产日期 + 保质期,优先自动计算过期日期。 if item.ProductionDate != nil && shelfLifeDays != nil { item.ShelfLifeDays = shelfLifeDays - item.ExpiryDate = dateOnly(item.ProductionDate.AddDate(0, 0, *shelfLifeDays)) + item.ExpiryDate = expirymodel.DateOnly(item.ProductionDate.AddDate(0, 0, *shelfLifeDays)) return nil } if expiryDate != nil { - item.ExpiryDate = dateOnly(*expiryDate) + item.ExpiryDate = expirymodel.DateOnly(*expiryDate) item.ShelfLifeDays = shelfLifeDays return nil } @@ -385,13 +388,13 @@ func normalizeFilters(filters ItemFilters) ItemFilters { func validateFilters(filters ItemFilters) error { switch filters.Status { - case "all", StatusExpiring, StatusExpired, StatusNormal, StatusUsed, StatusDiscarded: + case "all", expirymodel.StatusExpiring, expirymodel.StatusExpired, expirymodel.StatusNormal, expirymodel.StatusUsed, expirymodel.StatusDiscarded: default: return ErrExpiryFilterStatusInvalid } switch filters.Category { - case "all", CategoryFood, CategoryMedicine, CategoryCosmetic, CategoryOther: + case "all", expirymodel.CategoryFood, expirymodel.CategoryMedicine, expirymodel.CategoryCosmetic, expirymodel.CategoryOther: default: return ErrExpiryFilterCategoryInvalid } @@ -407,14 +410,14 @@ func validateFilters(filters ItemFilters) error { func isValidCategory(category string) bool { switch category { - case CategoryFood, CategoryMedicine, CategoryCosmetic, CategoryOther: + case expirymodel.CategoryFood, expirymodel.CategoryMedicine, expirymodel.CategoryCosmetic, expirymodel.CategoryOther: return true default: return false } } -func toItemView(item ExpiryItem) ItemView { +func ToItemView(item expirymodel.ExpiryItem) ItemView { calculatedStatus := item.CalculateStatus() return ItemView{ ID: item.ID, @@ -482,3 +485,36 @@ func (s *Service) invalidateSummaryCache(userID uint) { } _ = s.summaryCache.Delete(context.Background(), userID) } + +func copyIntSlice(values []int) []int { + if len(values) == 0 { + return nil + } + copied := make([]int, len(values)) + copy(copied, values) + return copied +} + +func NormalizeFilters(filters ItemFilters) ItemFilters { + return normalizeFilters(filters) +} + +func ValidateFilters(filters ItemFilters) error { + return validateFilters(filters) +} + +func ValidateBaseFields(name, category string, quantity int, location, remark string) error { + return validateBaseFields(name, category, quantity, location, remark) +} + +func ApplyExpiryDate(item *expirymodel.ExpiryItem, expiryDate *time.Time, shelfLifeDays *int) error { + return applyExpiryDate(item, expiryDate, shelfLifeDays) +} + +func IsValidCategory(category string) bool { + return isValidCategory(category) +} + +func ValidateRemindDays(days []int) ([]int, error) { + return validateRemindDays(days) +}