- 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
266 lines
6.5 KiB
Go
266 lines
6.5 KiB
Go
package handlers
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"time"
|
|
)
|
|
|
|
type FinanceTransactionListRequest struct {
|
|
Filter struct {
|
|
Since string `json:"since"`
|
|
To string `json:"to"`
|
|
TransactionType string `json:"transaction_type"`
|
|
PostingNumber []string `json:"posting_number"`
|
|
} `json:"filter"`
|
|
Limit int `json:"limit"`
|
|
}
|
|
|
|
type Transaction struct {
|
|
TransactionType string `json:"transaction_type"`
|
|
Amount float64 `json:"amount"`
|
|
CurrencyCode string `json:"currency_code"`
|
|
PostingNumber string `json:"posting_number,omitempty"`
|
|
OrderID int64 `json:"order_id,omitempty"`
|
|
Items []TransactionItem `json:"items,omitempty"`
|
|
CreatedAt string `json:"created_at"`
|
|
}
|
|
|
|
type TransactionItem struct {
|
|
Type string `json:"type"`
|
|
Amount float64 `json:"amount"`
|
|
Quantity int `json:"quantity,omitempty"`
|
|
ProductID int64 `json:"product_id,omitempty"`
|
|
OfferID string `json:"offer_id,omitempty"`
|
|
}
|
|
|
|
func FinanceTransactionList(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
http.Error(w, "method not allowed", 405)
|
|
return
|
|
}
|
|
|
|
var req FinanceTransactionListRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
writeError(w, 400, "invalid json")
|
|
return
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
if len(transactions) > req.Limit {
|
|
transactions = transactions[:req.Limit]
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]any{
|
|
"transactions": transactions,
|
|
"total": len(transactions),
|
|
})
|
|
}
|
|
|
|
type FinancePayoutListRequest struct {
|
|
Filter struct {
|
|
Since string `json:"since"`
|
|
To string `json:"to"`
|
|
} `json:"filter"`
|
|
Limit int `json:"limit"`
|
|
}
|
|
|
|
type Payout struct {
|
|
PayoutID int64 `json:"payout_id"`
|
|
Type string `json:"type"`
|
|
Status string `json:"status"`
|
|
Amount float64 `json:"amount"`
|
|
CurrencyCode string `json:"currency_code"`
|
|
CreatedAt string `json:"created_at"`
|
|
OperationsCount int `json:"operations_count"`
|
|
}
|
|
|
|
func FinancePayoutList(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
http.Error(w, "method not allowed", 405)
|
|
return
|
|
}
|
|
|
|
var req FinancePayoutListRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
writeError(w, 400, "invalid json")
|
|
return
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
if len(payouts) > req.Limit {
|
|
payouts = payouts[:req.Limit]
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]any{
|
|
"payouts": payouts,
|
|
"total": len(payouts),
|
|
})
|
|
}
|
|
|
|
type AnalyticsDataRequest struct {
|
|
DateFrom string `json:"date_from"`
|
|
DateTo string `json:"date_to"`
|
|
Metrics []string `json:"metrics"`
|
|
Dimension []string `json:"dimension"`
|
|
}
|
|
|
|
func AnalyticsData(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
http.Error(w, "method not allowed", 405)
|
|
return
|
|
}
|
|
|
|
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,
|
|
})
|
|
}
|
|
|
|
type TurnoverStocksRequest struct {
|
|
Date string `json:"date"`
|
|
SKU []string `json:"sku"`
|
|
}
|
|
|
|
type TurnoverStocksResponse struct {
|
|
Items []struct {
|
|
SKU string `json:"sku"`
|
|
ProductID int64 `json:"product_id"`
|
|
OnHand int `json:"orders_count_canceled"`
|
|
Reserved int `json:"reserved"`
|
|
InWayToCustomer int `json:"in_way_to_customer"`
|
|
InWayFromCustomer int `json:"in_way_from_customer"`
|
|
} `json:"items"`
|
|
}
|
|
|
|
func TurnoverStocks(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
http.Error(w, "method not allowed", 405)
|
|
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 {
|
|
SKU string `json:"sku"`
|
|
ProductID int64 `json:"product_id"`
|
|
OnHand int `json:"orders_count_canceled"`
|
|
Reserved int `json:"reserved"`
|
|
InWayToCustomer int `json:"in_way_to_customer"`
|
|
InWayFromCustomer int `json:"in_way_from_customer"`
|
|
}{
|
|
{SKU: "TEST-SKU-001", ProductID: 99001234001, OnHand: 150, Reserved: 0, InWayToCustomer: 10, InWayFromCustomer: 2},
|
|
{SKU: "TEST-SKU-002", ProductID: 99001234002, OnHand: 75, Reserved: 5, InWayToCustomer: 15, InWayFromCustomer: 1},
|
|
},
|
|
})
|
|
}
|