feat: add kyc test
This commit is contained in:
commit
6f790d65b3
|
@ -12,12 +12,14 @@ const (
|
||||||
StatusActive ItemStatus = 1 + iota // 上架
|
StatusActive ItemStatus = 1 + iota // 上架
|
||||||
StatusInactive // 下架
|
StatusInactive // 下架
|
||||||
StatusOutOfStock // 缺貨
|
StatusOutOfStock // 缺貨
|
||||||
|
StatusUnderReview // 商品審核中
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
StatusActiveStr = "active"
|
StatusActiveStr = "active"
|
||||||
StatusInactiveStr = "inactive"
|
StatusInactiveStr = "inactive"
|
||||||
StatusOutOfStockStr = "out_of_stock"
|
StatusOutOfStockStr = "out_of_stock"
|
||||||
|
StatusUnderReviewStr = "under_review"
|
||||||
)
|
)
|
||||||
|
|
||||||
var statusToStringMap = map[ItemStatus]string{
|
var statusToStringMap = map[ItemStatus]string{
|
||||||
|
@ -30,6 +32,7 @@ var stringToStatusMap = map[string]ItemStatus{
|
||||||
StatusActiveStr: StatusActive,
|
StatusActiveStr: StatusActive,
|
||||||
StatusInactiveStr: StatusInactive,
|
StatusInactiveStr: StatusInactive,
|
||||||
StatusOutOfStockStr: StatusOutOfStock,
|
StatusOutOfStockStr: StatusOutOfStock,
|
||||||
|
StatusUnderReviewStr: StatusUnderReview,
|
||||||
}
|
}
|
||||||
|
|
||||||
// StatusToString 將 ProductItemStatus 轉換為字串
|
// StatusToString 將 ProductItemStatus 轉換為字串
|
||||||
|
|
|
@ -1 +1,4 @@
|
||||||
package usecase
|
package usecase
|
||||||
|
|
||||||
|
type KYCUseCase struct {
|
||||||
|
}
|
||||||
|
|
|
@ -1 +1,70 @@
|
||||||
package usecase
|
package usecase
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/product"
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ProductItemUseCase interface {
|
||||||
|
// Create 新增單筆商品
|
||||||
|
Create(ctx context.Context, productItem *ProductItems) error
|
||||||
|
// Get 根據 ID 查詢單筆商品
|
||||||
|
Get(ctx context.Context, id string) (*ProductItems, error)
|
||||||
|
// Update 更新商品資訊
|
||||||
|
Update(ctx context.Context, id string, productItem *UpdateProductItems) error
|
||||||
|
// UpdateStatus 更新商品資訊
|
||||||
|
UpdateStatus(ctx context.Context, id string, status product.ItemStatus) error
|
||||||
|
// IncSalesCount 更新商品資訊
|
||||||
|
IncSalesCount(ctx context.Context, id string, saleCount uint64) error
|
||||||
|
// DecSalesCount 更新商品資訊
|
||||||
|
DecSalesCount(ctx context.Context, id string, saleCount uint64) error
|
||||||
|
// Delete ID 刪除商品
|
||||||
|
Delete(ctx context.Context, id string) error
|
||||||
|
// List 列出符合條件的商品,可根據需求加入分頁或其他條件參數
|
||||||
|
List(ctx context.Context, filter QueryProductItemParam) ([]*ProductItems, int64, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProductItems struct {
|
||||||
|
ID string `json:"_id,omitempty"` // 專案 ID
|
||||||
|
ReferenceID string `json:"reference_id"` // 對應的專案 ID
|
||||||
|
Name string `json:"name"` // 名稱
|
||||||
|
Description string `json:"description"` // 描述
|
||||||
|
ShortDescription string `json:"short_description"` // 封面簡短描述
|
||||||
|
IsUnLimit bool `json:"is_un_limit"` // 是否沒有數量上限
|
||||||
|
IsFree bool `json:"is_free"` // 是否為免費品項(贈品) -> 開啟就是自訂金額
|
||||||
|
Stock uint64 `json:"stock"` // 庫存總數
|
||||||
|
Price string `json:"price"` // 價格
|
||||||
|
SKU string `json:"sku"` // 型號:對應顯示 Item 的 FK
|
||||||
|
TimeSeries product.TimeSeries `json:"time_series"` // 時段種類
|
||||||
|
Media []Media `json:"media,omitempty"` // 專案動態內容(圖片或者影片)
|
||||||
|
Status product.ItemStatus `json:"status"` // 商品狀態
|
||||||
|
Freight []CustomFields `json:"freight,omitempty"` // 運費
|
||||||
|
CustomFields []CustomFields `json:"custom_fields,omitempty"` // 自定義屬性
|
||||||
|
SalesCount uint64 `json:"sales_count" ` // 已賣出數量(相反,減到零就不能在賣)
|
||||||
|
UpdatedAt string `json:"updated_at"` // 更新時間
|
||||||
|
CreatedAt string `json:"created_at"` // 創建時間
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateProductItems struct {
|
||||||
|
Name *string // 名稱
|
||||||
|
Description *string // 描述
|
||||||
|
ShortDescription *string // 封面簡短描述
|
||||||
|
IsUnLimit *bool // 是否沒有數量上限
|
||||||
|
IsFree *bool // 是否為免費品項(贈品) -> 開啟就是自訂金額
|
||||||
|
Stock *uint64 // 庫存總數
|
||||||
|
Price *string // 價格
|
||||||
|
SKU *string // 型號:對應顯示 Item 的 FK
|
||||||
|
TimeSeries *product.TimeSeries // 時段種類
|
||||||
|
Media []Media // 專案動態內容(圖片或者影片)
|
||||||
|
Freight []CustomFields // 運費
|
||||||
|
CustomFields []CustomFields // 自定義屬性
|
||||||
|
}
|
||||||
|
|
||||||
|
type QueryProductItemParam struct {
|
||||||
|
PageSize int64
|
||||||
|
PageIndex int64
|
||||||
|
ReferenceID *string // 對應的專案 ID
|
||||||
|
IsUnLimit *bool // 是否沒有數量上限
|
||||||
|
IsFree *bool // 是否為免費品項(贈品) -> 開啟就是自訂金額
|
||||||
|
Status *product.ItemStatus // 商品狀態
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
package usecase
|
|
@ -0,0 +1,315 @@
|
||||||
|
package usecase
|
||||||
|
|
||||||
|
import (
|
||||||
|
"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/app-cloudep-product-service/pkg/utils"
|
||||||
|
"code.30cm.net/digimon/library-go/errs"
|
||||||
|
"context"
|
||||||
|
"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: utils.UnixToRfc3339(e.UpdatedAt),
|
||||||
|
CreatedAt: utils.UnixToRfc3339(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) 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 {
|
||||||
|
// 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 {
|
||||||
|
// 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 {
|
||||||
|
// repository 方法使用 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, id string) error {
|
||||||
|
// repository.Delete 接收 slice,因此將 id 放入 slice 中
|
||||||
|
if err := use.ProductItemUseCaseParam.ProductItems.Delete(ctx, []string{id}); err != nil {
|
||||||
|
return errs.DBErrorL(logx.WithContext(ctx),
|
||||||
|
[]logx.LogField{
|
||||||
|
{Key: "id", Value: id},
|
||||||
|
{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,
|
||||||
|
// 注意:若需要依 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")
|
||||||
|
}
|
||||||
|
var result []*usecase.ProductItems
|
||||||
|
for i := range entities {
|
||||||
|
// 逐筆轉換 entity -> usecase 型別
|
||||||
|
result = append(result, fromEntity(&entities[i]))
|
||||||
|
}
|
||||||
|
return result, total, nil
|
||||||
|
}
|
Loading…
Reference in New Issue