refactor: expiry 模块按 handler/model/service 分层
This commit is contained in:
@@ -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)
|
||||
}
|
||||
Reference in New Issue
Block a user