129 lines
3.6 KiB
Go
129 lines
3.6 KiB
Go
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
|
||
|
|
}
|