267 lines
7.5 KiB
Go
267 lines
7.5 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"slices"
|
|
"sync"
|
|
"time"
|
|
|
|
messages "git.tornberg.me/go-cart-actor/pkg/messages"
|
|
"git.tornberg.me/go-cart-actor/pkg/voucher"
|
|
)
|
|
|
|
// Legacy padded [16]byte CartId and its helper methods removed.
|
|
// Unified CartId (uint64 with base62 string form) now defined in cart_id.go.
|
|
|
|
type StockStatus int
|
|
|
|
const (
|
|
OutOfStock StockStatus = 0
|
|
LowStock StockStatus = 1
|
|
InStock StockStatus = 2
|
|
)
|
|
|
|
type CartItem struct {
|
|
Id int `json:"id"`
|
|
ItemId int `json:"itemId,omitempty"`
|
|
ParentId int `json:"parentId,omitempty"`
|
|
Sku string `json:"sku"`
|
|
Name string `json:"name"`
|
|
Price int64 `json:"price"`
|
|
TotalPrice int64 `json:"totalPrice"`
|
|
TotalTax int64 `json:"totalTax"`
|
|
OrgPrice int64 `json:"orgPrice"`
|
|
Stock StockStatus `json:"stock"`
|
|
Quantity int `json:"qty"`
|
|
Tax int `json:"tax"`
|
|
TaxRate int `json:"taxRate"`
|
|
Brand string `json:"brand,omitempty"`
|
|
Category string `json:"category,omitempty"`
|
|
Category2 string `json:"category2,omitempty"`
|
|
Category3 string `json:"category3,omitempty"`
|
|
Category4 string `json:"category4,omitempty"`
|
|
Category5 string `json:"category5,omitempty"`
|
|
Disclaimer string `json:"disclaimer,omitempty"`
|
|
SellerId string `json:"sellerId,omitempty"`
|
|
SellerName string `json:"sellerName,omitempty"`
|
|
ArticleType string `json:"type,omitempty"`
|
|
Image string `json:"image,omitempty"`
|
|
Outlet *string `json:"outlet,omitempty"`
|
|
StoreId *string `json:"storeId,omitempty"`
|
|
}
|
|
|
|
type CartDelivery struct {
|
|
Id int `json:"id"`
|
|
Provider string `json:"provider"`
|
|
Price int64 `json:"price"`
|
|
Items []int `json:"items"`
|
|
PickupPoint *messages.PickupPoint `json:"pickupPoint,omitempty"`
|
|
}
|
|
|
|
type CartGrain struct {
|
|
mu sync.RWMutex
|
|
lastItemId int
|
|
lastDeliveryId int
|
|
lastAccess time.Time
|
|
lastChange time.Time // unix seconds of last successful mutation (replay sets from event ts)
|
|
Id CartId `json:"id"`
|
|
Items []*CartItem `json:"items"`
|
|
TotalPrice int64 `json:"totalPrice"`
|
|
TotalTax int64 `json:"totalTax"`
|
|
TotalDiscount int64 `json:"totalDiscount"`
|
|
Deliveries []*CartDelivery `json:"deliveries,omitempty"`
|
|
Processing bool `json:"processing"`
|
|
PaymentInProgress bool `json:"paymentInProgress"`
|
|
OrderReference string `json:"orderReference,omitempty"`
|
|
PaymentStatus string `json:"paymentStatus,omitempty"`
|
|
Vouchers []*voucher.Voucher `json:"vouchers,omitempty"`
|
|
}
|
|
|
|
func (c *CartGrain) GetId() uint64 {
|
|
return uint64(c.Id)
|
|
}
|
|
|
|
func (c *CartGrain) GetLastChange() time.Time {
|
|
return c.lastChange
|
|
}
|
|
|
|
func (c *CartGrain) GetLastAccess() time.Time {
|
|
return c.lastAccess
|
|
}
|
|
|
|
func (c *CartGrain) GetCurrentState() (*CartGrain, error) {
|
|
c.lastAccess = time.Now()
|
|
return c, nil
|
|
}
|
|
|
|
func getInt(data float64, ok bool) (int, error) {
|
|
if !ok {
|
|
return 0, fmt.Errorf("invalid type")
|
|
}
|
|
return int(data), nil
|
|
}
|
|
|
|
func GetItemAddMessage(sku string, qty int, country string, storeId *string) (*messages.AddItem, error) {
|
|
item, err := FetchItem(sku, country)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
orgPrice, _ := getInt(item.GetNumberFieldValue(5)) // getInt(item.Fields[5])
|
|
|
|
price, priceErr := getInt(item.GetNumberFieldValue(4)) //Fields[4]
|
|
|
|
if priceErr != nil {
|
|
return nil, fmt.Errorf("invalid price")
|
|
}
|
|
|
|
stock := InStock
|
|
item.HasStock()
|
|
stockValue, ok := item.GetNumberFieldValue(3)
|
|
if !ok || stockValue == 0 {
|
|
stock = OutOfStock
|
|
} else {
|
|
if stockValue < 5 {
|
|
stock = LowStock
|
|
}
|
|
}
|
|
|
|
articleType, _ := item.GetStringFieldValue(1) //.Fields[1].(string)
|
|
outletGrade, ok := item.GetStringFieldValue(20) //.Fields[20].(string)
|
|
var outlet *string
|
|
if ok {
|
|
outlet = &outletGrade
|
|
}
|
|
sellerId, _ := item.GetStringFieldValue(24) // .Fields[24].(string)
|
|
sellerName, _ := item.GetStringFieldValue(9) // .Fields[9].(string)
|
|
|
|
brand, _ := item.GetStringFieldValue(2) //.Fields[2].(string)
|
|
category, _ := item.GetStringFieldValue(10) //.Fields[10].(string)
|
|
category2, _ := item.GetStringFieldValue(11) //.Fields[11].(string)
|
|
category3, _ := item.GetStringFieldValue(12) //.Fields[12].(string)
|
|
category4, _ := item.GetStringFieldValue(13) //Fields[13].(string)
|
|
category5, _ := item.GetStringFieldValue(14) //.Fields[14].(string)
|
|
|
|
return &messages.AddItem{
|
|
ItemId: int64(item.Id),
|
|
Quantity: int32(qty),
|
|
Price: int64(price),
|
|
OrgPrice: int64(orgPrice),
|
|
Sku: sku,
|
|
Name: item.Title,
|
|
Image: item.Img,
|
|
Stock: int32(stock),
|
|
Brand: brand,
|
|
Category: category,
|
|
Category2: category2,
|
|
Category3: category3,
|
|
Category4: category4,
|
|
Category5: category5,
|
|
Tax: 2500,
|
|
SellerId: sellerId,
|
|
SellerName: sellerName,
|
|
ArticleType: articleType,
|
|
Disclaimer: item.Disclaimer,
|
|
Country: country,
|
|
Outlet: outlet,
|
|
StoreId: storeId,
|
|
}, nil
|
|
}
|
|
|
|
// func (c *CartGrain) AddItem(sku string, qty int, country string, storeId *string) (*CartGrain, error) {
|
|
// cartItem, err := getItemData(sku, qty, country)
|
|
// if err != nil {
|
|
// return nil, err
|
|
// }
|
|
// cartItem.StoreId = storeId
|
|
// return c.Apply(cartItem, false)
|
|
// }
|
|
|
|
func (c *CartGrain) GetState() ([]byte, error) {
|
|
return json.Marshal(c)
|
|
}
|
|
|
|
func (c *CartGrain) ItemsWithDelivery() []int {
|
|
ret := make([]int, 0, len(c.Items))
|
|
for _, item := range c.Items {
|
|
for _, delivery := range c.Deliveries {
|
|
for _, id := range delivery.Items {
|
|
if item.Id == id {
|
|
ret = append(ret, id)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return ret
|
|
}
|
|
|
|
func (c *CartGrain) ItemsWithoutDelivery() []int {
|
|
ret := make([]int, 0, len(c.Items))
|
|
hasDelivery := c.ItemsWithDelivery()
|
|
for _, item := range c.Items {
|
|
found := slices.Contains(hasDelivery, item.Id)
|
|
|
|
if !found {
|
|
ret = append(ret, item.Id)
|
|
}
|
|
}
|
|
return ret
|
|
}
|
|
|
|
func (c *CartGrain) FindItemWithSku(sku string) (*CartItem, bool) {
|
|
c.mu.RLock()
|
|
defer c.mu.RUnlock()
|
|
for _, item := range c.Items {
|
|
if item.Sku == sku {
|
|
return item, true
|
|
}
|
|
}
|
|
return nil, false
|
|
}
|
|
|
|
func GetTaxAmount(total int64, tax int) int64 {
|
|
taxD := 10000 / float64(tax)
|
|
return int64(float64(total) / float64((1 + taxD)))
|
|
}
|
|
|
|
// func (c *CartGrain) Apply(content proto.Message, isReplay bool) (*CartGrain, error) {
|
|
|
|
// updated, err := ApplyRegistered(c, content)
|
|
// if err != nil {
|
|
// if err == ErrMutationNotRegistered {
|
|
// return nil, fmt.Errorf("unsupported mutation type %T (not registered)", content)
|
|
// }
|
|
// return nil, err
|
|
// }
|
|
|
|
// // Sliding TTL: update lastChange only for non-replay successful mutations.
|
|
// if updated != nil && !isReplay {
|
|
// c.lastChange = time.Now()
|
|
// c.lastAccess = time.Now()
|
|
// go AppendCartEvent(c.Id, content)
|
|
// }
|
|
|
|
// return updated, nil
|
|
// }
|
|
|
|
func (c *CartGrain) UpdateTotals() {
|
|
c.TotalPrice = 0
|
|
c.TotalTax = 0
|
|
c.TotalDiscount = 0
|
|
for _, item := range c.Items {
|
|
rowTotal := item.Price * int64(item.Quantity)
|
|
rowTax := int64(item.Tax) * int64(item.Quantity)
|
|
item.TotalPrice = rowTotal
|
|
item.TotalTax = rowTax
|
|
c.TotalPrice += rowTotal
|
|
c.TotalTax += rowTax
|
|
itemDiff := max(0, item.OrgPrice-item.Price)
|
|
c.TotalDiscount += itemDiff * int64(item.Quantity)
|
|
}
|
|
for _, delivery := range c.Deliveries {
|
|
c.TotalPrice += delivery.Price
|
|
c.TotalTax += GetTaxAmount(delivery.Price, 2500)
|
|
}
|
|
|
|
}
|