feat: add kyc test

This commit is contained in:
王性驊 2025-04-03 15:32:53 +08:00
commit 6f790d65b3
5 changed files with 400 additions and 9 deletions

View File

@ -9,15 +9,17 @@ func (p *ItemStatus) ToString() string {
}
const (
StatusActive ItemStatus = 1 + iota // 上架
StatusInactive // 下架
StatusOutOfStock // 缺貨
StatusActive ItemStatus = 1 + iota // 上架
StatusInactive // 下架
StatusOutOfStock // 缺貨
StatusUnderReview // 商品審核中
)
const (
StatusActiveStr = "active"
StatusInactiveStr = "inactive"
StatusOutOfStockStr = "out_of_stock"
StatusActiveStr = "active"
StatusInactiveStr = "inactive"
StatusOutOfStockStr = "out_of_stock"
StatusUnderReviewStr = "under_review"
)
var statusToStringMap = map[ItemStatus]string{
@ -27,9 +29,10 @@ var statusToStringMap = map[ItemStatus]string{
}
var stringToStatusMap = map[string]ItemStatus{
StatusActiveStr: StatusActive,
StatusInactiveStr: StatusInactive,
StatusOutOfStockStr: StatusOutOfStock,
StatusActiveStr: StatusActive,
StatusInactiveStr: StatusInactive,
StatusOutOfStockStr: StatusOutOfStock,
StatusUnderReviewStr: StatusUnderReview,
}
// StatusToString 將 ProductItemStatus 轉換為字串

View File

@ -1 +1,4 @@
package usecase
type KYCUseCase struct {
}

View File

@ -1 +1,70 @@
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 // 商品狀態
}

1
pkg/usecase/kyc.go Normal file
View File

@ -0,0 +1 @@
package usecase

315
pkg/usecase/product_item.go Normal file
View File

@ -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
}