236 lines
7.1 KiB
Go
236 lines
7.1 KiB
Go
package propsstore
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"chatapp3-golang/internal/model"
|
|
"chatapp3-golang/internal/utils"
|
|
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// MapBySourceIDs returns active store commodities keyed by source id.
|
|
func (s *Service) MapBySourceIDs(ctx context.Context, req MapBySourceIDsRequest) (map[string]CommodityView, error) {
|
|
if s == nil || s.db == nil {
|
|
return nil, NewAppError(http.StatusServiceUnavailable, "props_store_unavailable", "props store service is unavailable")
|
|
}
|
|
sysOrigin := normalizeSysOrigin(req.SysOrigin)
|
|
if sysOrigin == "" {
|
|
return nil, NewAppError(http.StatusBadRequest, "sys_origin_required", "sysOrigin is required")
|
|
}
|
|
sourceIDs := uniquePositiveSourceIDs(req.SourceIDs)
|
|
if len(sourceIDs) == 0 {
|
|
return map[string]CommodityView{}, nil
|
|
}
|
|
|
|
var rows []model.PropsCommodityStore
|
|
if err := s.db.WithContext(ctx).
|
|
Model(&model.PropsCommodityStore{}).
|
|
Where("sys_origin = ? AND source_id IN ?", sysOrigin, sourceIDs).
|
|
Where("(is_del = ? OR is_del IS NULL)", false).
|
|
Order("update_time DESC").
|
|
Find(&rows).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
result := make(map[string]CommodityView, len(rows))
|
|
for _, row := range rows {
|
|
key := strconv.FormatInt(row.SourceID, 10)
|
|
if _, exists := result[key]; exists {
|
|
continue
|
|
}
|
|
result[key] = commodityViewFromModel(row)
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
// SaveCommodity creates or updates one store commodity by sysOrigin and sourceId.
|
|
func (s *Service) SaveCommodity(ctx context.Context, req SaveCommodityRequest) (*CommodityView, error) {
|
|
if s == nil || s.db == nil {
|
|
return nil, NewAppError(http.StatusServiceUnavailable, "props_store_unavailable", "props store service is unavailable")
|
|
}
|
|
if err := validateSaveCommodityRequest(req); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
sysOrigin := normalizeSysOrigin(req.SysOrigin)
|
|
sourceID := req.SourceID.Int64()
|
|
propsType := strings.ToUpper(strings.TrimSpace(req.PropsType))
|
|
discount := 0.0
|
|
if req.Discount != nil {
|
|
discount = *req.Discount
|
|
}
|
|
del := false
|
|
now := s.now()
|
|
var savedID int64
|
|
var previousPropsType string
|
|
|
|
err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
|
var existing model.PropsCommodityStore
|
|
findErr := tx.Where("sys_origin = ? AND source_id = ?", sysOrigin, sourceID).
|
|
Order("update_time DESC").
|
|
First(&existing).Error
|
|
if findErr != nil && !errors.Is(findErr, gorm.ErrRecordNotFound) {
|
|
return findErr
|
|
}
|
|
|
|
if findErr == nil {
|
|
previousPropsType = existing.PropsType
|
|
savedID = existing.ID
|
|
updates := map[string]any{
|
|
"sys_origin": sysOrigin,
|
|
"source_id": sourceID,
|
|
"props_type": propsType,
|
|
"currency_types": strings.ToUpper(strings.TrimSpace(req.CurrencyTypes)),
|
|
"valid_days": strings.TrimSpace(req.ValidDays),
|
|
"discount": discount,
|
|
"shelf_status": *req.ShelfStatus,
|
|
"sort": req.Sort,
|
|
"label": strings.TrimSpace(req.Label),
|
|
"is_del": del,
|
|
"update_time": now,
|
|
}
|
|
return tx.Model(&model.PropsCommodityStore{}).
|
|
Where("id = ?", existing.ID).
|
|
Updates(updates).Error
|
|
}
|
|
|
|
id, idErr := utils.NextID()
|
|
if idErr != nil {
|
|
return idErr
|
|
}
|
|
savedID = id
|
|
row := model.PropsCommodityStore{
|
|
ID: id,
|
|
SysOrigin: sysOrigin,
|
|
SourceID: sourceID,
|
|
PropsType: propsType,
|
|
CurrencyTypes: strings.ToUpper(strings.TrimSpace(req.CurrencyTypes)),
|
|
ValidDays: strings.TrimSpace(req.ValidDays),
|
|
Discount: &discount,
|
|
ShelfStatus: req.ShelfStatus,
|
|
Sort: req.Sort,
|
|
Label: strings.TrimSpace(req.Label),
|
|
Del: &del,
|
|
CreateTime: &now,
|
|
UpdateTime: &now,
|
|
}
|
|
return tx.Create(&row).Error
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
s.evictStoreCache(ctx, sysOrigin, propsType)
|
|
if strings.TrimSpace(previousPropsType) != "" && !strings.EqualFold(previousPropsType, propsType) {
|
|
s.evictStoreCache(ctx, sysOrigin, previousPropsType)
|
|
}
|
|
|
|
var saved model.PropsCommodityStore
|
|
if err := s.db.WithContext(ctx).Where("id = ?", savedID).First(&saved).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
view := commodityViewFromModel(saved)
|
|
return &view, nil
|
|
}
|
|
|
|
func validateSaveCommodityRequest(req SaveCommodityRequest) error {
|
|
if normalizeSysOrigin(req.SysOrigin) == "" {
|
|
return NewAppError(http.StatusBadRequest, "sys_origin_required", "sysOrigin is required")
|
|
}
|
|
if req.SourceID.Int64() <= 0 {
|
|
return NewAppError(http.StatusBadRequest, "source_id_required", "sourceId is required")
|
|
}
|
|
if strings.TrimSpace(req.PropsType) == "" {
|
|
return NewAppError(http.StatusBadRequest, "props_type_required", "propsType is required")
|
|
}
|
|
if strings.TrimSpace(req.CurrencyTypes) == "" {
|
|
return NewAppError(http.StatusBadRequest, "currency_types_required", "currencyTypes is required")
|
|
}
|
|
if strings.TrimSpace(req.ValidDays) == "" {
|
|
return NewAppError(http.StatusBadRequest, "valid_days_required", "validDays is required")
|
|
}
|
|
if req.ShelfStatus == nil {
|
|
return NewAppError(http.StatusBadRequest, "shelf_status_required", "shelfStatus is required")
|
|
}
|
|
if req.Discount != nil && (*req.Discount < 0 || *req.Discount > 1) {
|
|
return NewAppError(http.StatusBadRequest, "invalid_discount", "discount must be between 0 and 1")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *Service) evictStoreCache(ctx context.Context, sysOrigin string, propsType string) {
|
|
if s == nil || s.cache == nil {
|
|
return
|
|
}
|
|
propsType = strings.ToUpper(strings.TrimSpace(propsType))
|
|
if sysOrigin == "" || propsType == "" {
|
|
return
|
|
}
|
|
keys := []string{
|
|
propsStoreCacheKey(sysOrigin, propsType, "GOLD"),
|
|
propsStoreCacheKey(sysOrigin, propsType, "DIAMOND"),
|
|
propsStoreCacheKey(sysOrigin, propsType, "FREE"),
|
|
}
|
|
_ = s.cache.Del(ctx, keys...).Err()
|
|
}
|
|
|
|
func propsStoreCacheKey(sysOrigin string, propsType string, currencyType string) string {
|
|
return fmt.Sprintf("%s::%s_%s_%s", propsStoreCacheName, sysOrigin, propsType, currencyType)
|
|
}
|
|
|
|
func commodityViewFromModel(row model.PropsCommodityStore) CommodityView {
|
|
view := CommodityView{
|
|
ID: strconv.FormatInt(row.ID, 10),
|
|
SysOrigin: row.SysOrigin,
|
|
SourceID: strconv.FormatInt(row.SourceID, 10),
|
|
PropsType: row.PropsType,
|
|
CurrencyTypes: row.CurrencyTypes,
|
|
ValidDays: row.ValidDays,
|
|
Discount: row.Discount,
|
|
ShelfStatus: row.ShelfStatus,
|
|
Sort: row.Sort,
|
|
Label: row.Label,
|
|
Del: row.Del,
|
|
}
|
|
if row.CreateTime != nil && !row.CreateTime.IsZero() {
|
|
view.CreateTime = row.CreateTime.Format(timeLayout)
|
|
}
|
|
if row.UpdateTime != nil && !row.UpdateTime.IsZero() {
|
|
view.UpdateTime = row.UpdateTime.Format(timeLayout)
|
|
}
|
|
if row.CreateUser != nil {
|
|
view.CreateUser = strconv.FormatInt(*row.CreateUser, 10)
|
|
}
|
|
if row.UpdateUser != nil {
|
|
view.UpdateUser = strconv.FormatInt(*row.UpdateUser, 10)
|
|
}
|
|
return view
|
|
}
|
|
|
|
func normalizeSysOrigin(sysOrigin string) string {
|
|
return strings.ToUpper(strings.TrimSpace(sysOrigin))
|
|
}
|
|
|
|
func uniquePositiveSourceIDs(values []flexibleInt64) []int64 {
|
|
seen := make(map[int64]struct{}, len(values))
|
|
result := make([]int64, 0, len(values))
|
|
for _, value := range values {
|
|
id := value.Int64()
|
|
if id <= 0 {
|
|
continue
|
|
}
|
|
if _, exists := seen[id]; exists {
|
|
continue
|
|
}
|
|
seen[id] = struct{}{}
|
|
result = append(result, id)
|
|
}
|
|
return result
|
|
}
|