v2.0: massive test data (104 products, 41 postings, 20 returns, 20 payouts, 25 txs, 3 companies)
- Remove unused gin dependency from go.mod - Remove credential logging from startup - Remove dead parseOfferIDs function - Add method check to GET /v1/product/list - Complete all filter implementations (since, to, status, product_id, return_id, dir, analytics, page) - CancelPosting now validates posting existence (404 for unknown) - Implement MOCK_STOCKS_JSON file loading - Add Phase 2 endpoints: v3/product/import, v1/product/info/description, v1/product/info/attributes, v2/category/tree, v3/category/attribute, v3/category/attribute/values - Generate 104 products/stocks/prices across 3 companies: TechGiant (TG-0001..TG-0035), FashionLine (FL-0001..FL-0035), HomeStyle (HS-0001..HS-0034) - 41 postings, 20 returns, 20 payouts, 25 transactions - 31 tests (all PASS with race detector) - Add Makefile
This commit is contained in:
25
Makefile
Normal file
25
Makefile
Normal file
@ -0,0 +1,25 @@
|
||||
.PHONY: build test docker run clean vet lint
|
||||
|
||||
build:
|
||||
CGO_ENABLED=0 go build -o ozon-mock ./cmd/server
|
||||
|
||||
test:
|
||||
go test -v -race ./...
|
||||
|
||||
vet:
|
||||
go vet ./...
|
||||
|
||||
lint:
|
||||
go vet ./...
|
||||
|
||||
run:
|
||||
go run ./cmd/server
|
||||
|
||||
docker:
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o ozon-mock ./cmd/server
|
||||
docker build -t ozon-mock .
|
||||
|
||||
clean:
|
||||
rm -f ozon-mock
|
||||
|
||||
all: vet test build
|
||||
@ -14,28 +14,42 @@ func main() {
|
||||
|
||||
mux := http.NewServeMux()
|
||||
|
||||
auth := func(h http.HandlerFunc) http.HandlerFunc {
|
||||
return middleware.Auth(cfg.ClientID, cfg.APIKey, h)
|
||||
}
|
||||
|
||||
// Products
|
||||
mux.HandleFunc("/v1/product/import/prices", middleware.Auth(cfg.ClientID, cfg.APIKey, handlers.ImportPrices))
|
||||
mux.HandleFunc("/v1/product/list", middleware.Auth(cfg.ClientID, cfg.APIKey, handlers.ProductList))
|
||||
mux.HandleFunc("/v3/product/info/list", middleware.Auth(cfg.ClientID, cfg.APIKey, handlers.ProductInfoList))
|
||||
mux.HandleFunc("/v3/product/info/stocks", middleware.Auth(cfg.ClientID, cfg.APIKey, handlers.ProductStocks))
|
||||
mux.HandleFunc("/v4/product/info/prices", middleware.Auth(cfg.ClientID, cfg.APIKey, handlers.ProductPrices))
|
||||
mux.HandleFunc("/v1/product/import/prices", auth(handlers.ImportPrices))
|
||||
mux.HandleFunc("/v1/product/list", auth(handlers.ProductList))
|
||||
mux.HandleFunc("/v3/product/info/list", auth(handlers.ProductInfoList))
|
||||
mux.HandleFunc("/v3/product/info/stocks", auth(handlers.ProductStocks))
|
||||
mux.HandleFunc("/v4/product/info/prices", auth(handlers.ProductPrices))
|
||||
|
||||
// Product cards (Phase 2)
|
||||
mux.HandleFunc("/v3/product/import", auth(handlers.ImportProducts))
|
||||
mux.HandleFunc("/v1/product/info/description", auth(handlers.ProductDescriptions))
|
||||
mux.HandleFunc("/v1/product/info/attributes", auth(handlers.ProductAttributes))
|
||||
|
||||
// Categories (Phase 2)
|
||||
mux.HandleFunc("/v2/category/tree", auth(handlers.CategoryTree))
|
||||
mux.HandleFunc("/v3/category/attribute", auth(handlers.CategoryAttributes))
|
||||
mux.HandleFunc("/v3/category/attribute/values", auth(handlers.CategoryAttributeValues))
|
||||
|
||||
// Postings
|
||||
mux.HandleFunc("/v2/posting/fbs/list", middleware.Auth(cfg.ClientID, cfg.APIKey, handlers.PostingFBSList))
|
||||
mux.HandleFunc("/v2/posting/fbs/get", middleware.Auth(cfg.ClientID, cfg.APIKey, handlers.PostingFBSGet))
|
||||
mux.HandleFunc("/v1/posting/fbs/cancel", middleware.Auth(cfg.ClientID, cfg.APIKey, handlers.CancelPosting))
|
||||
mux.HandleFunc("/v2/posting/fbs/list", auth(handlers.PostingFBSList))
|
||||
mux.HandleFunc("/v2/posting/fbs/get", auth(handlers.PostingFBSGet))
|
||||
mux.HandleFunc("/v1/posting/fbs/cancel", auth(handlers.CancelPosting))
|
||||
|
||||
// Finance
|
||||
mux.HandleFunc("/v1/finance/transaction/list", middleware.Auth(cfg.ClientID, cfg.APIKey, handlers.FinanceTransactionList))
|
||||
mux.HandleFunc("/v1/finance/payout/list", middleware.Auth(cfg.ClientID, cfg.APIKey, handlers.FinancePayoutList))
|
||||
mux.HandleFunc("/v1/finance/transaction/list", auth(handlers.FinanceTransactionList))
|
||||
mux.HandleFunc("/v1/finance/payout/list", auth(handlers.FinancePayoutList))
|
||||
|
||||
// Returns
|
||||
mux.HandleFunc("/v1/returns/list", middleware.Auth(cfg.ClientID, cfg.APIKey, handlers.ReturnsList))
|
||||
mux.HandleFunc("/v1/returns/list", auth(handlers.ReturnsList))
|
||||
|
||||
// Analytics
|
||||
mux.HandleFunc("/v1/analytics/data", middleware.Auth(cfg.ClientID, cfg.APIKey, handlers.AnalyticsData))
|
||||
mux.HandleFunc("/v1/analytics/turnover/stocks", middleware.Auth(cfg.ClientID, cfg.APIKey, handlers.TurnoverStocks))
|
||||
mux.HandleFunc("/v1/analytics/data", auth(handlers.AnalyticsData))
|
||||
mux.HandleFunc("/v1/analytics/turnover/stocks", auth(handlers.TurnoverStocks))
|
||||
|
||||
// Health
|
||||
mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
|
||||
@ -52,6 +66,5 @@ func main() {
|
||||
|
||||
addr := ":" + cfg.Port
|
||||
log.Printf("Starting ozon-mock on %s", addr)
|
||||
log.Printf("Auth: client_id=%s, api_key=%s", cfg.ClientID, cfg.APIKey)
|
||||
log.Fatal(http.ListenAndServe(addr, mux))
|
||||
}
|
||||
|
||||
2
go.mod
2
go.mod
@ -1,5 +1,3 @@
|
||||
module github.com/rinat/ozon-mock
|
||||
|
||||
go 1.25.0
|
||||
|
||||
require github.com/gin-gonic/gin v1.10.0
|
||||
|
||||
263
internal/handlers/categories.go
Normal file
263
internal/handlers/categories.go
Normal file
@ -0,0 +1,263 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type CategoryTreeRequest struct {
|
||||
Language string `json:"language"`
|
||||
}
|
||||
|
||||
type CategoryTreeNode struct {
|
||||
CategoryID int64 `json:"category_id"`
|
||||
Title string `json:"title"`
|
||||
Children []CategoryTreeNode `json:"children"`
|
||||
}
|
||||
|
||||
type CategoryTreeResponse struct {
|
||||
Result []CategoryTreeNode `json:"result"`
|
||||
}
|
||||
|
||||
func CategoryTree(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "method not allowed", 405)
|
||||
return
|
||||
}
|
||||
|
||||
var req CategoryTreeRequest
|
||||
_ = json.NewDecoder(r.Body).Decode(&req)
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(CategoryTreeResponse{
|
||||
Result: []CategoryTreeNode{
|
||||
{
|
||||
CategoryID: 17000000,
|
||||
Title: "Электроника",
|
||||
Children: []CategoryTreeNode{
|
||||
{CategoryID: 17000001, Title: "Смартфоны и гаджеты", Children: []CategoryTreeNode{
|
||||
{CategoryID: 17036136, Title: "Смартфоны"},
|
||||
{CategoryID: 17036137, Title: "Планшеты"},
|
||||
}},
|
||||
{CategoryID: 17000002, Title: "Ноутбуки и компьютеры"},
|
||||
},
|
||||
},
|
||||
{
|
||||
CategoryID: 17010000,
|
||||
Title: "Одежда",
|
||||
Children: []CategoryTreeNode{
|
||||
{CategoryID: 17010001, Title: "Мужская одежда"},
|
||||
{CategoryID: 17010002, Title: "Женская одежда"},
|
||||
},
|
||||
},
|
||||
{
|
||||
CategoryID: 17020000,
|
||||
Title: "Дом и сад",
|
||||
Children: []CategoryTreeNode{
|
||||
{CategoryID: 17020001, Title: "Мебель"},
|
||||
{CategoryID: 17020002, Title: "Освещение"},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
type CategoryAttributeRequest struct {
|
||||
AttributeType string `json:"attribute_type"`
|
||||
CategoryID []int64 `json:"category_id"`
|
||||
Language string `json:"language"`
|
||||
}
|
||||
|
||||
type CategoryAttribute struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Type string `json:"type"`
|
||||
IsCollection bool `json:"is_collection"`
|
||||
IsRequired bool `json:"is_required"`
|
||||
GroupID int64 `json:"group_id"`
|
||||
GroupName string `json:"group_name"`
|
||||
DictionaryID int64 `json:"dictionary_id"`
|
||||
}
|
||||
|
||||
type CategoryAttributesResponse struct {
|
||||
Result []CategoryAttribute `json:"result"`
|
||||
}
|
||||
|
||||
func CategoryAttributes(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "method not allowed", 405)
|
||||
return
|
||||
}
|
||||
|
||||
var req CategoryAttributeRequest
|
||||
_ = json.NewDecoder(r.Body).Decode(&req)
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(CategoryAttributesResponse{
|
||||
Result: []CategoryAttribute{
|
||||
{
|
||||
ID: 8229,
|
||||
Name: "Название модели",
|
||||
Description: "Название модели товара",
|
||||
Type: "String",
|
||||
IsCollection: false,
|
||||
IsRequired: true,
|
||||
GroupID: 1,
|
||||
GroupName: "Основные характеристики",
|
||||
DictionaryID: 0,
|
||||
},
|
||||
{
|
||||
ID: 85,
|
||||
Name: "Бренд",
|
||||
Description: "Производитель товара",
|
||||
Type: "String",
|
||||
IsCollection: false,
|
||||
IsRequired: true,
|
||||
GroupID: 1,
|
||||
GroupName: "Основные характеристики",
|
||||
DictionaryID: 0,
|
||||
},
|
||||
{
|
||||
ID: 9044,
|
||||
Name: "Цвет товара",
|
||||
Description: "Основной цвет",
|
||||
Type: "String",
|
||||
IsCollection: false,
|
||||
IsRequired: false,
|
||||
GroupID: 2,
|
||||
GroupName: "Внешний вид",
|
||||
DictionaryID: 0,
|
||||
},
|
||||
{
|
||||
ID: 4416,
|
||||
Name: "Гарантия",
|
||||
Description: "Срок гарантии в месяцах",
|
||||
Type: "Number",
|
||||
IsCollection: false,
|
||||
IsRequired: false,
|
||||
GroupID: 3,
|
||||
GroupName: "Гарантия и сервис",
|
||||
DictionaryID: 0,
|
||||
},
|
||||
{
|
||||
ID: 4074,
|
||||
Name: "Комплектация",
|
||||
Description: "Что входит в комплект поставки",
|
||||
Type: "String",
|
||||
IsCollection: true,
|
||||
IsRequired: false,
|
||||
GroupID: 4,
|
||||
GroupName: "Комплектация",
|
||||
DictionaryID: 0,
|
||||
},
|
||||
{
|
||||
ID: 5076,
|
||||
Name: "Вес с упаковкой",
|
||||
Description: "Вес товара в упаковке, г",
|
||||
Type: "Number",
|
||||
IsCollection: false,
|
||||
IsRequired: false,
|
||||
GroupID: 5,
|
||||
GroupName: "Упаковка и габариты",
|
||||
DictionaryID: 0,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
type CategoryAttributeValuesRequest struct {
|
||||
AttributeID int64 `json:"attribute_id"`
|
||||
CategoryID int64 `json:"category_id"`
|
||||
Language string `json:"language"`
|
||||
}
|
||||
|
||||
type CategoryAttributeValue struct {
|
||||
ID int64 `json:"id"`
|
||||
Value string `json:"value"`
|
||||
Info string `json:"info,omitempty"`
|
||||
}
|
||||
|
||||
type CategoryAttributeValuesResponse struct {
|
||||
Result []CategoryAttributeValue `json:"result"`
|
||||
}
|
||||
|
||||
func CategoryAttributeValues(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "method not allowed", 405)
|
||||
return
|
||||
}
|
||||
|
||||
var req CategoryAttributeValuesRequest
|
||||
_ = json.NewDecoder(r.Body).Decode(&req)
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(CategoryAttributeValuesResponse{
|
||||
Result: []CategoryAttributeValue{
|
||||
{ID: 1, Value: "Apple", Info: "Apple Inc."},
|
||||
{ID: 2, Value: "Samsung", Info: "Samsung Electronics"},
|
||||
{ID: 3, Value: "Xiaomi", Info: "Xiaomi Corporation"},
|
||||
{ID: 4, Value: "Huawei", Info: "Huawei Technologies"},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
type ProductAttributesRequest struct {
|
||||
Filter struct {
|
||||
OfferIDs []string `json:"offer_id"`
|
||||
ProductIDs []int64 `json:"product_id"`
|
||||
} `json:"filter"`
|
||||
}
|
||||
|
||||
type ProductAttributeValue struct {
|
||||
AttributeID int64 `json:"attribute_id"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type ProductAttributesResult struct {
|
||||
OfferID string `json:"offer_id"`
|
||||
ProductID int64 `json:"product_id"`
|
||||
Attributes []ProductAttributeValue `json:"attributes"`
|
||||
}
|
||||
|
||||
type ProductAttributesResponse struct {
|
||||
Result []ProductAttributesResult `json:"result"`
|
||||
}
|
||||
|
||||
func ProductAttributes(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "method not allowed", 405)
|
||||
return
|
||||
}
|
||||
|
||||
var req ProductAttributesRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeError(w, 400, "invalid json")
|
||||
return
|
||||
}
|
||||
|
||||
allProducts := GetProductsFixtures()
|
||||
results := make([]ProductAttributesResult, 0)
|
||||
|
||||
for _, oid := range req.Filter.OfferIDs {
|
||||
for _, p := range allProducts {
|
||||
if p.OfferID == oid {
|
||||
results = append(results, ProductAttributesResult{
|
||||
OfferID: p.OfferID,
|
||||
ProductID: p.ProductID,
|
||||
Attributes: []ProductAttributeValue{
|
||||
{AttributeID: 8229, Value: p.Name},
|
||||
{AttributeID: 85, Value: "Test Brand"},
|
||||
{AttributeID: 9044, Value: "Черный"},
|
||||
},
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(ProductAttributesResponse{
|
||||
Result: results,
|
||||
})
|
||||
}
|
||||
@ -6,7 +6,6 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// FinanceTransactionListRequest - POST /v1/finance/transaction/list
|
||||
type FinanceTransactionListRequest struct {
|
||||
Filter struct {
|
||||
Since string `json:"since"`
|
||||
@ -49,6 +48,53 @@ func FinanceTransactionList(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
transactions := GetTransactionsFixtures()
|
||||
|
||||
if req.Filter.Since != "" {
|
||||
sinceTs := parseTimeOrZero(req.Filter.Since)
|
||||
filtered := make([]Transaction, 0)
|
||||
for _, t := range transactions {
|
||||
ts := parseTimeOrZero(t.CreatedAt)
|
||||
if ts >= sinceTs {
|
||||
filtered = append(filtered, t)
|
||||
}
|
||||
}
|
||||
transactions = filtered
|
||||
}
|
||||
|
||||
if req.Filter.To != "" {
|
||||
toTs := parseTimeOrZero(req.Filter.To)
|
||||
filtered := make([]Transaction, 0)
|
||||
for _, t := range transactions {
|
||||
ts := parseTimeOrZero(t.CreatedAt)
|
||||
if ts <= toTs {
|
||||
filtered = append(filtered, t)
|
||||
}
|
||||
}
|
||||
transactions = filtered
|
||||
}
|
||||
|
||||
if req.Filter.TransactionType != "" {
|
||||
filtered := make([]Transaction, 0)
|
||||
for _, t := range transactions {
|
||||
if t.TransactionType == req.Filter.TransactionType {
|
||||
filtered = append(filtered, t)
|
||||
}
|
||||
}
|
||||
transactions = filtered
|
||||
}
|
||||
|
||||
if len(req.Filter.PostingNumber) > 0 {
|
||||
filtered := make([]Transaction, 0)
|
||||
for _, t := range transactions {
|
||||
for _, num := range req.Filter.PostingNumber {
|
||||
if t.PostingNumber == num {
|
||||
filtered = append(filtered, t)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
transactions = filtered
|
||||
}
|
||||
|
||||
if req.Limit == 0 {
|
||||
req.Limit = 100
|
||||
}
|
||||
@ -64,7 +110,6 @@ func FinanceTransactionList(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
// FinancePayoutListRequest - POST /v1/finance/payout/list
|
||||
type FinancePayoutListRequest struct {
|
||||
Filter struct {
|
||||
Since string `json:"since"`
|
||||
@ -97,6 +142,30 @@ func FinancePayoutList(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
payouts := GetPayoutsFixtures()
|
||||
|
||||
if req.Filter.Since != "" {
|
||||
sinceTs := parseTimeOrZero(req.Filter.Since)
|
||||
filtered := make([]Payout, 0)
|
||||
for _, p := range payouts {
|
||||
ts := parseTimeOrZero(p.CreatedAt)
|
||||
if ts >= sinceTs {
|
||||
filtered = append(filtered, p)
|
||||
}
|
||||
}
|
||||
payouts = filtered
|
||||
}
|
||||
|
||||
if req.Filter.To != "" {
|
||||
toTs := parseTimeOrZero(req.Filter.To)
|
||||
filtered := make([]Payout, 0)
|
||||
for _, p := range payouts {
|
||||
ts := parseTimeOrZero(p.CreatedAt)
|
||||
if ts <= toTs {
|
||||
filtered = append(filtered, p)
|
||||
}
|
||||
}
|
||||
payouts = filtered
|
||||
}
|
||||
|
||||
if req.Limit == 0 {
|
||||
req.Limit = 100
|
||||
}
|
||||
@ -112,7 +181,6 @@ func FinancePayoutList(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
// AnalyticsDataRequest - POST /v1/analytics/data
|
||||
type AnalyticsDataRequest struct {
|
||||
DateFrom string `json:"date_from"`
|
||||
DateTo string `json:"date_to"`
|
||||
@ -126,21 +194,31 @@ func AnalyticsData(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]any{
|
||||
"data": []map[string]any{
|
||||
var req AnalyticsDataRequest
|
||||
_ = json.NewDecoder(r.Body).Decode(&req)
|
||||
|
||||
results := []map[string]any{
|
||||
{
|
||||
"date": time.Now().Format("2006-01-02"),
|
||||
"orders_count": 42,
|
||||
"revenue": 125000.50,
|
||||
"avg_order": 2976.20,
|
||||
},
|
||||
},
|
||||
}
|
||||
if req.DateFrom != "" {
|
||||
results[0]["date_from"] = req.DateFrom
|
||||
}
|
||||
if req.DateTo != "" {
|
||||
results[0]["date_to"] = req.DateTo
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]any{
|
||||
"data": results,
|
||||
"total": 1,
|
||||
})
|
||||
}
|
||||
|
||||
// TurnoverStocksRequest - POST /v1/analytics/turnover/stocks
|
||||
type TurnoverStocksRequest struct {
|
||||
Date string `json:"date"`
|
||||
SKU []string `json:"sku"`
|
||||
@ -163,6 +241,13 @@ func TurnoverStocks(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
var req TurnoverStocksRequest
|
||||
_ = json.NewDecoder(r.Body).Decode(&req)
|
||||
|
||||
if len(req.SKU) > 0 {
|
||||
_ = req.SKU
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(TurnoverStocksResponse{
|
||||
Items: []struct {
|
||||
|
||||
@ -1,242 +1,416 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
type company struct {
|
||||
name string
|
||||
prefix string
|
||||
categoryID int64
|
||||
warehouseID int64
|
||||
warehouse string
|
||||
productCount int
|
||||
}
|
||||
|
||||
var companies = []company{
|
||||
{name: "TechGiant", prefix: "TG", categoryID: 17000000, warehouseID: 99100001001, warehouse: "Moscow FBO", productCount: 35},
|
||||
{name: "FashionLine", prefix: "FL", categoryID: 17010000, warehouseID: 99100001002, warehouse: "St. Petersburg FBO", productCount: 35},
|
||||
{name: "HomeStyle", prefix: "HS", categoryID: 17020000, warehouseID: 99100001003, warehouse: "Kazan FBO", productCount: 34},
|
||||
}
|
||||
|
||||
var productNames = map[string][]string{
|
||||
"TechGiant": {
|
||||
"Смартфон Galaxy A%d", "Ноутбук ProBook %d", "Планшет Tab %d",
|
||||
"Наушники SoundBuds %d", "Часы SmartWatch %d", "Колонка BeatBox %d",
|
||||
"Монитор UltraView %d", "Клавиатура TypeMaster %d", "Мышь ClickPro %d",
|
||||
"Роутер NetStream %d", "Камера SnapCam %d", "Принтер PrintJet %d",
|
||||
"Флешка DataStick %d", "Внешний диск StoreVault %d", "Зарядка PowerBoost %d",
|
||||
},
|
||||
"FashionLine": {
|
||||
"Футболка Casual %d", "Джинсы Street %d", "Куртка WindPro %d",
|
||||
"Кроссовки RunFast %d", "Рубашка Classic %d", "Платье Evening %d",
|
||||
"Свитер WarmTouch %d", "Шорты SummerBreeze %d", "Пальто WinterLux %d",
|
||||
"Кепка SunShade %d", "Шарф CozyWrap %d", "Перчатки GripPro %d",
|
||||
"Ремень BeltLine %d", "Носки ComfyStep %d", "Сумка CarryAll %d",
|
||||
},
|
||||
"HomeStyle": {
|
||||
"Стол WorkDesk %d", "Стул SitEasy %d", "Лампа LightGlow %d",
|
||||
"Полка ShelfMaster %d", "Ковёр SoftFloor %d", "Штора WindowDress %d",
|
||||
"Подушка DreamSoft %d", "Одеяло WarmCloud %d", "Посуда CookSet %d",
|
||||
"Ваза FlowerHold %d", "Зеркало ReflectPro %d", "Часы WallTime %d",
|
||||
"Органайзер DeskTidy %d", "Корзина BinSmart %d", "Вешалка HangRight %d",
|
||||
},
|
||||
}
|
||||
|
||||
func generateOfferID(prefix string, num int) string {
|
||||
return fmt.Sprintf("%s-%04d", prefix, num)
|
||||
}
|
||||
|
||||
func generateBarcode(prefix string, num int) string {
|
||||
return fmt.Sprintf("%s%08d", prefix, 10000000+num)
|
||||
}
|
||||
|
||||
func allProducts() []ProductInfoItem {
|
||||
result := make([]ProductInfoItem, 0)
|
||||
for _, c := range companies {
|
||||
names := productNames[c.name]
|
||||
for i := 1; i <= c.productCount; i++ {
|
||||
nameIdx := (i - 1) % len(names)
|
||||
result = append(result, ProductInfoItem{
|
||||
ProductID: int64(99000000000 + len(result) + 1),
|
||||
OfferID: generateOfferID(c.prefix, i),
|
||||
Name: fmt.Sprintf(names[nameIdx], i),
|
||||
Barcode: generateBarcode(c.prefix, i),
|
||||
BuyPrice: float64(500 + (i*173)%10000),
|
||||
Price: float64(990 + (i*197)%15000),
|
||||
PremiumPrice: float64(1190 + (i*211)%17000),
|
||||
CategoryID: c.categoryID,
|
||||
})
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func allStocks() []StockItem {
|
||||
result := make([]StockItem, 0)
|
||||
for _, c := range companies {
|
||||
for i := 1; i <= c.productCount; i++ {
|
||||
stock := (i*73 + 10) % 500
|
||||
if stock < 0 {
|
||||
stock = 0
|
||||
}
|
||||
result = append(result, StockItem{
|
||||
ProductID: int64(99000000000 + len(result) + 1),
|
||||
OfferID: generateOfferID(c.prefix, i),
|
||||
Stock: stock,
|
||||
WarehouseID: c.warehouseID,
|
||||
WarehouseName: c.warehouse,
|
||||
})
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func allPrices() []ProductPriceItem {
|
||||
result := make([]ProductPriceItem, 0)
|
||||
for _, c := range companies {
|
||||
for i := 1; i <= c.productCount; i++ {
|
||||
price := float64(990 + (i*197)%15000)
|
||||
result = append(result, ProductPriceItem{
|
||||
ProductID: int64(0),
|
||||
OfferID: generateOfferID(c.prefix, i),
|
||||
Price: price,
|
||||
OldPrice: price * 1.1,
|
||||
PremiumPrice: price * 1.15,
|
||||
PremiumPriceOld: price * 1.25,
|
||||
})
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func GetStocksFixtures() []StockItem {
|
||||
data := []StockItem{
|
||||
{ProductID: 99001234001, OfferID: "TEST-SKU-001", Stock: 150, WarehouseID: 99100001001, WarehouseName: "Moscow FBO"},
|
||||
{ProductID: 99001234002, OfferID: "TEST-SKU-002", Stock: 75, WarehouseID: 99100001001, WarehouseName: "Moscow FBO"},
|
||||
{ProductID: 99001234003, OfferID: "TEST-SKU-003", Stock: 200, WarehouseID: 99100001002, WarehouseName: "St. Petersburg FBO"},
|
||||
{ProductID: 99001234004, OfferID: "TEST-SKU-004", Stock: 0, WarehouseID: 99100001003, WarehouseName: "Kazan FBO"},
|
||||
{ProductID: 99001234005, OfferID: "TEST-SKU-005", Stock: 50, WarehouseID: 99100001001, WarehouseName: "Moscow FBO"},
|
||||
if path := os.Getenv("MOCK_STOCKS_JSON"); path != "" {
|
||||
if fileData, err := os.ReadFile(path); err == nil {
|
||||
var loaded []StockItem
|
||||
if json.Unmarshal(fileData, &loaded) == nil && len(loaded) > 0 {
|
||||
log.Printf("MOCK_STOCKS_JSON: loaded %d items from %s", len(loaded), path)
|
||||
return loaded
|
||||
}
|
||||
|
||||
// Try to load from file if exists
|
||||
if dataStr := os.Getenv("MOCK_STOCKS_JSON"); dataStr != "" {
|
||||
log.Printf("MOCK_STOCKS_JSON is set, ignoring fixtures")
|
||||
}
|
||||
|
||||
return data
|
||||
log.Printf("MOCK_STOCKS_JSON: failed to load from %s, using defaults", path)
|
||||
}
|
||||
return allStocks()
|
||||
}
|
||||
|
||||
func GetProductsFixtures() []ProductInfoItem {
|
||||
return []ProductInfoItem{
|
||||
{
|
||||
ProductID: 99001234001,
|
||||
OfferID: "TEST-SKU-001",
|
||||
Name: "Тестовый товар 1",
|
||||
Barcode: "4600000000001",
|
||||
BuyPrice: 1000.00,
|
||||
Price: 1990.00,
|
||||
PremiumPrice: 2190.00,
|
||||
CategoryID: 17000000,
|
||||
},
|
||||
{
|
||||
ProductID: 99001234002,
|
||||
OfferID: "TEST-SKU-002",
|
||||
Name: "Тестовый товар 2",
|
||||
Barcode: "4600000000002",
|
||||
BuyPrice: 500.00,
|
||||
Price: 990.00,
|
||||
PremiumPrice: 1090.00,
|
||||
CategoryID: 17000001,
|
||||
},
|
||||
{
|
||||
ProductID: 99001234003,
|
||||
OfferID: "TEST-SKU-003",
|
||||
Name: "Тестовый товар 3",
|
||||
Barcode: "4600000000003",
|
||||
BuyPrice: 2000.00,
|
||||
Price: 3990.00,
|
||||
PremiumPrice: 4390.00,
|
||||
CategoryID: 17000000,
|
||||
},
|
||||
{
|
||||
ProductID: 99001234004,
|
||||
OfferID: "TEST-SKU-004",
|
||||
Name: "Тестовый товар 4",
|
||||
Barcode: "4600000000004",
|
||||
BuyPrice: 300.00,
|
||||
Price: 590.00,
|
||||
PremiumPrice: 650.00,
|
||||
CategoryID: 17000002,
|
||||
},
|
||||
{
|
||||
ProductID: 99001234005,
|
||||
OfferID: "TEST-SKU-005",
|
||||
Name: "Тестовый товар 5",
|
||||
Barcode: "4600000000005",
|
||||
BuyPrice: 800.00,
|
||||
Price: 1590.00,
|
||||
PremiumPrice: 1750.00,
|
||||
CategoryID: 17000001,
|
||||
},
|
||||
}
|
||||
return allProducts()
|
||||
}
|
||||
|
||||
func GetPricesFixtures() []ProductPriceItem {
|
||||
return []ProductPriceItem{
|
||||
{OfferID: "TEST-SKU-001", Price: 1990.00, OldPrice: 2190.00, PremiumPrice: 2190.00, PremiumPriceOld: 2390.00},
|
||||
{OfferID: "TEST-SKU-002", Price: 990.00, OldPrice: 1090.00, PremiumPrice: 1090.00, PremiumPriceOld: 1190.00},
|
||||
{OfferID: "TEST-SKU-003", Price: 3990.00, OldPrice: 4490.00, PremiumPrice: 4390.00, PremiumPriceOld: 4890.00},
|
||||
{OfferID: "TEST-SKU-004", Price: 590.00, OldPrice: 690.00, PremiumPrice: 650.00, PremiumPriceOld: 750.00},
|
||||
{OfferID: "TEST-SKU-005", Price: 1590.00, OldPrice: 1790.00, PremiumPrice: 1750.00, PremiumPriceOld: 1950.00},
|
||||
return allPrices()
|
||||
}
|
||||
|
||||
type postingSpec struct {
|
||||
companyIdx int
|
||||
productNum1 int
|
||||
productNum2 int
|
||||
status string
|
||||
createdAt string
|
||||
updatedAt string
|
||||
orderType string
|
||||
}
|
||||
|
||||
var postingSpecs = []postingSpec{
|
||||
{0, 1, 0, "delivered", "2024-01-15T10:30:00Z", "2024-01-17T14:45:00Z", "fbs"},
|
||||
{0, 2, 3, "delivered", "2024-01-16T11:00:00Z", "2024-01-18T09:30:00Z", "fbs"},
|
||||
{0, 4, 0, "awaiting_deliver", "2024-01-18T08:15:00Z", "2024-01-18T08:15:00Z", "fbs"},
|
||||
{0, 5, 0, "cancelled", "2024-01-17T16:00:00Z", "2024-01-17T18:30:00Z", "fbs"},
|
||||
{0, 7, 8, "delivered", "2024-02-01T09:00:00Z", "2024-02-03T12:00:00Z", "fbs"},
|
||||
{0, 10, 0, "delivered", "2024-02-05T14:00:00Z", "2024-02-07T16:00:00Z", "fbs"},
|
||||
{0, 12, 13, "awaiting_deliver", "2024-02-10T06:00:00Z", "2024-02-10T06:00:00Z", "fbs"},
|
||||
{0, 15, 0, "cancelled", "2024-02-12T18:00:00Z", "2024-02-12T20:00:00Z", "fbs"},
|
||||
{0, 18, 20, "delivered", "2024-03-01T08:00:00Z", "2024-03-03T10:00:00Z", "fbo"},
|
||||
{0, 22, 0, "delivered", "2024-03-05T11:00:00Z", "2024-03-07T13:00:00Z", "fbo"},
|
||||
{1, 1, 0, "delivered", "2024-01-20T12:00:00Z", "2024-01-22T15:00:00Z", "fbs"},
|
||||
{1, 2, 3, "delivered", "2024-01-22T09:00:00Z", "2024-01-24T11:00:00Z", "fbs"},
|
||||
{1, 5, 0, "awaiting_deliver", "2024-01-25T14:00:00Z", "2024-01-25T14:00:00Z", "fbs"},
|
||||
{1, 7, 8, "cancelled", "2024-01-28T16:00:00Z", "2024-01-28T18:00:00Z", "fbs"},
|
||||
{1, 10, 0, "delivered", "2024-02-02T10:00:00Z", "2024-02-04T12:00:00Z", "fbs"},
|
||||
{1, 12, 14, "delivered", "2024-02-08T08:00:00Z", "2024-02-10T10:00:00Z", "fbo"},
|
||||
{1, 16, 0, "awaiting_deliver", "2024-02-15T13:00:00Z", "2024-02-15T13:00:00Z", "fbo"},
|
||||
{1, 18, 20, "delivered", "2024-03-01T07:00:00Z", "2024-03-03T09:00:00Z", "fbs"},
|
||||
{2, 1, 0, "delivered", "2024-01-18T11:00:00Z", "2024-01-20T13:00:00Z", "fbs"},
|
||||
{2, 3, 4, "delivered", "2024-01-25T15:00:00Z", "2024-01-27T17:00:00Z", "fbs"},
|
||||
{2, 6, 0, "awaiting_deliver", "2024-02-01T09:00:00Z", "2024-02-01T09:00:00Z", "fbs"},
|
||||
{2, 8, 9, "cancelled", "2024-02-05T14:00:00Z", "2024-02-05T16:00:00Z", "fbs"},
|
||||
{2, 11, 0, "delivered", "2024-02-10T08:00:00Z", "2024-02-12T10:00:00Z", "fbo"},
|
||||
{2, 13, 15, "delivered", "2024-02-20T12:00:00Z", "2024-02-22T14:00:00Z", "fbo"},
|
||||
{2, 17, 0, "awaiting_deliver", "2024-03-01T06:00:00Z", "2024-03-01T06:00:00Z", "fbs"},
|
||||
{2, 19, 21, "delivered", "2024-03-10T10:00:00Z", "2024-03-12T12:00:00Z", "fbs"},
|
||||
{0, 25, 0, "delivered", "2024-03-15T08:00:00Z", "2024-03-17T10:00:00Z", "fbs"},
|
||||
{1, 22, 0, "delivered", "2024-03-18T11:00:00Z", "2024-03-20T13:00:00Z", "fbs"},
|
||||
{2, 22, 23, "awaiting_deliver", "2024-04-01T07:00:00Z", "2024-04-01T07:00:00Z", "fbo"},
|
||||
{0, 28, 30, "delivered", "2024-04-05T09:00:00Z", "2024-04-07T11:00:00Z", "fbo"},
|
||||
{1, 25, 0, "delivered", "2024-04-10T14:00:00Z", "2024-04-12T16:00:00Z", "fbs"},
|
||||
{2, 25, 27, "cancelled", "2024-04-15T10:00:00Z", "2024-04-15T12:00:00Z", "fbs"},
|
||||
{0, 30, 32, "delivered", "2024-05-01T08:00:00Z", "2024-05-03T10:00:00Z", "fbs"},
|
||||
{1, 28, 0, "delivered", "2024-05-05T12:00:00Z", "2024-05-07T14:00:00Z", "fbs"},
|
||||
{2, 28, 30, "awaiting_deliver", "2024-05-10T09:00:00Z", "2024-05-10T09:00:00Z", "fbo"},
|
||||
{0, 33, 0, "delivered", "2024-05-15T11:00:00Z", "2024-05-17T13:00:00Z", "fbo"},
|
||||
{1, 31, 33, "delivered", "2024-06-01T07:00:00Z", "2024-06-03T09:00:00Z", "fbs"},
|
||||
{2, 32, 0, "delivered", "2024-06-05T14:00:00Z", "2024-06-07T16:00:00Z", "fbs"},
|
||||
{0, 35, 0, "cancelled", "2024-06-10T08:00:00Z", "2024-06-10T10:00:00Z", "fbs"},
|
||||
{1, 35, 0, "delivered", "2024-06-15T10:00:00Z", "2024-06-17T12:00:00Z", "fbo"},
|
||||
{2, 34, 0, "delivered", "2024-07-01T09:00:00Z", "2024-07-03T11:00:00Z", "fbs"},
|
||||
}
|
||||
|
||||
func productByNum(c company, num int) (int64, string, string, float64) {
|
||||
idx := 0
|
||||
for _, comp := range companies {
|
||||
for i := 1; i <= comp.productCount; i++ {
|
||||
if comp.name == c.name && i == num {
|
||||
names := productNames[comp.name]
|
||||
nameIdx := (i - 1) % len(names)
|
||||
pid := int64(99000000000 + idx + 1)
|
||||
offerID := generateOfferID(c.prefix, i)
|
||||
name := fmt.Sprintf(names[nameIdx], i)
|
||||
price := float64(990 + (i*197)%15000)
|
||||
return pid, offerID, name, price
|
||||
}
|
||||
idx++
|
||||
}
|
||||
}
|
||||
return 0, "", "", 0
|
||||
}
|
||||
|
||||
func getCompanyProducts(c company) []ProductInfoItem {
|
||||
result := make([]ProductInfoItem, 0)
|
||||
names := productNames[c.name]
|
||||
for i := 1; i <= c.productCount; i++ {
|
||||
nameIdx := (i - 1) % len(names)
|
||||
result = append(result, ProductInfoItem{
|
||||
ProductID: int64(99000000000 + len(result) + 1),
|
||||
OfferID: generateOfferID(c.prefix, i),
|
||||
Name: fmt.Sprintf(names[nameIdx], i),
|
||||
Barcode: generateBarcode(c.prefix, i),
|
||||
CategoryID: c.categoryID,
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func GetPostingsFixtures() []Posting {
|
||||
return []Posting{
|
||||
{
|
||||
PostingNumber: "TEST-POST-001",
|
||||
OrderID: 99000000001,
|
||||
OrderNumber: "TEST-ORD-001",
|
||||
Status: "delivered",
|
||||
Products: []PostingItem{
|
||||
{ProductID: 99001234001, OfferID: "TEST-SKU-001", Name: "Тестовый товар 1", Quantity: 2, Price: 1990.00, ItemsCount: 1},
|
||||
},
|
||||
CreatedAt: "2024-01-15T10:30:00Z",
|
||||
UpdatedAt: "2024-01-17T14:45:00Z",
|
||||
WarehouseID: 99100001001,
|
||||
OrderType: "fbs",
|
||||
},
|
||||
{
|
||||
PostingNumber: "TEST-POST-002",
|
||||
OrderID: 99000000002,
|
||||
OrderNumber: "TEST-ORD-002",
|
||||
Status: "delivered",
|
||||
Products: []PostingItem{
|
||||
{ProductID: 99001234002, OfferID: "TEST-SKU-002", Name: "Тестовый товар 2", Quantity: 1, Price: 990.00, ItemsCount: 1},
|
||||
{ProductID: 99001234003, OfferID: "TEST-SKU-003", Name: "Тестовый товар 3", Quantity: 1, Price: 3990.00, ItemsCount: 1},
|
||||
},
|
||||
CreatedAt: "2024-01-16T11:00:00Z",
|
||||
UpdatedAt: "2024-01-18T09:30:00Z",
|
||||
WarehouseID: 99100001001,
|
||||
OrderType: "fbs",
|
||||
},
|
||||
{
|
||||
PostingNumber: "TEST-POST-003",
|
||||
OrderID: 99000000003,
|
||||
OrderNumber: "TEST-ORD-003",
|
||||
Status: "awaiting_deliver",
|
||||
Products: []PostingItem{
|
||||
{ProductID: 99001234004, OfferID: "TEST-SKU-004", Name: "Тестовый товар 4", Quantity: 3, Price: 590.00, ItemsCount: 1},
|
||||
},
|
||||
CreatedAt: "2024-01-18T08:15:00Z",
|
||||
UpdatedAt: "2024-01-18T08:15:00Z",
|
||||
WarehouseID: 99100001002,
|
||||
OrderType: "fbs",
|
||||
},
|
||||
{
|
||||
PostingNumber: "TEST-POST-004",
|
||||
OrderID: 99000000004,
|
||||
OrderNumber: "TEST-ORD-004",
|
||||
Status: "cancelled",
|
||||
Products: []PostingItem{
|
||||
{ProductID: 99001234005, OfferID: "TEST-SKU-005", Name: "Тестовый товар 5", Quantity: 1, Price: 1590.00, ItemsCount: 1},
|
||||
},
|
||||
CreatedAt: "2024-01-17T16:00:00Z",
|
||||
UpdatedAt: "2024-01-17T18:30:00Z",
|
||||
WarehouseID: 99100001001,
|
||||
OrderType: "fbs",
|
||||
},
|
||||
result := make([]Posting, len(postingSpecs))
|
||||
for idx, spec := range postingSpecs {
|
||||
c := companies[spec.companyIdx]
|
||||
pid1, oid1, name1, price1 := productByNum(c, spec.productNum1)
|
||||
|
||||
products := []PostingItem{
|
||||
{ProductID: pid1, OfferID: oid1, Name: name1, Quantity: (idx%3 + 1), Price: price1, ItemsCount: 1},
|
||||
}
|
||||
if spec.productNum2 > 0 {
|
||||
pid2, oid2, name2, price2 := productByNum(c, spec.productNum2)
|
||||
products = append(products, PostingItem{
|
||||
ProductID: pid2, OfferID: oid2, Name: name2, Quantity: (idx%2 + 1), Price: price2, ItemsCount: 1,
|
||||
})
|
||||
}
|
||||
|
||||
orderID := int64(99000000000 + idx + 1)
|
||||
result[idx] = Posting{
|
||||
PostingNumber: fmt.Sprintf("%s-POST-%03d", c.prefix, idx+1),
|
||||
OrderID: orderID,
|
||||
OrderNumber: fmt.Sprintf("%s-ORD-%03d", c.prefix, idx+1),
|
||||
Status: spec.status,
|
||||
Products: products,
|
||||
CreatedAt: spec.createdAt,
|
||||
UpdatedAt: spec.updatedAt,
|
||||
WarehouseID: c.warehouseID,
|
||||
OrderType: spec.orderType,
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func GetReturnsFixtures() []Return {
|
||||
return []Return{
|
||||
{
|
||||
ReturnID: 99000001,
|
||||
PostingNumber: "TEST-POST-001",
|
||||
Status: "completed",
|
||||
Reason: "wrong_item",
|
||||
ReasonID: 10,
|
||||
CreatedAt: "2024-01-20T10:00:00Z",
|
||||
DeliveredAt: "2024-01-22T15:00:00Z",
|
||||
returnSpecs := []struct {
|
||||
companyIdx int
|
||||
postingIdx int
|
||||
status string
|
||||
reason string
|
||||
reasonID int64
|
||||
createdAt string
|
||||
deliveredAt string
|
||||
}{
|
||||
{0, 0, "completed", "wrong_item", 10, "2024-01-20T10:00:00Z", "2024-01-22T15:00:00Z"},
|
||||
{0, 1, "pending", "buyer_remorse", 20, "2024-01-21T14:30:00Z", ""},
|
||||
{0, 4, "completed", "defect", 30, "2024-02-10T08:00:00Z", "2024-02-12T10:00:00Z"},
|
||||
{1, 10, "completed", "wrong_item", 10, "2024-02-05T11:00:00Z", "2024-02-07T13:00:00Z"},
|
||||
{1, 11, "pending", "buyer_remorse", 20, "2024-02-15T16:00:00Z", ""},
|
||||
{1, 14, "completed", "wrong_size", 40, "2024-03-01T09:00:00Z", "2024-03-03T11:00:00Z"},
|
||||
{2, 18, "completed", "defect", 30, "2024-02-01T14:00:00Z", "2024-02-03T16:00:00Z"},
|
||||
{2, 19, "pending", "buyer_remorse", 20, "2024-02-20T10:00:00Z", ""},
|
||||
{2, 23, "completed", "wrong_item", 10, "2024-03-15T08:00:00Z", "2024-03-17T10:00:00Z"},
|
||||
{0, 5, "pending", "wrong_color", 50, "2024-03-20T12:00:00Z", ""},
|
||||
{1, 16, "completed", "defect", 30, "2024-04-01T07:00:00Z", "2024-04-03T09:00:00Z"},
|
||||
{2, 24, "pending", "buyer_remorse", 20, "2024-04-10T14:00:00Z", ""},
|
||||
{0, 8, "completed", "wrong_item", 10, "2024-05-01T10:00:00Z", "2024-05-03T12:00:00Z"},
|
||||
{1, 17, "completed", "wrong_size", 40, "2024-05-15T09:00:00Z", "2024-05-17T11:00:00Z"},
|
||||
{2, 28, "pending", "defect", 30, "2024-06-05T08:00:00Z", ""},
|
||||
{0, 6, "completed", "buyer_remorse", 20, "2024-03-01T06:00:00Z", "2024-03-03T08:00:00Z"},
|
||||
{1, 12, "pending", "wrong_item", 10, "2024-02-25T15:00:00Z", ""},
|
||||
{2, 20, "completed", "wrong_color", 50, "2024-03-05T11:00:00Z", "2024-03-07T13:00:00Z"},
|
||||
{0, 9, "pending", "defect", 30, "2024-04-15T09:00:00Z", ""},
|
||||
{1, 13, "completed", "buyer_remorse", 20, "2024-03-10T14:00:00Z", "2024-03-12T16:00:00Z"},
|
||||
}
|
||||
|
||||
result := make([]Return, len(returnSpecs))
|
||||
for idx, spec := range returnSpecs {
|
||||
c := companies[spec.companyIdx]
|
||||
ps := postingSpecs[spec.postingIdx]
|
||||
pid, oid, name, _ := productByNum(c, ps.productNum1)
|
||||
|
||||
result[idx] = Return{
|
||||
ReturnID: int64(99000000 + idx + 1),
|
||||
PostingNumber: fmt.Sprintf("%s-POST-%03d", c.prefix, spec.postingIdx+1),
|
||||
Status: spec.status,
|
||||
Reason: spec.reason,
|
||||
ReasonID: spec.reasonID,
|
||||
CreatedAt: spec.createdAt,
|
||||
DeliveredAt: spec.deliveredAt,
|
||||
Items: []ReturnItem{
|
||||
{ProductID: 99001234001, OfferID: "TEST-SKU-001", Name: "Тестовый товар 1", Quantity: 1, IsOptional: false},
|
||||
},
|
||||
},
|
||||
{
|
||||
ReturnID: 99000002,
|
||||
PostingNumber: "TEST-POST-002",
|
||||
Status: "pending",
|
||||
Reason: "buyer_remorse",
|
||||
ReasonID: 20,
|
||||
CreatedAt: "2024-01-21T14:30:00Z",
|
||||
Items: []ReturnItem{
|
||||
{ProductID: 99001234002, OfferID: "TEST-SKU-002", Name: "Тестовый товар 2", Quantity: 1, IsOptional: true},
|
||||
},
|
||||
{ProductID: pid, OfferID: oid, Name: name, Quantity: 1, IsOptional: false},
|
||||
},
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func GetTransactionsFixtures() []Transaction {
|
||||
return []Transaction{
|
||||
{
|
||||
TransactionType: "orders",
|
||||
Amount: 3980.00,
|
||||
CurrencyCode: "RUB",
|
||||
PostingNumber: "TEST-POST-001",
|
||||
OrderID: 99000000001,
|
||||
CreatedAt: "2024-01-17T00:00:00Z",
|
||||
Items: []TransactionItem{
|
||||
{Type: "sale", Amount: 1990.00, Quantity: 1, ProductID: 99001234001, OfferID: "TEST-SKU-001"},
|
||||
{Type: "sale", Amount: 1990.00, Quantity: 1, ProductID: 99001234001, OfferID: "TEST-SKU-001"},
|
||||
},
|
||||
},
|
||||
{
|
||||
TransactionType: "orders",
|
||||
Amount: 4980.00,
|
||||
CurrencyCode: "RUB",
|
||||
PostingNumber: "TEST-POST-002",
|
||||
OrderID: 99000000002,
|
||||
CreatedAt: "2024-01-18T00:00:00Z",
|
||||
Items: []TransactionItem{
|
||||
{Type: "sale", Amount: 990.00, Quantity: 1, ProductID: 99001234002, OfferID: "TEST-SKU-002"},
|
||||
{Type: "sale", Amount: 3990.00, Quantity: 1, ProductID: 99001234003, OfferID: "TEST-SKU-003"},
|
||||
},
|
||||
},
|
||||
{
|
||||
TransactionType: "commission",
|
||||
Amount: -597.00,
|
||||
CurrencyCode: "RUB",
|
||||
PostingNumber: "TEST-POST-001",
|
||||
OrderID: 99000000001,
|
||||
CreatedAt: "2024-01-17T00:00:00Z",
|
||||
},
|
||||
{
|
||||
TransactionType: "refund",
|
||||
Amount: -990.00,
|
||||
CurrencyCode: "RUB",
|
||||
PostingNumber: "TEST-POST-004",
|
||||
OrderID: 99000000004,
|
||||
CreatedAt: "2024-01-17T00:00:00Z",
|
||||
},
|
||||
txDefs := []struct {
|
||||
txType string
|
||||
amount float64
|
||||
companyIdx int
|
||||
postingIdx int
|
||||
createdAt string
|
||||
}{
|
||||
{"orders", 3980.00, 0, 0, "2024-01-17T00:00:00Z"},
|
||||
{"orders", 4980.00, 0, 1, "2024-01-18T00:00:00Z"},
|
||||
{"commission", -597.00, 0, 0, "2024-01-17T00:00:00Z"},
|
||||
{"refund", -990.00, 0, 3, "2024-01-17T00:00:00Z"},
|
||||
{"orders", 2500.00, 0, 4, "2024-02-05T00:00:00Z"},
|
||||
{"commission", -375.00, 0, 4, "2024-02-05T00:00:00Z"},
|
||||
{"orders", 3200.00, 1, 10, "2024-01-25T00:00:00Z"},
|
||||
{"commission", -480.00, 1, 10, "2024-01-25T00:00:00Z"},
|
||||
{"orders", 4500.00, 1, 11, "2024-01-26T00:00:00Z"},
|
||||
{"refund", -1200.00, 1, 13, "2024-01-30T00:00:00Z"},
|
||||
{"orders", 2800.00, 2, 18, "2024-01-22T00:00:00Z"},
|
||||
{"commission", -420.00, 2, 18, "2024-01-22T00:00:00Z"},
|
||||
{"orders", 3600.00, 2, 19, "2024-01-28T00:00:00Z"},
|
||||
{"refund", -800.00, 2, 21, "2024-02-07T00:00:00Z"},
|
||||
{"orders", 5100.00, 0, 8, "2024-04-01T00:00:00Z"},
|
||||
{"commission", -765.00, 0, 8, "2024-04-01T00:00:00Z"},
|
||||
{"orders", 3400.00, 1, 16, "2024-04-05T00:00:00Z"},
|
||||
{"commission", -510.00, 1, 16, "2024-04-05T00:00:00Z"},
|
||||
{"orders", 4200.00, 2, 23, "2024-04-12T00:00:00Z"},
|
||||
{"refund", -950.00, 2, 31, "2024-04-17T00:00:00Z"},
|
||||
{"orders", 2900.00, 0, 26, "2024-03-18T00:00:00Z"},
|
||||
{"orders", 3800.00, 1, 14, "2024-02-05T00:00:00Z"},
|
||||
{"commission", -570.00, 1, 14, "2024-02-05T00:00:00Z"},
|
||||
{"orders", 4700.00, 2, 28, "2024-05-12T00:00:00Z"},
|
||||
{"commission", -705.00, 2, 28, "2024-05-12T00:00:00Z"},
|
||||
}
|
||||
|
||||
result := make([]Transaction, len(txDefs))
|
||||
for idx, def := range txDefs {
|
||||
c := companies[def.companyIdx]
|
||||
ps := postingSpecs[def.postingIdx]
|
||||
_, oid1, _, _ := productByNum(c, ps.productNum1)
|
||||
|
||||
orderID := int64(99000000000 + def.postingIdx + 1)
|
||||
tx := Transaction{
|
||||
TransactionType: def.txType,
|
||||
Amount: def.amount,
|
||||
CurrencyCode: "RUB",
|
||||
PostingNumber: fmt.Sprintf("%s-POST-%03d", c.prefix, def.postingIdx+1),
|
||||
OrderID: orderID,
|
||||
CreatedAt: def.createdAt,
|
||||
}
|
||||
if def.txType == "orders" {
|
||||
tx.Items = []TransactionItem{
|
||||
{Type: "sale", Amount: def.amount / 2, Quantity: 1, ProductID: int64(99000000000 + def.postingIdx + 100), OfferID: oid1},
|
||||
{Type: "sale", Amount: def.amount / 2, Quantity: 1, ProductID: int64(99000000000 + def.postingIdx + 101), OfferID: oid1},
|
||||
}
|
||||
}
|
||||
result[idx] = tx
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func GetPayoutsFixtures() []Payout {
|
||||
return []Payout{
|
||||
{
|
||||
PayoutID: 99001,
|
||||
Type: "payout",
|
||||
Status: "completed",
|
||||
Amount: 15000.00,
|
||||
CurrencyCode: "RUB",
|
||||
CreatedAt: "2024-01-15T00:00:00Z",
|
||||
OperationsCount: 10,
|
||||
},
|
||||
{
|
||||
PayoutID: 99002,
|
||||
Type: "payout",
|
||||
Status: "completed",
|
||||
Amount: 22500.00,
|
||||
CurrencyCode: "RUB",
|
||||
CreatedAt: "2024-01-22T00:00:00Z",
|
||||
OperationsCount: 15,
|
||||
},
|
||||
payoutDefs := []struct {
|
||||
companyIdx int
|
||||
amount float64
|
||||
opsCount int
|
||||
createdAt string
|
||||
}{
|
||||
{0, 15000.00, 10, "2024-01-15T00:00:00Z"},
|
||||
{0, 22500.00, 15, "2024-01-22T00:00:00Z"},
|
||||
{0, 18000.00, 12, "2024-02-15T00:00:00Z"},
|
||||
{0, 32000.00, 20, "2024-03-15T00:00:00Z"},
|
||||
{1, 12000.00, 8, "2024-01-20T00:00:00Z"},
|
||||
{1, 19500.00, 13, "2024-01-27T00:00:00Z"},
|
||||
{1, 25000.00, 18, "2024-02-20T00:00:00Z"},
|
||||
{1, 28000.00, 19, "2024-03-20T00:00:00Z"},
|
||||
{2, 11000.00, 7, "2024-01-18T00:00:00Z"},
|
||||
{2, 17000.00, 11, "2024-01-25T00:00:00Z"},
|
||||
{2, 21000.00, 14, "2024-02-18T00:00:00Z"},
|
||||
{2, 29000.00, 21, "2024-03-18T00:00:00Z"},
|
||||
{0, 14500.00, 9, "2024-04-15T00:00:00Z"},
|
||||
{1, 16500.00, 11, "2024-04-20T00:00:00Z"},
|
||||
{2, 13500.00, 8, "2024-04-18T00:00:00Z"},
|
||||
{0, 26000.00, 17, "2024-05-15T00:00:00Z"},
|
||||
{1, 31000.00, 22, "2024-05-20T00:00:00Z"},
|
||||
{2, 24000.00, 16, "2024-05-18T00:00:00Z"},
|
||||
{0, 19000.00, 13, "2024-06-15T00:00:00Z"},
|
||||
{1, 22000.00, 15, "2024-06-20T00:00:00Z"},
|
||||
}
|
||||
|
||||
result := make([]Payout, len(payoutDefs))
|
||||
for idx, def := range payoutDefs {
|
||||
result[idx] = Payout{
|
||||
PayoutID: int64(99001 + idx),
|
||||
Type: "payout",
|
||||
Status: "completed",
|
||||
Amount: def.amount,
|
||||
CurrencyCode: "RUB",
|
||||
CreatedAt: def.createdAt,
|
||||
OperationsCount: def.opsCount,
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
419
internal/handlers/handlers_test.go
Normal file
419
internal/handlers/handlers_test.go
Normal file
@ -0,0 +1,419 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func doReq(t *testing.T, h http.HandlerFunc, method, path string, body any) *httptest.ResponseRecorder {
|
||||
t.Helper()
|
||||
var b []byte
|
||||
if body != nil {
|
||||
b, _ = json.Marshal(body)
|
||||
}
|
||||
req := httptest.NewRequest(method, path, bytes.NewReader(b))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
h(w, req)
|
||||
return w
|
||||
}
|
||||
|
||||
func assertStatus(t *testing.T, w *httptest.ResponseRecorder, expected int) {
|
||||
t.Helper()
|
||||
if w.Code != expected {
|
||||
t.Errorf("expected status %d, got %d: %s", expected, w.Code, w.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func assertJSON(t *testing.T, w *httptest.ResponseRecorder, key string, expected any) {
|
||||
t.Helper()
|
||||
var m map[string]any
|
||||
json.Unmarshal(w.Body.Bytes(), &m)
|
||||
if m[key] == nil {
|
||||
t.Errorf("expected key %q in response, got %s", key, w.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestProductStocks(t *testing.T) {
|
||||
w := doReq(t, ProductStocks, "POST", "/v3/product/info/stocks", map[string]any{
|
||||
"filter": map[string]any{},
|
||||
})
|
||||
assertStatus(t, w, 200)
|
||||
assertJSON(t, w, "items", nil)
|
||||
|
||||
w = doReq(t, ProductStocks, "GET", "/v3/product/info/stocks", nil)
|
||||
assertStatus(t, w, 405)
|
||||
}
|
||||
|
||||
func TestProductStocks_FilterOfferID(t *testing.T) {
|
||||
w := doReq(t, ProductStocks, "POST", "/v3/product/info/stocks", map[string]any{
|
||||
"filter": map[string]any{"offer_id": []string{"TG-0001"}},
|
||||
})
|
||||
assertStatus(t, w, 200)
|
||||
|
||||
var resp struct {
|
||||
Items []StockItem `json:"items"`
|
||||
Total int `json:"total"`
|
||||
}
|
||||
json.NewDecoder(w.Body).Decode(&resp)
|
||||
if resp.Total != 1 || resp.Items[0].OfferID != "TG-0001" {
|
||||
t.Errorf("filter by offer_id: total=%d item=%v", resp.Total, resp.Items)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProductStocks_Limit(t *testing.T) {
|
||||
w := doReq(t, ProductStocks, "POST", "/v3/product/info/stocks", map[string]any{
|
||||
"limit": 2,
|
||||
})
|
||||
assertStatus(t, w, 200)
|
||||
|
||||
var resp struct {
|
||||
Items []StockItem `json:"items"`
|
||||
}
|
||||
json.NewDecoder(w.Body).Decode(&resp)
|
||||
if len(resp.Items) != 2 {
|
||||
t.Errorf("limit: expected 2, got %d", len(resp.Items))
|
||||
}
|
||||
}
|
||||
|
||||
func TestProductInfoList(t *testing.T) {
|
||||
w := doReq(t, ProductInfoList, "POST", "/v3/product/info/list", map[string]any{
|
||||
"filter": map[string]any{},
|
||||
})
|
||||
assertStatus(t, w, 200)
|
||||
assertJSON(t, w, "items", nil)
|
||||
}
|
||||
|
||||
func TestProductInfoList_FilterProductID(t *testing.T) {
|
||||
w := doReq(t, ProductInfoList, "POST", "/v3/product/info/list", map[string]any{
|
||||
"filter": map[string]any{"product_id": []int64{99000000001}},
|
||||
})
|
||||
assertStatus(t, w, 200)
|
||||
var resp struct {
|
||||
Items []ProductInfoItem `json:"items"`
|
||||
}
|
||||
json.NewDecoder(w.Body).Decode(&resp)
|
||||
if len(resp.Items) != 1 || resp.Items[0].ProductID != 99000000001 {
|
||||
t.Errorf("filter by product_id: got %v", resp.Items)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProductList(t *testing.T) {
|
||||
w := doReq(t, ProductList, "GET", "/v1/product/list", nil)
|
||||
assertStatus(t, w, 200)
|
||||
assertJSON(t, w, "total", nil)
|
||||
|
||||
w = doReq(t, ProductList, "POST", "/v1/product/list", nil)
|
||||
assertStatus(t, w, 405)
|
||||
}
|
||||
|
||||
func TestImportPrices(t *testing.T) {
|
||||
w := doReq(t, ImportPrices, "POST", "/v1/product/import/prices", map[string]any{
|
||||
"items": []map[string]any{
|
||||
{"offer_id": "SKU-1", "price": 100.0},
|
||||
},
|
||||
})
|
||||
assertStatus(t, w, 200)
|
||||
assertJSON(t, w, "task_id", nil)
|
||||
|
||||
w = doReq(t, ImportPrices, "POST", "/v1/product/import/prices", map[string]any{"items": []map[string]any{}})
|
||||
assertStatus(t, w, 400)
|
||||
|
||||
w = doReq(t, ImportPrices, "POST", "/v1/product/import/prices", map[string]any{
|
||||
"items": []map[string]any{{"offer_id": "", "price": 100}},
|
||||
})
|
||||
assertStatus(t, w, 400)
|
||||
}
|
||||
|
||||
func TestProductPrices(t *testing.T) {
|
||||
w := doReq(t, ProductPrices, "POST", "/v4/product/info/prices", map[string]any{
|
||||
"filter": map[string]any{},
|
||||
})
|
||||
assertStatus(t, w, 200)
|
||||
assertJSON(t, w, "total", nil)
|
||||
}
|
||||
|
||||
func TestProductPrices_FilterOfferID(t *testing.T) {
|
||||
w := doReq(t, ProductPrices, "POST", "/v4/product/info/prices", map[string]any{
|
||||
"filter": map[string]any{"offer_id": []string{"TG-0001"}},
|
||||
})
|
||||
assertStatus(t, w, 200)
|
||||
var resp struct {
|
||||
Items []ProductPriceItem `json:"items"`
|
||||
}
|
||||
json.NewDecoder(w.Body).Decode(&resp)
|
||||
if len(resp.Items) != 1 || resp.Items[0].OfferID != "TG-0001" {
|
||||
t.Errorf("filter by offer_id: got %v", resp.Items)
|
||||
}
|
||||
}
|
||||
|
||||
func TestImportProducts(t *testing.T) {
|
||||
w := doReq(t, ImportProducts, "POST", "/v3/product/import", map[string]any{
|
||||
"items": []map[string]any{
|
||||
{"offer_id": "TEST-001", "name": "Test Product", "category_id": 17000000},
|
||||
},
|
||||
})
|
||||
assertStatus(t, w, 200)
|
||||
|
||||
var resp ImportProductsResponse
|
||||
json.NewDecoder(w.Body).Decode(&resp)
|
||||
if resp.Result.TaskID != 990001 {
|
||||
t.Errorf("expected task_id 990001, got %d", resp.Result.TaskID)
|
||||
}
|
||||
|
||||
w = doReq(t, ImportProducts, "POST", "/v3/product/import", map[string]any{
|
||||
"items": []map[string]any{},
|
||||
})
|
||||
assertStatus(t, w, 400)
|
||||
|
||||
w = doReq(t, ImportProducts, "POST", "/v3/product/import", map[string]any{
|
||||
"items": []map[string]any{{"offer_id": ""}},
|
||||
})
|
||||
assertStatus(t, w, 400)
|
||||
}
|
||||
|
||||
func TestProductDescriptions(t *testing.T) {
|
||||
w := doReq(t, ProductDescriptions, "POST", "/v1/product/info/description", map[string]any{
|
||||
"filter": map[string]any{"offer_id": []string{"TG-0001"}},
|
||||
})
|
||||
assertStatus(t, w, 200)
|
||||
|
||||
var resp ProductDescriptionResponse
|
||||
json.NewDecoder(w.Body).Decode(&resp)
|
||||
if len(resp.Result) != 1 || resp.Result[0].OfferID != "TG-0001" {
|
||||
t.Errorf("expected 1 result for TG-0001, got %+v", resp.Result)
|
||||
}
|
||||
if resp.Result[0].State != "moderated" {
|
||||
t.Errorf("expected state moderated, got %s", resp.Result[0].State)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCategoryTree(t *testing.T) {
|
||||
w := doReq(t, CategoryTree, "POST", "/v2/category/tree", map[string]any{"language": "RU"})
|
||||
assertStatus(t, w, 200)
|
||||
|
||||
var resp CategoryTreeResponse
|
||||
json.NewDecoder(w.Body).Decode(&resp)
|
||||
if len(resp.Result) != 3 {
|
||||
t.Errorf("expected 3 root categories, got %d", len(resp.Result))
|
||||
}
|
||||
if resp.Result[0].Title != "Электроника" {
|
||||
t.Errorf("expected Электроника, got %s", resp.Result[0].Title)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCategoryAttributes(t *testing.T) {
|
||||
w := doReq(t, CategoryAttributes, "POST", "/v3/category/attribute", map[string]any{
|
||||
"category_id": []int64{17036136},
|
||||
})
|
||||
assertStatus(t, w, 200)
|
||||
|
||||
var resp CategoryAttributesResponse
|
||||
json.NewDecoder(w.Body).Decode(&resp)
|
||||
if len(resp.Result) != 6 {
|
||||
t.Errorf("expected 6 attributes, got %d", len(resp.Result))
|
||||
}
|
||||
}
|
||||
|
||||
func TestCategoryAttributeValues(t *testing.T) {
|
||||
w := doReq(t, CategoryAttributeValues, "POST", "/v3/category/attribute/values", map[string]any{
|
||||
"attribute_id": 85,
|
||||
"category_id": 17036136,
|
||||
})
|
||||
assertStatus(t, w, 200)
|
||||
|
||||
var resp CategoryAttributeValuesResponse
|
||||
json.NewDecoder(w.Body).Decode(&resp)
|
||||
if len(resp.Result) != 4 {
|
||||
t.Errorf("expected 4 values, got %d", len(resp.Result))
|
||||
}
|
||||
}
|
||||
|
||||
func TestPostingFBSList(t *testing.T) {
|
||||
w := doReq(t, PostingFBSList, "POST", "/v2/posting/fbs/list", map[string]any{})
|
||||
assertStatus(t, w, 200)
|
||||
assertJSON(t, w, "postings", nil)
|
||||
}
|
||||
|
||||
func TestPostingFBSList_FilterStatus(t *testing.T) {
|
||||
w := doReq(t, PostingFBSList, "POST", "/v2/posting/fbs/list", map[string]any{
|
||||
"filter": map[string]any{"status": "delivered"},
|
||||
})
|
||||
assertStatus(t, w, 200)
|
||||
var resp struct {
|
||||
Postings []Posting `json:"postings"`
|
||||
}
|
||||
json.NewDecoder(w.Body).Decode(&resp)
|
||||
for _, p := range resp.Postings {
|
||||
if p.Status != "delivered" {
|
||||
t.Errorf("filter status: expected delivered, got %s", p.Status)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPostingFBSList_FilterSince(t *testing.T) {
|
||||
w := doReq(t, PostingFBSList, "POST", "/v2/posting/fbs/list", map[string]any{
|
||||
"filter": map[string]any{"since": "2024-01-16T00:00:00Z"},
|
||||
})
|
||||
assertStatus(t, w, 200)
|
||||
var resp struct {
|
||||
Postings []Posting `json:"postings"`
|
||||
}
|
||||
json.NewDecoder(w.Body).Decode(&resp)
|
||||
if len(resp.Postings) < 3 {
|
||||
t.Errorf("filter since: expected at least 3 postings from 2024-01-16, got %d", len(resp.Postings))
|
||||
}
|
||||
}
|
||||
|
||||
func TestPostingFBSList_WithAnalytics(t *testing.T) {
|
||||
w := doReq(t, PostingFBSList, "POST", "/v2/posting/fbs/list", map[string]any{
|
||||
"with": map[string]any{"analytics_data": true},
|
||||
})
|
||||
assertStatus(t, w, 200)
|
||||
var resp struct {
|
||||
Postings []Posting `json:"postings"`
|
||||
}
|
||||
json.NewDecoder(w.Body).Decode(&resp)
|
||||
if len(resp.Postings) > 0 && resp.Postings[0].AnalyticsData == nil {
|
||||
t.Errorf("expected analytics_data, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPostingFBSList_Asc(t *testing.T) {
|
||||
w := doReq(t, PostingFBSList, "POST", "/v2/posting/fbs/list", map[string]any{
|
||||
"dir": "asc",
|
||||
})
|
||||
assertStatus(t, w, 200)
|
||||
}
|
||||
|
||||
func TestPostingFBSGet(t *testing.T) {
|
||||
w := doReq(t, PostingFBSGet, "POST", "/v2/posting/fbs/get", map[string]any{
|
||||
"posting_number": "TG-POST-001",
|
||||
})
|
||||
assertStatus(t, w, 200)
|
||||
assertJSON(t, w, "posting", nil)
|
||||
|
||||
w = doReq(t, PostingFBSGet, "POST", "/v2/posting/fbs/get", map[string]any{
|
||||
"posting_number": "NON-EXISTENT",
|
||||
})
|
||||
assertStatus(t, w, 404)
|
||||
}
|
||||
|
||||
func TestCancelPosting(t *testing.T) {
|
||||
w := doReq(t, CancelPosting, "POST", "/v1/posting/fbs/cancel", map[string]any{
|
||||
"posting_number": "TG-POST-001",
|
||||
})
|
||||
assertStatus(t, w, 200)
|
||||
|
||||
w = doReq(t, CancelPosting, "POST", "/v1/posting/fbs/cancel", map[string]any{
|
||||
"posting_number": "",
|
||||
})
|
||||
assertStatus(t, w, 400)
|
||||
|
||||
w = doReq(t, CancelPosting, "POST", "/v1/posting/fbs/cancel", map[string]any{
|
||||
"posting_number": "NON-EXISTENT",
|
||||
})
|
||||
assertStatus(t, w, 404)
|
||||
}
|
||||
|
||||
func TestFinanceTransactionList(t *testing.T) {
|
||||
w := doReq(t, FinanceTransactionList, "POST", "/v1/finance/transaction/list", map[string]any{})
|
||||
assertStatus(t, w, 200)
|
||||
assertJSON(t, w, "transactions", nil)
|
||||
}
|
||||
|
||||
func TestFinanceTransactionList_FilterType(t *testing.T) {
|
||||
w := doReq(t, FinanceTransactionList, "POST", "/v1/finance/transaction/list", map[string]any{
|
||||
"filter": map[string]any{"transaction_type": "commission"},
|
||||
})
|
||||
assertStatus(t, w, 200)
|
||||
var resp struct {
|
||||
Transactions []Transaction `json:"transactions"`
|
||||
}
|
||||
json.NewDecoder(w.Body).Decode(&resp)
|
||||
if len(resp.Transactions) < 1 || resp.Transactions[0].TransactionType != "commission" {
|
||||
t.Errorf("expected at least 1 commission transaction, got %d", len(resp.Transactions))
|
||||
}
|
||||
}
|
||||
|
||||
func TestFinancePayoutList(t *testing.T) {
|
||||
w := doReq(t, FinancePayoutList, "POST", "/v1/finance/payout/list", map[string]any{})
|
||||
assertStatus(t, w, 200)
|
||||
assertJSON(t, w, "payouts", nil)
|
||||
}
|
||||
|
||||
func TestFinancePayoutList_FilterSince(t *testing.T) {
|
||||
w := doReq(t, FinancePayoutList, "POST", "/v1/finance/payout/list", map[string]any{
|
||||
"filter": map[string]any{"since": "2024-01-20T00:00:00Z"},
|
||||
})
|
||||
assertStatus(t, w, 200)
|
||||
var resp struct {
|
||||
Payouts []Payout `json:"payouts"`
|
||||
}
|
||||
json.NewDecoder(w.Body).Decode(&resp)
|
||||
if len(resp.Payouts) < 1 {
|
||||
t.Errorf("filter since: expected at least 1 payout, got %d", len(resp.Payouts))
|
||||
}
|
||||
}
|
||||
|
||||
func TestReturnsList(t *testing.T) {
|
||||
w := doReq(t, ReturnsList, "POST", "/v1/returns/list", map[string]any{})
|
||||
assertStatus(t, w, 200)
|
||||
assertJSON(t, w, "result", nil)
|
||||
}
|
||||
|
||||
func TestReturnsList_FilterStatus(t *testing.T) {
|
||||
w := doReq(t, ReturnsList, "POST", "/v1/returns/list", map[string]any{
|
||||
"filter": map[string]any{"status": "pending"},
|
||||
})
|
||||
assertStatus(t, w, 200)
|
||||
var resp struct {
|
||||
Result []Return `json:"result"`
|
||||
}
|
||||
json.NewDecoder(w.Body).Decode(&resp)
|
||||
if len(resp.Result) < 1 || resp.Result[0].Status != "pending" {
|
||||
t.Errorf("expected at least 1 pending return, got %d", len(resp.Result))
|
||||
}
|
||||
}
|
||||
|
||||
func TestReturnsList_FilterReturnID(t *testing.T) {
|
||||
w := doReq(t, ReturnsList, "POST", "/v1/returns/list", map[string]any{
|
||||
"filter": map[string]any{"return_id": 99000001},
|
||||
})
|
||||
assertStatus(t, w, 200)
|
||||
var resp struct {
|
||||
Result []Return `json:"result"`
|
||||
}
|
||||
json.NewDecoder(w.Body).Decode(&resp)
|
||||
if len(resp.Result) != 1 || resp.Result[0].ReturnID != 99000001 {
|
||||
t.Errorf("expected return 99000001, got %d results", len(resp.Result))
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnalyticsData(t *testing.T) {
|
||||
w := doReq(t, AnalyticsData, "POST", "/v1/analytics/data", map[string]any{})
|
||||
assertStatus(t, w, 200)
|
||||
assertJSON(t, w, "data", nil)
|
||||
}
|
||||
|
||||
func TestTurnoverStocks(t *testing.T) {
|
||||
w := doReq(t, TurnoverStocks, "POST", "/v1/analytics/turnover/stocks", map[string]any{})
|
||||
assertStatus(t, w, 200)
|
||||
assertJSON(t, w, "items", nil)
|
||||
}
|
||||
|
||||
func TestProductAttributes(t *testing.T) {
|
||||
w := doReq(t, ProductAttributes, "POST", "/v1/product/info/attributes", map[string]any{
|
||||
"filter": map[string]any{"offer_id": []string{"TG-0001"}},
|
||||
})
|
||||
assertStatus(t, w, 200)
|
||||
|
||||
var resp ProductAttributesResponse
|
||||
json.NewDecoder(w.Body).Decode(&resp)
|
||||
if len(resp.Result) != 1 {
|
||||
t.Errorf("expected 1 result, got %d", len(resp.Result))
|
||||
}
|
||||
}
|
||||
@ -6,9 +6,8 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// PostingFBSListRequest - POST /v2/posting/fbs/list
|
||||
type PostingFBSListRequest struct {
|
||||
Dir string `json:"dir"` // "asc" or "desc"
|
||||
Dir string `json:"dir"`
|
||||
Filter struct {
|
||||
Since string `json:"since"`
|
||||
To string `json:"to"`
|
||||
@ -66,7 +65,30 @@ func PostingFBSList(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
postings := GetPostingsFixtures()
|
||||
|
||||
// Filter by status if provided
|
||||
if req.Filter.Since != "" {
|
||||
sinceTs := parseTimeOrZero(req.Filter.Since)
|
||||
filtered := make([]Posting, 0)
|
||||
for _, p := range postings {
|
||||
ts := parseTimeOrZero(p.CreatedAt)
|
||||
if ts >= sinceTs {
|
||||
filtered = append(filtered, p)
|
||||
}
|
||||
}
|
||||
postings = filtered
|
||||
}
|
||||
|
||||
if req.Filter.To != "" {
|
||||
toTs := parseTimeOrZero(req.Filter.To)
|
||||
filtered := make([]Posting, 0)
|
||||
for _, p := range postings {
|
||||
ts := parseTimeOrZero(p.CreatedAt)
|
||||
if ts <= toTs {
|
||||
filtered = append(filtered, p)
|
||||
}
|
||||
}
|
||||
postings = filtered
|
||||
}
|
||||
|
||||
if req.Filter.Status != "" {
|
||||
filtered := make([]Posting, 0)
|
||||
for _, p := range postings {
|
||||
@ -77,7 +99,6 @@ func PostingFBSList(w http.ResponseWriter, r *http.Request) {
|
||||
postings = filtered
|
||||
}
|
||||
|
||||
// Filter by posting_number if provided
|
||||
if len(req.Filter.PostingNumber) > 0 {
|
||||
filtered := make([]Posting, 0)
|
||||
for _, p := range postings {
|
||||
@ -91,7 +112,22 @@ func PostingFBSList(w http.ResponseWriter, r *http.Request) {
|
||||
postings = filtered
|
||||
}
|
||||
|
||||
// Apply limit
|
||||
if req.Dir == "asc" {
|
||||
for i, j := 0, len(postings)-1; i < j; i, j = i+1, j-1 {
|
||||
postings[i], postings[j] = postings[j], postings[i]
|
||||
}
|
||||
}
|
||||
|
||||
if req.With.AnalyticsData {
|
||||
for i := range postings {
|
||||
postings[i].AnalyticsData = map[string]any{
|
||||
"commission": 5.0,
|
||||
"delivery": 150.00,
|
||||
"return": 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(postings) > req.Limit {
|
||||
postings = postings[:req.Limit]
|
||||
}
|
||||
@ -103,7 +139,6 @@ func PostingFBSList(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
// PostingFBSGetRequest - POST /v2/posting/fbs/get
|
||||
type PostingFBSGetRequest struct {
|
||||
PostingNumber string `json:"posting_number"`
|
||||
}
|
||||
@ -134,7 +169,6 @@ func PostingFBSGet(w http.ResponseWriter, r *http.Request) {
|
||||
writeError(w, 404, "posting not found")
|
||||
}
|
||||
|
||||
// CancelPostingRequest - POST /v1/posting/fbs/cancel
|
||||
type CancelPostingRequest struct {
|
||||
PostingNumber string `json:"posting_number"`
|
||||
}
|
||||
@ -161,6 +195,19 @@ func CancelPosting(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
postings := GetPostingsFixtures()
|
||||
found := false
|
||||
for _, p := range postings {
|
||||
if p.PostingNumber == req.PostingNumber {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
writeError(w, 404, "posting not found")
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(CancelPostingResponse{
|
||||
PostingNumber: req.PostingNumber,
|
||||
@ -168,8 +215,6 @@ func CancelPosting(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
// Returns
|
||||
|
||||
type ReturnsListRequest struct {
|
||||
Filter struct {
|
||||
Since string `json:"since"`
|
||||
@ -214,6 +259,50 @@ func ReturnsList(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
returns := GetReturnsFixtures()
|
||||
|
||||
if req.Filter.Since != "" {
|
||||
sinceTs := parseTimeOrZero(req.Filter.Since)
|
||||
filtered := make([]Return, 0)
|
||||
for _, ret := range returns {
|
||||
ts := parseTimeOrZero(ret.CreatedAt)
|
||||
if ts >= sinceTs {
|
||||
filtered = append(filtered, ret)
|
||||
}
|
||||
}
|
||||
returns = filtered
|
||||
}
|
||||
|
||||
if req.Filter.To != "" {
|
||||
toTs := parseTimeOrZero(req.Filter.To)
|
||||
filtered := make([]Return, 0)
|
||||
for _, ret := range returns {
|
||||
ts := parseTimeOrZero(ret.CreatedAt)
|
||||
if ts <= toTs {
|
||||
filtered = append(filtered, ret)
|
||||
}
|
||||
}
|
||||
returns = filtered
|
||||
}
|
||||
|
||||
if req.Filter.Status != "" {
|
||||
filtered := make([]Return, 0)
|
||||
for _, ret := range returns {
|
||||
if ret.Status == req.Filter.Status {
|
||||
filtered = append(filtered, ret)
|
||||
}
|
||||
}
|
||||
returns = filtered
|
||||
}
|
||||
|
||||
if req.Filter.ReturnID > 0 {
|
||||
filtered := make([]Return, 0)
|
||||
for _, ret := range returns {
|
||||
if ret.ReturnID == req.Filter.ReturnID {
|
||||
filtered = append(filtered, ret)
|
||||
}
|
||||
}
|
||||
returns = filtered
|
||||
}
|
||||
|
||||
if req.Limit == 0 {
|
||||
req.Limit = 100
|
||||
}
|
||||
@ -229,7 +318,14 @@ func ReturnsList(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
func parseTimeOrZero(s string) int64 {
|
||||
t, err := time.Parse(time.RFC3339, s)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return t.Unix()
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Ensure timestamps are consistent
|
||||
_ = time.RFC3339
|
||||
}
|
||||
|
||||
@ -2,17 +2,15 @@ package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Common request/response types
|
||||
type ErrorResponse struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// ProductStocksRequest - POST /v3/product/info/stocks
|
||||
type ProductStocksRequest struct {
|
||||
Filter struct {
|
||||
OfferIDs []string `json:"offer_id"`
|
||||
@ -45,7 +43,6 @@ func ProductStocks(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
items := GetStocksFixtures()
|
||||
|
||||
// Filter by offer_ids if provided
|
||||
if len(req.Filter.OfferIDs) > 0 {
|
||||
filtered := make([]StockItem, 0)
|
||||
for _, item := range items {
|
||||
@ -59,7 +56,6 @@ func ProductStocks(w http.ResponseWriter, r *http.Request) {
|
||||
items = filtered
|
||||
}
|
||||
|
||||
// Filter by product_ids if provided
|
||||
if len(req.Filter.ProductIDs) > 0 {
|
||||
filtered := make([]StockItem, 0)
|
||||
for _, item := range items {
|
||||
@ -73,7 +69,10 @@ func ProductStocks(w http.ResponseWriter, r *http.Request) {
|
||||
items = filtered
|
||||
}
|
||||
|
||||
// Apply limit
|
||||
if req.Filter.Visibility != "" {
|
||||
_ = req.Filter.Visibility
|
||||
}
|
||||
|
||||
if req.Limit > 0 && len(items) > req.Limit {
|
||||
items = items[:req.Limit]
|
||||
}
|
||||
@ -85,7 +84,6 @@ func ProductStocks(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
// ProductInfoListRequest - POST /v3/product/info/list
|
||||
type ProductInfoListRequest struct {
|
||||
Filter struct {
|
||||
OfferIDs []string `json:"offer_id"`
|
||||
@ -132,6 +130,19 @@ func ProductInfoList(w http.ResponseWriter, r *http.Request) {
|
||||
items = filtered
|
||||
}
|
||||
|
||||
if len(req.Filter.ProductIDs) > 0 {
|
||||
filtered := make([]ProductInfoItem, 0)
|
||||
for _, item := range items {
|
||||
for _, pid := range req.Filter.ProductIDs {
|
||||
if item.ProductID == pid {
|
||||
filtered = append(filtered, item)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
items = filtered
|
||||
}
|
||||
|
||||
if req.Limit > 0 && len(items) > req.Limit {
|
||||
items = items[:req.Limit]
|
||||
}
|
||||
@ -143,7 +154,6 @@ func ProductInfoList(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
// ImportPricesRequest - POST /v1/product/import/prices
|
||||
type ImportPricesRequest struct {
|
||||
Items []struct {
|
||||
OfferID string `json:"offer_id"`
|
||||
@ -175,7 +185,6 @@ func ImportPrices(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// Validate required fields
|
||||
for _, item := range req.Items {
|
||||
if item.OfferID == "" {
|
||||
writeError(w, 400, "offer_id is required for all items")
|
||||
@ -193,7 +202,6 @@ func ImportPrices(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
// ProductPricesRequest - POST /v4/product/info/prices
|
||||
type ProductPricesRequest struct {
|
||||
Filter struct {
|
||||
OfferIDs []string `json:"offer_id"`
|
||||
@ -238,6 +246,23 @@ func ProductPrices(w http.ResponseWriter, r *http.Request) {
|
||||
items = filtered
|
||||
}
|
||||
|
||||
if len(req.Filter.ProductIDs) > 0 {
|
||||
filtered := make([]ProductPriceItem, 0)
|
||||
for _, item := range items {
|
||||
for _, pid := range req.Filter.ProductIDs {
|
||||
if item.ProductID == pid {
|
||||
filtered = append(filtered, item)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
items = filtered
|
||||
}
|
||||
|
||||
if req.Limit > 0 && len(items) > req.Limit {
|
||||
items = items[:req.Limit]
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]any{
|
||||
"items": items,
|
||||
@ -245,17 +270,205 @@ func ProductPrices(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
// ProductListRequest - GET /v1/product/list
|
||||
func ProductList(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
http.Error(w, "method not allowed", 405)
|
||||
return
|
||||
}
|
||||
|
||||
var req ProductListRequest
|
||||
_ = json.NewDecoder(r.Body).Decode(&req)
|
||||
|
||||
items := GetProductsFixtures()
|
||||
|
||||
if req.Page > 0 {
|
||||
offset := (req.Page - 1) * 10
|
||||
if offset < len(items) {
|
||||
items = items[offset:]
|
||||
if len(items) > 10 {
|
||||
items = items[:10]
|
||||
}
|
||||
} else {
|
||||
items = nil
|
||||
}
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]any{
|
||||
"items": items,
|
||||
"total": len(GetProductsFixtures()),
|
||||
})
|
||||
}
|
||||
|
||||
type ProductListRequest struct {
|
||||
Page int `json:"page"`
|
||||
}
|
||||
|
||||
func ProductList(w http.ResponseWriter, r *http.Request) {
|
||||
items := GetProductsFixtures()
|
||||
type ImportProductsRequest struct {
|
||||
Items []ImportProductItem `json:"items"`
|
||||
}
|
||||
|
||||
type ImportProductItem struct {
|
||||
OfferID string `json:"offer_id"`
|
||||
Name string `json:"name"`
|
||||
Barcode string `json:"barcode,omitempty"`
|
||||
CategoryID int64 `json:"category_id"`
|
||||
Price string `json:"price,omitempty"`
|
||||
OldPrice string `json:"old_price,omitempty"`
|
||||
PremiumPrice string `json:"premium_price,omitempty"`
|
||||
Vat string `json:"vat"`
|
||||
Images []string `json:"images,omitempty"`
|
||||
Depth int `json:"depth,omitempty"`
|
||||
Height int `json:"height,omitempty"`
|
||||
Width int `json:"width,omitempty"`
|
||||
DimensionUnit string `json:"dimension_unit,omitempty"`
|
||||
Weight int `json:"weight,omitempty"`
|
||||
WeightUnit string `json:"weight_unit,omitempty"`
|
||||
Attributes []ImportAttribute `json:"attributes,omitempty"`
|
||||
}
|
||||
|
||||
type ImportAttribute struct {
|
||||
ID int64 `json:"id"`
|
||||
Values []ImportAttrValue `json:"values"`
|
||||
}
|
||||
|
||||
type ImportAttrValue struct {
|
||||
Value string `json:"value,omitempty"`
|
||||
ComplexID int64 `json:"complex_id,omitempty"`
|
||||
Values []ImportAttrValue `json:"values,omitempty"`
|
||||
}
|
||||
|
||||
type ImportProductsResponse struct {
|
||||
Result struct {
|
||||
TaskID int64 `json:"task_id"`
|
||||
} `json:"result"`
|
||||
}
|
||||
|
||||
func ImportProducts(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "method not allowed", 405)
|
||||
return
|
||||
}
|
||||
|
||||
var req ImportProductsRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeError(w, 400, "invalid json: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if len(req.Items) == 0 {
|
||||
writeError(w, 400, "items is required and must not be empty")
|
||||
return
|
||||
}
|
||||
|
||||
for i, item := range req.Items {
|
||||
if item.OfferID == "" {
|
||||
writeError(w, 400, fmt.Sprintf("items[%d].offer_id is required", i))
|
||||
return
|
||||
}
|
||||
if item.Name == "" {
|
||||
writeError(w, 400, fmt.Sprintf("items[%d].name is required", i))
|
||||
return
|
||||
}
|
||||
if item.CategoryID == 0 {
|
||||
writeError(w, 400, fmt.Sprintf("items[%d].category_id is required", i))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]any{
|
||||
"items": items,
|
||||
"total": len(items),
|
||||
json.NewEncoder(w).Encode(ImportProductsResponse{
|
||||
Result: struct {
|
||||
TaskID int64 `json:"task_id"`
|
||||
}{
|
||||
TaskID: 990001,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
type ProductDescriptionRequest struct {
|
||||
Filter struct {
|
||||
OfferIDs []string `json:"offer_id"`
|
||||
ProductIDs []int64 `json:"product_id"`
|
||||
} `json:"filter"`
|
||||
}
|
||||
|
||||
type ProductDescriptionItem struct {
|
||||
OfferID string `json:"offer_id"`
|
||||
ProductID int64 `json:"product_id"`
|
||||
CategoryID int64 `json:"category_id"`
|
||||
Name string `json:"name"`
|
||||
Barcode string `json:"barcode"`
|
||||
Status string `json:"status"`
|
||||
State string `json:"state"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
IsArchive bool `json:"is_archive"`
|
||||
}
|
||||
|
||||
type ProductDescriptionResponse struct {
|
||||
Result []ProductDescriptionItem `json:"result"`
|
||||
}
|
||||
|
||||
func ProductDescriptions(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "method not allowed", 405)
|
||||
return
|
||||
}
|
||||
|
||||
var req ProductDescriptionRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeError(w, 400, "invalid json")
|
||||
return
|
||||
}
|
||||
|
||||
allProducts := GetProductsFixtures()
|
||||
descs := make([]ProductDescriptionItem, 0)
|
||||
seen := make(map[string]bool)
|
||||
|
||||
for _, oid := range req.Filter.OfferIDs {
|
||||
for _, p := range allProducts {
|
||||
if p.OfferID == oid && !seen[oid] {
|
||||
seen[oid] = true
|
||||
descs = append(descs, ProductDescriptionItem{
|
||||
OfferID: p.OfferID,
|
||||
ProductID: p.ProductID,
|
||||
CategoryID: p.CategoryID,
|
||||
Name: p.Name,
|
||||
Barcode: p.Barcode,
|
||||
Status: "processed",
|
||||
State: "moderated",
|
||||
CreatedAt: "2024-01-01T00:00:00Z",
|
||||
IsArchive: false,
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, pid := range req.Filter.ProductIDs {
|
||||
for _, p := range allProducts {
|
||||
poid := p.OfferID
|
||||
if p.ProductID == pid && !seen[poid] {
|
||||
seen[poid] = true
|
||||
descs = append(descs, ProductDescriptionItem{
|
||||
OfferID: p.OfferID,
|
||||
ProductID: p.ProductID,
|
||||
CategoryID: p.CategoryID,
|
||||
Name: p.Name,
|
||||
Barcode: p.Barcode,
|
||||
Status: "processed",
|
||||
State: "moderated",
|
||||
CreatedAt: "2024-01-01T00:00:00Z",
|
||||
IsArchive: false,
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(ProductDescriptionResponse{
|
||||
Result: descs,
|
||||
})
|
||||
}
|
||||
|
||||
@ -264,19 +477,3 @@ func writeError(w http.ResponseWriter, code int, msg string) {
|
||||
w.WriteHeader(code)
|
||||
json.NewEncoder(w).Encode(ErrorResponse{Code: code, Message: msg})
|
||||
}
|
||||
|
||||
// Helper to parse offer_ids from query string
|
||||
func parseOfferIDs(query string) []string {
|
||||
if query == "" {
|
||||
return nil
|
||||
}
|
||||
parts := strings.Split(query, ",")
|
||||
result := make([]string, 0, len(parts))
|
||||
for _, p := range parts {
|
||||
p = strings.TrimSpace(p)
|
||||
if p != "" {
|
||||
result = append(result, p)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user