package tasks import ( "errors" "math" "math/big" "sort" "strings" "time" _ "time/tzdata" ) const maxStartItems = 100 var ( ErrStartConflict = errors.New("purchase start conflicts with current task state") ErrInvalidStart = errors.New("invalid purchase start request") ) type StartPolicy struct { AuthorizationTTL time.Duration MaxQuantity int MaxTotalPrice string } type StartItem struct { TaskID string `json:"task_id"` ExpectedTaskVersion int `json:"expected_task_version"` } type StartCommand struct { StartKey string `json:"start_key"` Tasks []StartItem `json:"tasks"` } type AuthorizedTask struct { TaskID string `json:"task_id"` TaskVersion int `json:"task_version"` AuthorizationID string `json:"authorization_id"` ExpiresAt time.Time `json:"expires_at"` } type StartResult struct { StartKey string `json:"start_key"` AuthorizedCount int `json:"authorized_count"` Tasks []AuthorizedTask `json:"tasks"` PaymentAutomated bool `json:"payment_automated"` } type TaskFilter struct{ Keyword, Status, CreatedFrom, CreatedTo string } type TaskRow struct { ID, Title, GoodsID, SKUColor, SKUSize, MaxTotalPrice, Status string Quantity, Version int CreatedAt time.Time } func normalizeCents(value string) (string, *big.Int, bool) { if value == "" || strings.TrimSpace(value) != value { return "", nil, false } parts := strings.Split(value, ".") if len(parts) != 2 || len(parts[0]) == 0 || len(parts[1]) != 2 || (len(parts[0]) > 1 && parts[0][0] == '0') { return "", nil, false } for _, part := range parts { for _, ch := range part { if ch < '0' || ch > '9' { return "", nil, false } } } cents := new(big.Int) if _, ok := cents.SetString(parts[0]+parts[1], 10); !ok || cents.Sign() <= 0 { return "", nil, false } return value, cents, true } func startItems(command StartCommand) ([]StartItem, error) { if !validUUID(command.StartKey) || len(command.Tasks) == 0 || len(command.Tasks) > maxStartItems { return nil, ErrInvalidStart } items := append([]StartItem(nil), command.Tasks...) sort.Slice(items, func(i, j int) bool { return items[i].TaskID < items[j].TaskID }) for i, item := range items { if !validUUID(item.TaskID) || item.ExpectedTaskVersion <= 0 || item.ExpectedTaskVersion == math.MaxInt || (i > 0 && item.TaskID == items[i-1].TaskID) { return nil, ErrInvalidStart } } return items, nil } func validTaskStatus(value string) bool { if value == "" { return true } for _, status := range []string{"DRAFT", "PENDING", "CLAIMED", "ORDERING", "NEEDS_MANUAL", "WAITING_PAYMENT", "RECONCILIATION_REQUIRED", "SUCCEEDED", "FAILED", "CANCELED"} { if value == status { return true } } return false } func ShanghaiRange(from, to string) (time.Time, time.Time, error) { if from == "" && to == "" { return time.Time{}, time.Time{}, nil } location, err := time.LoadLocation("Asia/Shanghai") if err != nil { return time.Time{}, time.Time{}, err } parse := func(value string) (time.Time, error) { return time.ParseInLocation("2006-01-02", value, location) } var start, end time.Time if from != "" { start, err = parse(from) if err != nil { return time.Time{}, time.Time{}, ErrInvalidStart } start = start.UTC() } if to != "" { end, err = parse(to) if err != nil { return time.Time{}, time.Time{}, ErrInvalidStart } end = end.AddDate(0, 0, 1).UTC() } if !start.IsZero() && !end.IsZero() && !start.Before(end) { return time.Time{}, time.Time{}, ErrInvalidStart } return start, end, nil }