2025-04-01 09:44:57 +00:00
|
|
|
|
package usecase
|
|
|
|
|
|
|
|
|
|
import (
|
2025-04-06 02:08:46 +00:00
|
|
|
|
"context"
|
|
|
|
|
"math"
|
|
|
|
|
|
2025-04-01 09:44:57 +00:00
|
|
|
|
"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
|
|
|
|
|
}
|
2025-04-06 02:08:46 +00:00
|
|
|
|
|
2025-04-01 09:44:57 +00:00
|
|
|
|
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,
|
2025-04-09 14:46:53 +00:00
|
|
|
|
UpdatedAt: e.UpdatedAt,
|
|
|
|
|
CreatedAt: e.CreatedAt,
|
2025-04-01 09:44:57 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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,
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-04-06 02:08:46 +00:00
|
|
|
|
|
2025-04-01 09:44:57 +00:00
|
|
|
|
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,
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-04-06 02:08:46 +00:00
|
|
|
|
|
2025-04-01 09:44:57 +00:00
|
|
|
|
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,
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-04-06 02:08:46 +00:00
|
|
|
|
|
2025-04-01 09:44:57 +00:00
|
|
|
|
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,
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-04-06 02:08:46 +00:00
|
|
|
|
|
2025-04-01 09:44:57 +00:00
|
|
|
|
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")
|
|
|
|
|
}
|
2025-04-06 02:08:46 +00:00
|
|
|
|
|
2025-04-01 09:44:57 +00:00
|
|
|
|
return fromEntity(ent), nil
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-09 09:29:56 +00:00
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-01 09:44:57 +00:00
|
|
|
|
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 {
|
2025-04-06 02:08:46 +00:00
|
|
|
|
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")
|
|
|
|
|
}
|
2025-04-01 09:44:57 +00:00
|
|
|
|
// 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 {
|
2025-04-06 02:08:46 +00:00
|
|
|
|
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")
|
|
|
|
|
}
|
2025-04-01 09:44:57 +00:00
|
|
|
|
// 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 {
|
2025-04-06 02:08:46 +00:00
|
|
|
|
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")
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-01 09:44:57 +00:00
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-09 09:29:56 +00:00
|
|
|
|
func (use *ProductItemUseCase) Delete(ctx context.Context, ids []string) error {
|
2025-04-01 09:44:57 +00:00
|
|
|
|
// repository.Delete 接收 slice,因此將 id 放入 slice 中
|
2025-04-09 09:29:56 +00:00
|
|
|
|
if err := use.ProductItemUseCaseParam.ProductItems.Delete(ctx, ids); err != nil {
|
2025-04-01 09:44:57 +00:00
|
|
|
|
return errs.DBErrorL(logx.WithContext(ctx),
|
|
|
|
|
[]logx.LogField{
|
2025-04-09 09:29:56 +00:00
|
|
|
|
{Key: "id", Value: ids},
|
2025-04-01 09:44:57 +00:00
|
|
|
|
{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,
|
2025-04-10 07:47:07 +00:00
|
|
|
|
IsUnlimited: filter.IsUnLimit,
|
2025-04-01 09:44:57 +00:00
|
|
|
|
// 注意:若需要依 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")
|
|
|
|
|
}
|
2025-04-06 02:08:46 +00:00
|
|
|
|
result := make([]*usecase.ProductItems, 0, len(entities))
|
2025-04-01 09:44:57 +00:00
|
|
|
|
for i := range entities {
|
|
|
|
|
// 逐筆轉換 entity -> usecase 型別
|
|
|
|
|
result = append(result, fromEntity(&entities[i]))
|
|
|
|
|
}
|
2025-04-06 02:08:46 +00:00
|
|
|
|
|
2025-04-01 09:44:57 +00:00
|
|
|
|
return result, total, nil
|
|
|
|
|
}
|