refactor: expiry 模块按 handler/model/service 分层

This commit is contained in:
root
2026-03-09 21:13:59 +08:00
parent f45940cbc5
commit 27617cd373
6 changed files with 258 additions and 85 deletions
+133
View File
@@ -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, "服务器错误")
}
@@ -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{}) {
@@ -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)
}
@@ -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),
}
@@ -1,4 +1,4 @@
package expiry
package service
import (
"context"
@@ -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)
}