package listingstatus import ( "strings" "time" "hfb_sys/backend/internal/model" "hfb_sys/backend/internal/timeutil" "gorm.io/gorm" ) // 统计用的事件类型(与 to_status 对齐时优先用这些标准值)。 const ( EventPublished = "published" EventOffline = "offline" EventRented = "rented" EventCompleted = "completed" EventSealed = "sealed" EventAbnormal = "abnormal" ) // 来源 const ( SourceSeller = "seller" SourceAdmin = "admin" SourceReview = "review" SourceOrder = "order" SourcePickup = "pickup" SourceDispute = "dispute" SourceSystem = "system" ) // 操作者类型 const ( ActorUser = "user" ActorAdmin = "admin" ActorSystem = "system" ) // Entry 单次状态变更事件。 type Entry struct { ListingID uint64 OwnerID uint64 EventType string FromStatus string ToStatus string Source string ActorType string ActorID uint64 Remark string // CreatedAt 为空时使用上海时间当前时刻。 CreatedAt time.Time } // Append 写入一条状态事件。from==to 或 event 无法识别时跳过。 func Append(tx *gorm.DB, entry Entry) error { if tx == nil { return nil } from := strings.TrimSpace(entry.FromStatus) to := strings.TrimSpace(entry.ToStatus) if to == "" { to = strings.TrimSpace(entry.EventType) } if to == "" || from == to { return nil } eventType := strings.TrimSpace(entry.EventType) if eventType == "" { eventType = normalizeEventType(to) } if eventType == "" { return nil } createdAt := entry.CreatedAt if createdAt.IsZero() { createdAt = timeutil.ShanghaiNow() } row := model.ListingStatusEvent{ ListingID: entry.ListingID, OwnerID: entry.OwnerID, EventType: eventType, FromStatus: from, ToStatus: to, Source: strings.TrimSpace(entry.Source), ActorType: strings.TrimSpace(entry.ActorType), ActorID: entry.ActorID, Remark: strings.TrimSpace(entry.Remark), CreatedAt: createdAt, } return tx.Create(&row).Error } // AppendTransition 根据 listing 变更前后状态写事件。 func AppendTransition( tx *gorm.DB, listing *model.RentalListing, fromStatus string, source string, actorType string, actorID uint64, remark string, ) error { if listing == nil { return nil } return Append(tx, Entry{ ListingID: listing.ID, OwnerID: listing.OwnerID, EventType: normalizeEventType(listing.Status), FromStatus: fromStatus, ToStatus: listing.Status, Source: source, ActorType: actorType, ActorID: actorID, Remark: remark, }) } func normalizeEventType(status string) string { switch strings.TrimSpace(status) { case EventPublished, EventOffline, EventRented, EventCompleted, EventSealed, EventAbnormal: return status default: return "" } }