feat: add product func without testing
This commit is contained in:
parent
84e608f4bb
commit
eafd691dab
|
@ -27,6 +27,8 @@ type Base interface {
|
||||||
// List 根據查詢條件取得 Tag 資料列表
|
// List 根據查詢條件取得 Tag 資料列表
|
||||||
// 回傳值分別為資料列表與符合條件的總筆數
|
// 回傳值分別為資料列表與符合條件的總筆數
|
||||||
List(ctx context.Context, params TagQueryParams) ([]*entity.Tags, int64, error)
|
List(ctx context.Context, params TagQueryParams) ([]*entity.Tags, int64, error)
|
||||||
|
// GetByIDs 根據查詢條件取得 Tag 資料列表
|
||||||
|
GetByIDs(ctx context.Context, ids []string) ([]*entity.Tags, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TagQueryParams 為查詢 Tags 時的參數結構
|
// TagQueryParams 為查詢 Tags 時的參數結構
|
||||||
|
@ -52,6 +54,8 @@ type TagBindingRepo interface {
|
||||||
BindTags(ctx context.Context, binding []*entity.TagsBindingTable) error
|
BindTags(ctx context.Context, binding []*entity.TagsBindingTable) error
|
||||||
// UnbindTag 刪除一筆綁定資料
|
// UnbindTag 刪除一筆綁定資料
|
||||||
UnbindTag(ctx context.Context, tagID, referenceID string) error
|
UnbindTag(ctx context.Context, tagID, referenceID string) error
|
||||||
|
// UnbindTagByReferenceID 刪除一筆綁定資料
|
||||||
|
UnbindTagByReferenceID(ctx context.Context, referenceID string) error
|
||||||
// GetBindingsByReference 根據參照 ID 取得所有綁定資料
|
// GetBindingsByReference 根據參照 ID 取得所有綁定資料
|
||||||
GetBindingsByReference(ctx context.Context, referenceID string) ([]*entity.TagsBindingTable, error)
|
GetBindingsByReference(ctx context.Context, referenceID string) ([]*entity.TagsBindingTable, error)
|
||||||
// ListTagBinding 根據查詢條件取得 tag binding 的資料列表
|
// ListTagBinding 根據查詢條件取得 tag binding 的資料列表
|
||||||
|
|
|
@ -172,6 +172,33 @@ func (repo *TagsRepository) List(ctx context.Context, params repository.TagQuery
|
||||||
return tags, count, nil
|
return tags, count, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (repo *TagsRepository) GetByIDs(ctx context.Context, ids []string) ([]*entity.Tags, error) {
|
||||||
|
// 轉換字串 ID 為 ObjectID
|
||||||
|
objectIDs := make([]primitive.ObjectID, 0, len(ids))
|
||||||
|
for _, id := range ids {
|
||||||
|
oid, err := primitive.ObjectIDFromHex(id)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
objectIDs = append(objectIDs, oid)
|
||||||
|
}
|
||||||
|
if len(objectIDs) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 構建查詢過濾器
|
||||||
|
filter := bson.M{"_id": bson.M{"$in": objectIDs}}
|
||||||
|
|
||||||
|
// 查詢符合條件的文件
|
||||||
|
var results []*entity.Tags
|
||||||
|
err := repo.Tags.GetClient().Find(ctx, &results, filter)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (repo *TagsRepository) IndexTags20250317001UP(ctx context.Context) (*mongo.Cursor, error) {
|
func (repo *TagsRepository) IndexTags20250317001UP(ctx context.Context) (*mongo.Cursor, error) {
|
||||||
// 等價於 db.account.createIndex({"create_at": 1})
|
// 等價於 db.account.createIndex({"create_at": 1})
|
||||||
repo.Tags.PopulateIndex(ctx, "show_type", 1, false)
|
repo.Tags.PopulateIndex(ctx, "show_type", 1, false)
|
||||||
|
|
|
@ -42,6 +42,16 @@ func (repo *TagsRepository) UnbindTag(ctx context.Context, tagID, referenceID st
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (repo *TagsRepository) UnbindTagByReferenceID(ctx context.Context, referenceID string) error {
|
||||||
|
filter := bson.M{"reference_id": referenceID}
|
||||||
|
_, err := repo.TageBinding.GetClient().DeleteMany(ctx, filter)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (repo *TagsRepository) GetBindingsByReference(ctx context.Context, referenceID string) ([]*entity.TagsBindingTable, error) {
|
func (repo *TagsRepository) GetBindingsByReference(ctx context.Context, referenceID string) ([]*entity.TagsBindingTable, error) {
|
||||||
var result []*entity.TagsBindingTable
|
var result []*entity.TagsBindingTable
|
||||||
filter := bson.M{"reference_id": referenceID}
|
filter := bson.M{"reference_id": referenceID}
|
||||||
|
|
|
@ -91,6 +91,15 @@ func (use *ProductUseCase) Create(ctx context.Context, product *usecase.Product)
|
||||||
insert.CustomFields = cf
|
insert.CustomFields = cf
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 綁定 Tags
|
||||||
|
tagsBinding := make([]*entity.TagsBindingTable, 0, len(product.Tags))
|
||||||
|
for _, tag := range product.Tags {
|
||||||
|
tagsBinding = append(tagsBinding, &entity.TagsBindingTable{
|
||||||
|
ReferenceID: insert.ID.Hex(),
|
||||||
|
TagID: tag,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Transaction 設定:只做必要寫入
|
// Transaction 設定:只做必要寫入
|
||||||
opts := options.Transaction().SetReadConcern(readconcern.Local())
|
opts := options.Transaction().SetReadConcern(readconcern.Local())
|
||||||
err := use.ProductRepo.Transaction(ctx, func(sessCtx mongo.SessionContext) (any, error) {
|
err := use.ProductRepo.Transaction(ctx, func(sessCtx mongo.SessionContext) (any, error) {
|
||||||
|
@ -128,24 +137,17 @@ func (use *ProductUseCase) Create(ctx context.Context, product *usecase.Product)
|
||||||
return nil, e
|
return nil, e
|
||||||
}
|
}
|
||||||
|
|
||||||
// 過濾 Tag
|
if err := use.TagBinding.BindTags(sessCtx, tagsBinding); err != nil {
|
||||||
// 綁定 Tags
|
e := errs.DBErrorL(logx.WithContext(ctx),
|
||||||
//for _, tag := range product.Tags {
|
[]logx.LogField{
|
||||||
// if err := use.TagBinding.BindTag(sessCtx, &entity.TagsBindingTable{
|
{Key: "ReferenceID", Value: insert.ID.Hex()},
|
||||||
// ReferenceID: insert.ID.Hex(),
|
{Key: "tags", Value: product.Tags},
|
||||||
// TagID: tag,
|
{Key: "func", Value: "TagBinding.BindTag"},
|
||||||
// }); err != nil {
|
{Key: "err", Value: err.Error()},
|
||||||
// _ = errs.DBErrorL(logx.WithContext(ctx),
|
}, "failed to binding product tags")
|
||||||
// []logx.LogField{
|
|
||||||
// {Key: "ReferenceID", Value: insert.ID.Hex()},
|
return nil, e
|
||||||
// {Key: "TagID", Value: tag},
|
}
|
||||||
// {Key: "func", Value: "TagBinding.BindTag"},
|
|
||||||
// {Key: "err", Value: err.Error()},
|
|
||||||
// }, "")
|
|
||||||
//
|
|
||||||
// continue
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}, opts)
|
}, opts)
|
||||||
|
@ -158,23 +160,320 @@ func (use *ProductUseCase) Create(ctx context.Context, product *usecase.Product)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (use *ProductUseCase) Update(ctx context.Context, id string, product *usecase.Product) error {
|
func (use *ProductUseCase) Update(ctx context.Context, id string, product *usecase.Product) error {
|
||||||
//TODO implement me
|
// 資料前處理:準備 entity.Product 實體
|
||||||
panic("implement me")
|
update := &repository.ProductUpdateParams{}
|
||||||
|
|
||||||
|
if product.Title != nil {
|
||||||
|
update.Title = product.Title
|
||||||
|
}
|
||||||
|
if product.IsPublished != nil {
|
||||||
|
update.IsPublished = product.IsPublished
|
||||||
|
}
|
||||||
|
if product.Category != nil {
|
||||||
|
update.Category = product.Category
|
||||||
|
}
|
||||||
|
if product.ShortTitle != nil {
|
||||||
|
update.ShortTitle = product.ShortTitle
|
||||||
|
}
|
||||||
|
if product.Details != nil {
|
||||||
|
update.Details = product.Details
|
||||||
|
}
|
||||||
|
if product.ShortDescription != nil {
|
||||||
|
update.ShortDescription = *product.ShortDescription
|
||||||
|
}
|
||||||
|
if product.Slug != nil {
|
||||||
|
update.Slug = product.Slug
|
||||||
|
}
|
||||||
|
if product.Amount != 0 {
|
||||||
|
update.Amount = &product.Amount
|
||||||
|
}
|
||||||
|
if product.StartTime != nil {
|
||||||
|
st := utils.Rfc3339ToUnix(utils.ToValue(product.StartTime))
|
||||||
|
update.StartTime = &st
|
||||||
|
}
|
||||||
|
if product.EndTime != nil {
|
||||||
|
et := utils.Rfc3339ToUnix(utils.ToValue(product.EndTime))
|
||||||
|
update.EndTime = &et
|
||||||
|
}
|
||||||
|
if len(product.Media) > 0 {
|
||||||
|
medias := make([]entity.Media, 0, len(product.Media))
|
||||||
|
for _, m := range product.Media {
|
||||||
|
medias = append(medias, entity.Media{
|
||||||
|
Sort: m.Sort,
|
||||||
|
URL: m.URL,
|
||||||
|
Type: m.Type,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
update.Media = medias
|
||||||
|
}
|
||||||
|
if len(product.CustomFields) > 0 {
|
||||||
|
cf := make([]entity.CustomFields, 0, len(product.CustomFields))
|
||||||
|
for _, field := range product.CustomFields {
|
||||||
|
cf = append(cf, entity.CustomFields{
|
||||||
|
Key: field.Key,
|
||||||
|
Value: field.Value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
update.CustomFields = cf
|
||||||
|
}
|
||||||
|
|
||||||
|
// 綁定 Tags
|
||||||
|
tagsBinding := make([]*entity.TagsBindingTable, 0, len(product.Tags))
|
||||||
|
for _, tag := range product.Tags {
|
||||||
|
tagsBinding = append(tagsBinding, &entity.TagsBindingTable{
|
||||||
|
ReferenceID: id,
|
||||||
|
TagID: tag,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transaction 設定:只做必要寫入
|
||||||
|
opts := options.Transaction().SetReadConcern(readconcern.Local())
|
||||||
|
err := use.ProductRepo.Transaction(ctx, func(sessCtx mongo.SessionContext) (any, error) {
|
||||||
|
_, err := use.ProductRepo.Update(sessCtx, id, update)
|
||||||
|
if err != nil {
|
||||||
|
e := errs.DBErrorL(logx.WithContext(ctx),
|
||||||
|
[]logx.LogField{
|
||||||
|
{Key: "req", Value: product},
|
||||||
|
{Key: "func", Value: "ProductRepo.Update"},
|
||||||
|
{Key: "err", Value: err.Error()},
|
||||||
|
},
|
||||||
|
"failed to update product")
|
||||||
|
|
||||||
|
return nil, e
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := use.TagBinding.UnbindTagByReferenceID(sessCtx, id); err != nil {
|
||||||
|
e := errs.DBErrorL(logx.WithContext(ctx),
|
||||||
|
[]logx.LogField{
|
||||||
|
{Key: "ReferenceID", Value: id},
|
||||||
|
{Key: "func", Value: "TagBinding.UnbindTagByReferenceID"},
|
||||||
|
{Key: "err", Value: err.Error()},
|
||||||
|
}, "failed to unbind tags")
|
||||||
|
|
||||||
|
return nil, e
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := use.TagBinding.BindTags(sessCtx, tagsBinding); err != nil {
|
||||||
|
e := errs.DBErrorL(logx.WithContext(ctx),
|
||||||
|
[]logx.LogField{
|
||||||
|
{Key: "ReferenceID", Value: id},
|
||||||
|
{Key: "tags", Value: product.Tags},
|
||||||
|
{Key: "func", Value: "TagBinding.BindTags"},
|
||||||
|
{Key: "err", Value: err.Error()},
|
||||||
|
}, "failed to binding product tags")
|
||||||
|
|
||||||
|
return nil, e
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}, opts)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (use *ProductUseCase) Delete(ctx context.Context, id string) error {
|
func (use *ProductUseCase) Delete(ctx context.Context, id string) error {
|
||||||
//TODO implement me
|
// Transaction 設定:只做必要寫入
|
||||||
panic("implement me")
|
opts := options.Transaction().SetReadConcern(readconcern.Local())
|
||||||
|
err := use.ProductRepo.Transaction(ctx, func(sessCtx mongo.SessionContext) (any, error) {
|
||||||
|
err := use.ProductRepo.Delete(sessCtx, id)
|
||||||
|
if err != nil {
|
||||||
|
e := errs.DBErrorL(logx.WithContext(ctx),
|
||||||
|
[]logx.LogField{
|
||||||
|
{Key: "ReferenceID", Value: id},
|
||||||
|
{Key: "func", Value: "ProductRepo.Delete"},
|
||||||
|
{Key: "err", Value: err.Error()},
|
||||||
|
}, "failed to delete product")
|
||||||
|
|
||||||
|
return nil, e
|
||||||
|
}
|
||||||
|
|
||||||
|
err = use.TagRepo.UnbindTagByReferenceID(sessCtx, id)
|
||||||
|
if err != nil {
|
||||||
|
e := errs.DBErrorL(logx.WithContext(ctx),
|
||||||
|
[]logx.LogField{
|
||||||
|
{Key: "ReferenceID", Value: id},
|
||||||
|
{Key: "func", Value: "TagBinding.UnbindTagByReferenceID"},
|
||||||
|
{Key: "err", Value: err.Error()},
|
||||||
|
}, "failed to unbind tags")
|
||||||
|
|
||||||
|
return nil, e
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}, opts)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (use *ProductUseCase) Get(ctx context.Context, id string) (*usecase.ProductResp, error) {
|
func (use *ProductUseCase) Get(ctx context.Context, id string) (*usecase.ProductResp, error) {
|
||||||
//TODO implement me
|
product, err := use.ProductRepo.FindOneByID(ctx, id)
|
||||||
panic("implement me")
|
if err != nil {
|
||||||
|
e := errs.DBErrorL(logx.WithContext(ctx),
|
||||||
|
[]logx.LogField{
|
||||||
|
{Key: "product_id", Value: id},
|
||||||
|
{Key: "func", Value: "ProductRepo.FindOneByID"},
|
||||||
|
{Key: "err", Value: err.Error()},
|
||||||
|
}, "failed to find product")
|
||||||
|
|
||||||
|
return nil, e
|
||||||
|
}
|
||||||
|
productStatistics, err := use.ProductStatisticsRepo.GetByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
e := errs.DBErrorL(logx.WithContext(ctx),
|
||||||
|
[]logx.LogField{
|
||||||
|
{Key: "product_id", Value: id},
|
||||||
|
{Key: "func", Value: "ProductStatisticsRepo.GetByID"},
|
||||||
|
{Key: "err", Value: err.Error()},
|
||||||
|
}, "failed to get product statistics")
|
||||||
|
|
||||||
|
return nil, e
|
||||||
|
}
|
||||||
|
|
||||||
|
tags, err := use.TagRepo.GetBindingsByReference(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
e := errs.DBErrorL(logx.WithContext(ctx),
|
||||||
|
[]logx.LogField{
|
||||||
|
{Key: "product_id", Value: id},
|
||||||
|
{Key: "func", Value: "TagRepo.GetBindingsByReference"},
|
||||||
|
{Key: "err", Value: err.Error()},
|
||||||
|
}, "failed to get tags")
|
||||||
|
|
||||||
|
return nil, e
|
||||||
|
}
|
||||||
|
|
||||||
|
t := make([]string, 0, len(tags))
|
||||||
|
for _, item := range tags {
|
||||||
|
t = append(t, item.TagID)
|
||||||
|
}
|
||||||
|
|
||||||
|
tagsInfo, err := use.TagRepo.GetByIDs(ctx, t)
|
||||||
|
if err != nil {
|
||||||
|
e := errs.DBErrorL(logx.WithContext(ctx),
|
||||||
|
[]logx.LogField{
|
||||||
|
{Key: "product_id", Value: id},
|
||||||
|
{Key: "func", Value: "TagRepo.GetByIDs"},
|
||||||
|
{Key: "err", Value: err.Error()},
|
||||||
|
}, "failed to get tags")
|
||||||
|
|
||||||
|
return nil, e
|
||||||
|
}
|
||||||
|
// 組合資料
|
||||||
|
result := &usecase.ProductResp{
|
||||||
|
ID: product.ID.Hex(),
|
||||||
|
UID: product.UID,
|
||||||
|
Title: product.Title,
|
||||||
|
ShortDescription: product.ShortDescription,
|
||||||
|
IsPublished: product.IsPublished,
|
||||||
|
Amount: product.Amount,
|
||||||
|
Category: product.Category,
|
||||||
|
Orders: productStatistics.Orders,
|
||||||
|
AverageRating: productStatistics.AverageRating,
|
||||||
|
FansCount: productStatistics.FansCount,
|
||||||
|
UpdatedAt: utils.UnixToRfc3339(product.UpdatedAt),
|
||||||
|
CreatedAt: utils.UnixToRfc3339(product.CreatedAt),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tag := range tagsInfo {
|
||||||
|
item := usecase.Tags{
|
||||||
|
ID: tag.ID.Hex(),
|
||||||
|
Types: tag.Types,
|
||||||
|
Name: tag.Name,
|
||||||
|
ShowType: tag.ShowType,
|
||||||
|
UpdatedAt: utils.UnixToRfc3339(tag.UpdatedAt),
|
||||||
|
CreatedAt: utils.UnixToRfc3339(tag.CreatedAt),
|
||||||
|
}
|
||||||
|
if tag.Cover != nil {
|
||||||
|
item.Cover = *tag.Cover
|
||||||
|
}
|
||||||
|
result.Tags = append(result.Tags, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(product.Media) > 0 {
|
||||||
|
medias := make([]usecase.Media, 0, len(product.Media))
|
||||||
|
for _, m := range product.Media {
|
||||||
|
medias = append(medias, usecase.Media{
|
||||||
|
Sort: m.Sort,
|
||||||
|
URL: m.URL,
|
||||||
|
Type: m.Type,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
result.Media = medias
|
||||||
|
}
|
||||||
|
if len(product.CustomFields) > 0 {
|
||||||
|
cf := make([]usecase.CustomFields, 0, len(product.CustomFields))
|
||||||
|
for _, field := range product.CustomFields {
|
||||||
|
cf = append(cf, usecase.CustomFields{
|
||||||
|
Key: field.Key,
|
||||||
|
Value: field.Value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
result.CustomFields = cf
|
||||||
|
}
|
||||||
|
|
||||||
|
if product.Slug != nil {
|
||||||
|
result.Slug = *product.Slug
|
||||||
|
}
|
||||||
|
if product.ShortTitle != nil {
|
||||||
|
result.ShortTitle = *product.ShortTitle
|
||||||
|
}
|
||||||
|
if product.Details != nil {
|
||||||
|
result.Details = *product.Details
|
||||||
|
}
|
||||||
|
if product.StartTime != nil {
|
||||||
|
result.StartTime = utils.UnixToRfc3339(*product.StartTime)
|
||||||
|
}
|
||||||
|
if product.EndTime != nil {
|
||||||
|
result.EndTime = utils.UnixToRfc3339(*product.EndTime)
|
||||||
|
}
|
||||||
|
if productStatistics.OrdersUpdateTime > 0 {
|
||||||
|
result.OrdersUpdateTime = utils.UnixToRfc3339(productStatistics.OrdersUpdateTime)
|
||||||
|
}
|
||||||
|
if productStatistics.FansCountUpdateTime > 0 {
|
||||||
|
result.FansCountUpdateTime = utils.UnixToRfc3339(productStatistics.FansCountUpdateTime)
|
||||||
|
}
|
||||||
|
if productStatistics.AverageRatingUpdateTime > 0 {
|
||||||
|
result.AverageRatingUpdateTime = utils.UnixToRfc3339(productStatistics.AverageRatingUpdateTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (use *ProductUseCase) List(ctx context.Context, data usecase.ProductQueryParams) ([]*usecase.ProductResp, int64, error) {
|
func (use *ProductUseCase) List(ctx context.Context, data usecase.ProductQueryParams) ([]*usecase.ProductResp, int64, error) {
|
||||||
//TODO implement me
|
query := &repository.ProductQueryParams{
|
||||||
panic("implement me")
|
PageSize: data.PageSize,
|
||||||
|
PageIndex: data.PageIndex,
|
||||||
|
}
|
||||||
|
|
||||||
|
if data.Slug != nil {
|
||||||
|
query.Slug = data.Slug
|
||||||
|
}
|
||||||
|
if data.UID != nil {
|
||||||
|
query.UID = data.UID
|
||||||
|
}
|
||||||
|
if data.IsPublished != nil {
|
||||||
|
query.IsPublished = data.IsPublished
|
||||||
|
}
|
||||||
|
if data.Category != nil {
|
||||||
|
query.Category = data.Category
|
||||||
|
}
|
||||||
|
if data.StartTime != nil {
|
||||||
|
query.StartTime = data.StartTime
|
||||||
|
}
|
||||||
|
|
||||||
|
EndTime * int64 // 結束時間(Unix 時間戳)
|
||||||
|
Slug * string // URL 後綴
|
||||||
|
|
||||||
|
product, i, err := use.ProductRepo.ListProduct(ctx, &repository.ProductQueryParams{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (use *ProductUseCase) IncOrders(ctx context.Context, productID string, count int64) error {
|
func (use *ProductUseCase) IncOrders(ctx context.Context, productID string, count int64) error {
|
||||||
|
|
Loading…
Reference in New Issue