- 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
332 lines
7.4 KiB
Go
332 lines
7.4 KiB
Go
package handlers
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"time"
|
|
)
|
|
|
|
type PostingFBSListRequest struct {
|
|
Dir string `json:"dir"`
|
|
Filter struct {
|
|
Since string `json:"since"`
|
|
To string `json:"to"`
|
|
Status string `json:"status"`
|
|
PostingNumber []string `json:"posting_number"`
|
|
DeliveryMethod int `json:"delivery_method"`
|
|
} `json:"filter"`
|
|
Limit int `json:"limit"`
|
|
With struct {
|
|
AnalyticsData bool `json:"analytics_data"`
|
|
} `json:"with"`
|
|
}
|
|
|
|
type PostingItem struct {
|
|
ProductID int64 `json:"product_id"`
|
|
OfferID string `json:"offer_id"`
|
|
Name string `json:"name"`
|
|
Quantity int `json:"quantity"`
|
|
Price float64 `json:"price"`
|
|
ItemsCount int `json:"items_count"`
|
|
}
|
|
|
|
type Posting struct {
|
|
PostingNumber string `json:"posting_number"`
|
|
OrderID int64 `json:"order_id"`
|
|
OrderNumber string `json:"order_number"`
|
|
Status string `json:"status"`
|
|
Products []PostingItem `json:"products"`
|
|
AnalyticsData any `json:"analytics_data,omitempty"`
|
|
CreatedAt string `json:"created_at"`
|
|
UpdatedAt string `json:"updated_at"`
|
|
Warehouses []int64 `json:"warehouses"`
|
|
WarehouseID int64 `json:"warehouse_id"`
|
|
OrderType string `json:"order_type"`
|
|
}
|
|
|
|
func PostingFBSList(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
http.Error(w, "method not allowed", 405)
|
|
return
|
|
}
|
|
|
|
var req PostingFBSListRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
writeError(w, 400, "invalid json")
|
|
return
|
|
}
|
|
|
|
if req.Limit == 0 {
|
|
req.Limit = 100
|
|
}
|
|
if req.Limit > 1000 {
|
|
req.Limit = 1000
|
|
}
|
|
|
|
postings := GetPostingsFixtures()
|
|
|
|
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 {
|
|
if p.Status == req.Filter.Status {
|
|
filtered = append(filtered, p)
|
|
}
|
|
}
|
|
postings = filtered
|
|
}
|
|
|
|
if len(req.Filter.PostingNumber) > 0 {
|
|
filtered := make([]Posting, 0)
|
|
for _, p := range postings {
|
|
for _, num := range req.Filter.PostingNumber {
|
|
if p.PostingNumber == num {
|
|
filtered = append(filtered, p)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
postings = filtered
|
|
}
|
|
|
|
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]
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]any{
|
|
"postings": postings,
|
|
"total": len(postings),
|
|
})
|
|
}
|
|
|
|
type PostingFBSGetRequest struct {
|
|
PostingNumber string `json:"posting_number"`
|
|
}
|
|
|
|
func PostingFBSGet(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
http.Error(w, "method not allowed", 405)
|
|
return
|
|
}
|
|
|
|
var req PostingFBSGetRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
writeError(w, 400, "invalid json")
|
|
return
|
|
}
|
|
|
|
postings := GetPostingsFixtures()
|
|
for _, p := range postings {
|
|
if p.PostingNumber == req.PostingNumber {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]any{
|
|
"posting": p,
|
|
})
|
|
return
|
|
}
|
|
}
|
|
|
|
writeError(w, 404, "posting not found")
|
|
}
|
|
|
|
type CancelPostingRequest struct {
|
|
PostingNumber string `json:"posting_number"`
|
|
}
|
|
|
|
type CancelPostingResponse struct {
|
|
PostingNumber string `json:"posting_number"`
|
|
Status string `json:"status"`
|
|
}
|
|
|
|
func CancelPosting(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
http.Error(w, "method not allowed", 405)
|
|
return
|
|
}
|
|
|
|
var req CancelPostingRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
writeError(w, 400, "invalid json")
|
|
return
|
|
}
|
|
|
|
if req.PostingNumber == "" {
|
|
writeError(w, 400, "posting_number is required")
|
|
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,
|
|
Status: "cancelled",
|
|
})
|
|
}
|
|
|
|
type ReturnsListRequest struct {
|
|
Filter struct {
|
|
Since string `json:"since"`
|
|
To string `json:"to"`
|
|
Status string `json:"status"`
|
|
ReturnID int64 `json:"return_id"`
|
|
} `json:"filter"`
|
|
Limit int `json:"limit"`
|
|
}
|
|
|
|
type ReturnItem struct {
|
|
ProductID int64 `json:"product_id"`
|
|
OfferID string `json:"offer_id"`
|
|
Name string `json:"name"`
|
|
Quantity int `json:"quantity"`
|
|
IsOptional bool `json:"is_optional"`
|
|
}
|
|
|
|
type Return struct {
|
|
ReturnID int64 `json:"return_id"`
|
|
PostingNumber string `json:"posting_number"`
|
|
Status string `json:"status"`
|
|
Reason string `json:"reason"`
|
|
ReasonID int64 `json:"reason_id"`
|
|
CreatedAt string `json:"created_at"`
|
|
RejectedAt string `json:"rejected_at,omitempty"`
|
|
DeliveredAt string `json:"delivered_at,omitempty"`
|
|
Items []ReturnItem `json:"items"`
|
|
}
|
|
|
|
func ReturnsList(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
http.Error(w, "method not allowed", 405)
|
|
return
|
|
}
|
|
|
|
var req ReturnsListRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
writeError(w, 400, "invalid json")
|
|
return
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
if len(returns) > req.Limit {
|
|
returns = returns[:req.Limit]
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]any{
|
|
"result": returns,
|
|
"total": len(returns),
|
|
})
|
|
}
|
|
|
|
func parseTimeOrZero(s string) int64 {
|
|
t, err := time.Parse(time.RFC3339, s)
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
return t.Unix()
|
|
}
|
|
|
|
func init() {
|
|
_ = time.RFC3339
|
|
}
|