-
Notifications
You must be signed in to change notification settings - Fork 4
/
i_cart.go
384 lines (318 loc) · 11 KB
/
i_cart.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
package cart
import (
"sort"
"strconv"
"time"
"github.com/ottemo/commerce/app/models/cart"
"github.com/ottemo/commerce/app/models/visitor"
"github.com/ottemo/commerce/db"
"github.com/ottemo/commerce/env"
"github.com/ottemo/commerce/utils"
)
// function calls when cart was changes and subtotal recalculation needed
func (it *DefaultCart) cartChanged() {
it.Subtotal = 0
}
// function checks that options were set correctly (required elements, option values)
func (it *DefaultCart) checkOptions(productOptions map[string]interface{}, cartItemOptions map[string]interface{}) error {
// loop 1: checking that all products customer set are available for product
for optionName, optionValue := range cartItemOptions {
// checking if product have attribute customer set
if productOption, present := productOptions[optionName]; present {
// checking that product option values are strictly predefined
if productOption, ok := productOption.(map[string]interface{}); ok {
if _, present := productOption["options"]; present {
// checking for valid value was set by customer
// cart option value can be one or multiple values, but should be string there
var optionValuesToCheck []string
switch typedOptionValue := optionValue.(type) {
case string:
optionValuesToCheck = append(optionValuesToCheck, typedOptionValue)
case []string:
optionValuesToCheck = typedOptionValue
case []interface{}:
for _, value := range typedOptionValue {
if value, ok := value.(string); ok {
optionValuesToCheck = append(optionValuesToCheck, value)
}
}
default:
return env.ErrorNew(ConstErrorModule, ConstErrorLevel, "8e735b08-b2b3-4bb8-aa74-bb8ebfe86369", "unexpected option value for '"+optionName+"' option")
}
// checking for option customer set with available for product
for _, optionValue := range optionValuesToCheck {
// there could be couple forms of product available options specification
switch productOptionValues := productOption["options"].(type) {
case map[string]interface{}:
if _, present := productOptionValues[optionValue]; !present {
return env.ErrorNew(ConstErrorModule, ConstErrorLevel, "482efc22-b29e-44e2-9ed6-b78505a56d4d", "invalid value for option '"+optionName+"'")
}
case []interface{}:
found := false
for _, productOptionValue := range productOptionValues {
if productOptionValue == optionValue {
found = true
break
}
}
if !found {
return env.ErrorNew(ConstErrorModule, ConstErrorLevel, "aad634c1-fe7b-42fb-adb7-58dfc0e3591b", "invalid value for option '"+optionName+"'")
}
default:
if productOptionValues != optionValue {
return env.ErrorNew(ConstErrorModule, ConstErrorLevel, "eec3b62d-3aad-429c-821c-1b243fd80fd9", "invalid value for option '"+optionName+"'")
}
}
}
}
}
} else {
return env.ErrorNew(ConstErrorModule, ConstErrorLevel, "504c4098-c7fe-418d-9857-adf2a08a8c3f", "unknown option '"+optionName+"'")
}
}
// loop 2: checking that all product required options were set
for productOption, productOptionValue := range productOptions {
// required product option means that "productOption["required"]=true" should be set
if productOptionValue, ok := productOptionValue.(map[string]interface{}); ok {
if _, present := productOptionValue["required"]; present {
if value, ok := productOptionValue["required"].(bool); ok && value {
//checking cart item option for required option existence
itemOptionValue, present := cartItemOptions[productOption]
if !present {
return env.ErrorNew(ConstErrorModule, ConstErrorLevel, "7d23e4a5-4f3c-4986-8860-c49008510175", productOption+" was not specified")
}
// for multi value options additional check
switch typedValue := itemOptionValue.(type) {
case []interface{}:
if len(typedValue) == 0 {
return env.ErrorNew(ConstErrorModule, ConstErrorLevel, "c03fd072-5aa1-48d9-bb60-f03d23ed6ba4", productOption+" was not specified")
}
}
}
}
}
}
return nil
}
// AddItem adds item to the current cart
// - returns added item or nil if error happened
func (it *DefaultCart) AddItem(productID string, qty int, options map[string]interface{}) (cart.InterfaceCartItem, error) {
//checking qty
if qty <= 0 {
return nil, env.ErrorNew(ConstErrorModule, ConstErrorLevel, "653e6163-0775-41c6-9ff5-4d8ac93bed5e", "qty can't be zero or less")
}
// options default value if them are not set
if options == nil {
options = make(map[string]interface{})
}
// looking for already present item in cart
newItemOptions := utils.EncodeToJSONString(options)
cartItems := it.GetItems()
for _, item := range cartItems {
cartItemOptions := utils.EncodeToJSONString(item.GetOptions())
if item.GetProductID() == productID && cartItemOptions == newItemOptions {
err := item.SetQty(item.GetQty() + qty)
if err != nil {
return nil, env.ErrorDispatch(err)
}
return item, nil
}
}
// if we are there then new item was added to cart
// preparing new cart item
cartItem := &DefaultCartItem{
idx: it.maxIdx,
ProductID: productID,
Qty: qty,
Options: options,
Cart: it,
}
// validate cart item before add to cart
if err := cartItem.ValidateProduct(); err != nil {
return nil, err
}
// adding new item to others
it.maxIdx++
cartItem.idx = it.maxIdx
it.Items[it.maxIdx] = cartItem
// cart change event broadcast
it.cartChanged()
return cartItem, nil
}
// RemoveItem removes item from cart
// - you need to know index you can get from ListItems()
func (it *DefaultCart) RemoveItem(itemIdx int) error {
if cartItem, present := it.Items[itemIdx]; present {
dbEngine := db.GetDBEngine()
if dbEngine == nil {
return env.ErrorNew(ConstErrorModule, ConstErrorLevel, "d68c0e08-8bc6-4884-89b0-f8f949266ec4", "can't get DB engine")
}
cartItemsCollection, err := dbEngine.GetCollection(ConstCartItemsCollectionName)
if err != nil {
return env.ErrorDispatch(err)
}
err = cartItemsCollection.DeleteByID(cartItem.GetID())
if err != nil {
return env.ErrorDispatch(err)
}
delete(it.Items, itemIdx)
it.cartChanged()
return nil
}
return env.ErrorNew(ConstErrorModule, ConstErrorLevel, "f72d898f-477d-42b4-9a69-8ccfffeb298d", "can't find index "+strconv.Itoa(itemIdx))
}
// SetQty sets new qty for particular item in cart
// - you need to it's index, use ListItems() for that
func (it *DefaultCart) SetQty(itemIdx int, qty int) error {
cartItem, present := it.Items[itemIdx]
if present {
err := cartItem.SetQty(qty)
if err != nil {
return env.ErrorDispatch(err)
}
it.cartChanged()
return nil
}
return env.ErrorNew(ConstErrorModule, ConstErrorLevel, "77005096-3b3f-46bc-8c67-4b7df255fdaf", "there is no item with idx="+strconv.Itoa(itemIdx))
}
// GetSubtotal returns subtotal for cart items
func (it *DefaultCart) GetSubtotal() float64 {
if it.Subtotal == 0 {
for _, cartItem := range it.Items {
if cartProduct := cartItem.GetProduct(); cartProduct != nil {
it.Subtotal += cartProduct.GetPrice() * float64(cartItem.GetQty())
}
}
}
return it.Subtotal
}
// GetItems enumerates current cart items sorted by item idx
func (it *DefaultCart) GetItems() []cart.InterfaceCartItem {
var result []cart.InterfaceCartItem
var keys []int
for key := range it.Items {
keys = append(keys, key)
}
sort.Ints(keys)
for _, key := range keys {
result = append(result, it.Items[key])
}
return result
}
// GetVisitorID returns visitor id this cart belongs to
func (it *DefaultCart) GetVisitorID() string {
return it.VisitorID
}
// SetVisitorID sets new owner of cart
func (it *DefaultCart) SetVisitorID(visitorID string) error {
it.VisitorID = visitorID
return nil
}
// GetVisitor returns visitor model represents owner or current cart or nil if visitor was not set to cart
func (it *DefaultCart) GetVisitor() visitor.InterfaceVisitor {
visitorInstance, err := visitor.LoadVisitorByID(it.VisitorID)
if err != nil {
_ = env.ErrorDispatch(err)
}
return visitorInstance
}
// SetCartInfo assigns some information to current cart
func (it *DefaultCart) SetCartInfo(infoAttribute string, infoValue interface{}) error {
if it.Info == nil {
it.Info = make(map[string]interface{})
}
it.Info[infoAttribute] = infoValue
return nil
}
// GetCartInfo returns current cart info assigned
func (it *DefaultCart) GetCartInfo() map[string]interface{} {
return it.Info
}
func (it *DefaultCart) SetCustomInfo(info map[string]interface{}) {
it.CustomInfo = info
}
func (it *DefaultCart) GetCustomInfo() map[string]interface{} {
return it.CustomInfo
}
// MakeCartForVisitor loads cart for given visitor from database or creates new one
func (it *DefaultCart) MakeCartForVisitor(visitorID string) error {
dbEngine := db.GetDBEngine()
if dbEngine == nil {
return env.ErrorNew(ConstErrorModule, ConstErrorLevel, "4c6da71f-e0ea-4aae-8560-026c08bfc097", "can't get DB Engine")
}
cartCollection, err := dbEngine.GetCollection(ConstCartCollectionName)
if err != nil {
return env.ErrorDispatch(err)
}
if err := cartCollection.AddFilter("visitor_id", "=", visitorID); err != nil {
return env.ErrorDispatch(err)
}
if err := cartCollection.AddFilter("active", "=", true); err != nil {
return env.ErrorDispatch(err)
}
rowsData, err := cartCollection.Load()
if err != nil {
return env.ErrorDispatch(err)
}
if len(rowsData) < 1 {
newModel, err := it.New()
if err != nil {
return env.ErrorDispatch(err)
}
newCart := newModel.(*DefaultCart)
if err := newCart.SetVisitorID(visitorID); err != nil {
return env.ErrorDispatch(err)
}
if err := newCart.Activate(); err != nil {
return env.ErrorDispatch(err)
}
if err := newCart.Save(); err != nil {
return env.ErrorDispatch(err)
}
*it = *newCart
} else {
err := it.Load(rowsData[0]["_id"].(string))
if err != nil {
return env.ErrorDispatch(err)
}
}
return nil
}
// Activate makes cart active
// - only one cart can be active for particular visitor
func (it *DefaultCart) Activate() error {
it.Active = true
return nil
}
// Deactivate makes cart un-active
// - so new cart will be created on next request
func (it *DefaultCart) Deactivate() error {
it.Active = false
return nil
}
// IsActive returns active flag status of cart
func (it *DefaultCart) IsActive() bool {
return it.Active
}
// ValidateCart returns nil of cart is valid, error otherwise
func (it *DefaultCart) ValidateCart() error {
for _, cartItem := range it.GetItems() {
if err := cartItem.ValidateProduct(); err != nil {
return err
}
}
return nil
}
// GetSessionID returns session id last time used for cart
func (it *DefaultCart) GetSessionID() string {
return it.SessionID
}
// SetSessionID sets session id associated to cart
func (it *DefaultCart) SetSessionID(sessionID string) error {
it.SessionID = sessionID
return nil
}
// GetLastUpdateTime returns cart last update time
func (it *DefaultCart) GetLastUpdateTime() time.Time {
return it.UpdatedAt
}