Files
ozon-mock/internal/handlers/finance.go
Rinat 50dc046369
Some checks failed
deploy / deploy (push) Failing after 2s
test / test (push) Failing after 1s
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
2026-07-17 14:38:53 +03:00

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},
},
})
}