package usecase import ( "context" "math" "code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/entity" "code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/product" "code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/repository" "code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/usecase" "code.30cm.net/digimon/library-go/errs" "github.com/shopspring/decimal" "github.com/zeromicro/go-zero/core/logx" ) type ProductItemUseCaseParam struct { ProductItems repository.ProductItemRepository TagRepo repository.TagRepo } type ProductItemUseCase struct { ProductItemUseCaseParam } func MustProductItemUseCase(param ProductItemUseCaseParam) usecase.ProductItemUseCase { return &ProductItemUseCase{ param, } } // --- 輔助函式 --- // 將 usecase.ProductItems 轉換為 entity.ProductItems(用於 Create) func toEntity(u *usecase.ProductItems) (entity.ProductItems, error) { // 將價格字串轉換為 decimal.Decimal priceDec, err := decimal.NewFromString(u.Price) if err != nil { return entity.ProductItems{}, err } return entity.ProductItems{ // ID 在新增時通常由資料庫自動產生 ReferenceID: u.ReferenceID, Name: u.Name, Description: u.Description, ShortDescription: u.ShortDescription, IsUnLimit: u.IsUnLimit, IsFree: u.IsFree, Stock: u.Stock, Price: priceDec, SKU: u.SKU, TimeSeries: u.TimeSeries, Media: convertMediaToEntity(u.Media), Status: u.Status, Freight: convertCustomFieldsToEntity(u.Freight), CustomFields: convertCustomFieldsToEntity(u.CustomFields), }, nil } // 將 entity.ProductItems 轉換為 usecase.ProductItems(用於 Get 與 List) func fromEntity(e *entity.ProductItems) *usecase.ProductItems { return &usecase.ProductItems{ ID: e.ID.Hex(), ReferenceID: e.ReferenceID, Name: e.Name, Description: e.Description, ShortDescription: e.ShortDescription, IsUnLimit: e.IsUnLimit, IsFree: e.IsFree, Stock: e.Stock, Price: e.Price.String(), SKU: e.SKU, TimeSeries: e.TimeSeries, Media: convertEntityMedia(e.Media), Status: e.Status, Freight: convertEntityCustomFields(e.Freight), CustomFields: convertEntityCustomFields(e.CustomFields), SalesCount: e.SalesCount, UpdatedAt: e.UpdatedAt, CreatedAt: e.CreatedAt, } } // Media 轉換:usecase.Media -> entity.Media func convertMediaToEntity(meds []usecase.Media) []entity.Media { res := make([]entity.Media, len(meds)) for i, m := range meds { res[i] = entity.Media{ Sort: m.Sort, Type: m.Type, URL: m.URL, } } return res } // Media 轉換:entity.Media -> usecase.Media func convertEntityMedia(meds []entity.Media) []usecase.Media { res := make([]usecase.Media, len(meds)) for i, m := range meds { res[i] = usecase.Media{ Sort: m.Sort, Type: m.Type, URL: m.URL, } } return res } // CustomFields 轉換:usecase.CustomFields -> entity.CustomFields func convertCustomFieldsToEntity(cfs []usecase.CustomFields) []entity.CustomFields { res := make([]entity.CustomFields, len(cfs)) for i, cf := range cfs { res[i] = entity.CustomFields{ Key: cf.Key, Value: cf.Value, } } return res } // CustomFields 轉換:entity.CustomFields -> usecase.CustomFields func convertEntityCustomFields(cfs []entity.CustomFields) []usecase.CustomFields { res := make([]usecase.CustomFields, len(cfs)) for i, cf := range cfs { res[i] = usecase.CustomFields{ Key: cf.Key, Value: cf.Value, } } return res } // --- 實作 ProductItemUseCase interface --- func (use *ProductItemUseCase) Create(ctx context.Context, productItem *usecase.ProductItems) error { // 轉換成 entity 層型別 ent, err := toEntity(productItem) if err != nil { return errs.InvalidFormatL(logx.WithContext(ctx), []logx.LogField{ {Key: "product_id", Value: productItem}, {Key: "func", Value: "ProductItemUseCase.Create"}, {Key: "err", Value: err.Error()}, }, "failed to change use case into entity") } err = use.ProductItemUseCaseParam.ProductItems.Insert(ctx, []entity.ProductItems{ent}) if err != nil { return errs.DBErrorL(logx.WithContext(ctx), []logx.LogField{ {Key: "req", Value: productItem}, {Key: "func", Value: "ProductItemUseCaseParam.ProductItems.Insert"}, {Key: "err", Value: err.Error()}, }, "failed to insert product item") } return nil } func (use *ProductItemUseCase) Get(ctx context.Context, id string) (*usecase.ProductItems, error) { ent, err := use.ProductItemUseCaseParam.ProductItems.FindByID(ctx, id) if err != nil { return nil, errs.DBErrorL(logx.WithContext(ctx), []logx.LogField{ {Key: "id", Value: id}, {Key: "func", Value: "ProductItemUseCaseParam.ProductItems.FindByID"}, {Key: "err", Value: err.Error()}, }, "failed to create product items") } return fromEntity(ent), nil } func (use *ProductItemUseCase) DeleteByReferenceID(ctx context.Context, id string) error { err := use.ProductItems.DeleteByReferenceID(ctx, id) if err != nil { return errs.DBErrorL(logx.WithContext(ctx), []logx.LogField{ {Key: "id", Value: id}, {Key: "func", Value: "ProductItemUseCaseParam.ProductItems.DeleteByReferenceID"}, {Key: "err", Value: err.Error()}, }, "failed to delete product items") } return nil } func (use *ProductItemUseCase) Update(ctx context.Context, id string, data *usecase.UpdateProductItems) error { // 構建更新參數(僅更新部分欄位) update := &repository.ProductUpdateItem{} if data.Name != nil { update.Name = data.Name } if data.Description != nil { update.Description = data.Description } if data.ShortDescription != nil { update.ShortDescription = data.ShortDescription } if data.IsUnLimit != nil { update.IsUnLimit = data.IsUnLimit } if data.IsFree != nil { update.IsFree = data.IsFree } if data.Stock != nil { if *data.Stock > math.MaxInt64 { return errs.InvalidFormatL(logx.WithContext(ctx), []logx.LogField{ {Key: "id", Value: id}, {Key: "data.Stock ", Value: *data.Stock}, }, "data.Stock too large to convert to int64") } // data.Stock 為 *uint64,需先解引用再轉換為 int64 s := int64(*data.Stock) update.Stock = &s } if data.Price != nil { // data.Price 為 *string,需先解引用並轉換為 decimal.Decimal dec, err := decimal.NewFromString(*data.Price) if err != nil { return errs.InvalidFormat("failed to convert price to decimal") } update.Price = &dec } if data.SKU != nil { update.SKU = data.SKU } // 假設有 TimeSeries 欄位也需要更新(若有定義的話) if data.TimeSeries != nil { update.TimeSeries = data.TimeSeries } if len(data.Media) > 0 { update.Media = convertMediaToEntity(data.Media) } if len(data.CustomFields) > 0 { update.CustomFields = convertCustomFieldsToEntity(data.CustomFields) } if len(data.Freight) > 0 { update.Freight = convertCustomFieldsToEntity(data.Freight) } if err := use.ProductItemUseCaseParam.ProductItems.Update(ctx, id, update); err != nil { return errs.DBErrorL(logx.WithContext(ctx), []logx.LogField{ {Key: "id", Value: id}, {Key: "req", Value: data}, {Key: "func", Value: "ProductItemUseCaseParam.ProductItems.Update"}, {Key: "err", Value: err.Error()}, }, "failed to update product item") } return nil } func (use *ProductItemUseCase) UpdateStatus(ctx context.Context, id string, status product.ItemStatus) error { err := use.ProductItemUseCaseParam.ProductItems.UpdateStatus(ctx, id, status) if err != nil { return errs.DBErrorL(logx.WithContext(ctx), []logx.LogField{ {Key: "id", Value: id}, {Key: "status", Value: status}, {Key: "func", Value: "ProductItemUseCaseParam.ProductItems.UpdateStatus"}, {Key: "err", Value: err.Error()}, }, "failed to update product item status") } return nil } func (use *ProductItemUseCase) IncSalesCount(ctx context.Context, id string, saleCount uint64) error { if saleCount > math.MaxInt64 { return errs.InvalidFormatL(logx.WithContext(ctx), []logx.LogField{ {Key: "id", Value: id}, {Key: "saleCount", Value: saleCount}, }, "saleCount too large to convert to int64") } // repository 方法使用 int64,故需轉型 if err := use.ProductItemUseCaseParam.ProductItems.IncSalesCount(ctx, id, int64(saleCount)); err != nil { return errs.DBErrorL(logx.WithContext(ctx), []logx.LogField{ {Key: "id", Value: id}, {Key: "saleCount", Value: saleCount}, {Key: "func", Value: "ProductItemUseCaseParam.ProductItems.IncSalesCount"}, {Key: "err", Value: err.Error()}, }, "failed to insert product item") } return nil } func (use *ProductItemUseCase) DecSalesCount(ctx context.Context, id string, saleCount uint64) error { if saleCount > math.MaxInt64 { return errs.InvalidFormatL(logx.WithContext(ctx), []logx.LogField{ {Key: "id", Value: id}, {Key: "saleCount", Value: saleCount}, }, "saleCount too large to convert to int64") } if err := use.ProductItemUseCaseParam.ProductItems.DecSalesCount(ctx, id, int64(saleCount)); err != nil { return errs.DBErrorL(logx.WithContext(ctx), []logx.LogField{ {Key: "id", Value: id}, {Key: "saleCount", Value: saleCount}, {Key: "func", Value: "ProductItemUseCaseParam.ProductItems.DecSalesCount"}, {Key: "err", Value: err.Error()}, }, "failed to Dec product item") } return nil } func (use *ProductItemUseCase) Delete(ctx context.Context, ids []string) error { // repository.Delete 接收 slice,因此將 id 放入 slice 中 if err := use.ProductItemUseCaseParam.ProductItems.Delete(ctx, ids); err != nil { return errs.DBErrorL(logx.WithContext(ctx), []logx.LogField{ {Key: "id", Value: ids}, {Key: "func", Value: "ProductItemUseCaseParam.ProductItems.Delete"}, {Key: "err", Value: err.Error()}, }, "failed to delete product item") } return nil } func (use *ProductItemUseCase) List(ctx context.Context, filter usecase.QueryProductItemParam) ([]*usecase.ProductItems, int64, error) { repoQuery := repository.ProductItemQueryParams{ PageSize: filter.PageSize, PageIndex: filter.PageIndex, ReferenceID: filter.ReferenceID, IsFree: filter.IsFree, Status: filter.Status, IsUnlimited: filter.IsUnLimit, // 注意:若需要依 IsUnLimit 過濾,需擴充 repository.ProductItemQueryParams 結構 } entities, total, err := use.ProductItemUseCaseParam.ProductItems.ListProductItem(ctx, repoQuery) if err != nil { return nil, 0, errs.DBErrorL(logx.WithContext(ctx), []logx.LogField{ {Key: "filter", Value: filter}, {Key: "func", Value: "ProductItemUseCaseParam.ProductItems.ListProductItem"}, {Key: "err", Value: err.Error()}, }, "failed to list product item") } result := make([]*usecase.ProductItems, 0, len(entities)) for i := range entities { // 逐筆轉換 entity -> usecase 型別 result = append(result, fromEntity(&entities[i])) } return result, total, nil }