/
delivery.go
341 lines (282 loc) · 10.1 KB
/
delivery.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
package cart
import (
"encoding/json"
"errors"
"time"
priceDomain "flamingo.me/flamingo-commerce/v3/price/domain"
)
type (
// Delivery - represents the DeliveryInfo and the assigned Items
Delivery struct {
//DeliveryInfo - The details for this delivery - normally completed during checkout
DeliveryInfo DeliveryInfo
//Cartitems - list of cartitems
Cartitems []Item
//ShippingItem - The Shipping Costs that may be involved in this delivery
ShippingItem ShippingItem
}
// DeliveryInfo - represents the Delivery
DeliveryInfo struct {
// Code - is a project specific identifier for the Delivery - you need it for the AddToCart Request for example
// The code can follow the convention in the Readme: Type_Method_LocationType_LocationCode
Code string
//Type - The Type of the Delivery - e.g. delivery or pickup - this might trigger different workflows
Workflow string
//Method - The shippingmethod something that is project specific and that can mean different delivery qualities with different deliverycosts
Method string
//Carrier - Optional the name of the Carrier that should be responsible for executing the delivery
Carrier string
//DeliveryLocation The target Location for the delivery
DeliveryLocation DeliveryLocation
//DesiredTime - Optional - the desired time of the delivery
DesiredTime time.Time
//AdditionalData - Possibility for key value based information on the delivery - can be used flexible by each project
AdditionalData map[string]string
//AdditionalDeliveryInfos - similar to AdditionalData this can be used to store "any" other object on a delivery encoded as json.RawMessage
AdditionalDeliveryInfos map[string]json.RawMessage `swaggerignore:"true"`
}
// ShippingItem value object
ShippingItem struct {
Title string
PriceNet priceDomain.Price
TaxAmount priceDomain.Price
AppliedDiscounts AppliedDiscounts
}
//AdditionalDeliverInfo is an interface that allows to store "any" additional objects on the cart
// see DeliveryInfoUpdateCommand
AdditionalDeliverInfo interface {
Marshal() (json.RawMessage, error)
Unmarshal(json.RawMessage) error
}
// DeliveryLocation value object
DeliveryLocation struct {
//Type - the type of the delivery - use some of the constant defined in the package like DeliverylocationTypeAddress
Type string
//Address - (only relevant for type address)
Address *Address
//UseBillingAddress - the address should be taken from billing (only relevant for type address)
UseBillingAddress bool
//Code - optional identifier of this location/destination - is used in special destination Types
Code string
}
//DeliveryBuilder - the Builder (factory) to build new deliveries by making sure the invariants are ok
DeliveryBuilder struct {
deliveryInBuilding *Delivery
}
// DeliveryBuilderProvider should be used to create a Delivery
DeliveryBuilderProvider func() *DeliveryBuilder
)
const (
//DeliveryWorkflowPickup - constant for common delivery workflows
DeliveryWorkflowPickup = "pickup"
//DeliveryWorkflowDelivery - workflow constant
DeliveryWorkflowDelivery = "delivery"
//DeliveryWorkflowUnspecified - workflow constant
DeliveryWorkflowUnspecified = "unspecified"
//DeliverylocationTypeUnspecified - constant
DeliverylocationTypeUnspecified = "unspecified"
//DeliverylocationTypeCollectionpoint - constant
DeliverylocationTypeCollectionpoint = "collection-point"
//DeliverylocationTypeStore - constant
DeliverylocationTypeStore = "store"
//DeliverylocationTypeAddress - constant
DeliverylocationTypeAddress = "address"
//DeliverylocationTypeFreightstation - constant
DeliverylocationTypeFreightstation = "freight-station"
)
//LoadAdditionalInfo - returns the additional Data
func (di *DeliveryInfo) LoadAdditionalInfo(key string, info AdditionalDeliverInfo) error {
if di.AdditionalDeliveryInfos == nil {
return ErrAdditionalInfosNotFound
}
if val, ok := di.AdditionalDeliveryInfos[key]; ok {
return info.Unmarshal(val)
}
return ErrAdditionalInfosNotFound
}
//SubTotalGross - returns SubTotalGross
func (d Delivery) SubTotalGross() priceDomain.Price {
prices := make([]priceDomain.Price, 0, len(d.Cartitems))
for _, item := range d.Cartitems {
prices = append(prices, item.RowPriceGross)
}
result, _ := priceDomain.SumAll(prices...)
return result
}
//GrandTotal - returns SubTotalGross including shipping and discounts - for the Delivery
func (d Delivery) GrandTotal() priceDomain.Price {
// we need a capacity of cartitems + 2
prices := make([]priceDomain.Price, 0, len(d.Cartitems)+2)
for _, item := range d.Cartitems {
prices = append(prices, item.RowPriceGross)
}
prices = append(prices, d.SumTotalDiscountAmount())
if !d.ShippingItem.TotalWithDiscountInclTax().IsZero() {
prices = append(prices, d.ShippingItem.TotalWithDiscountInclTax())
}
result, _ := priceDomain.SumAll(prices...)
return result
}
//SumRowTaxes - returns SumRowTaxes
func (d Delivery) SumRowTaxes() Taxes {
var taxes Taxes
for _, item := range d.Cartitems {
for _, tax := range item.RowTaxes {
taxes = taxes.AddTaxWithMerge(tax)
}
}
return taxes
}
//SumTotalTaxAmount - returns SumTotalTaxAmount
func (d Delivery) SumTotalTaxAmount() priceDomain.Price {
prices := make([]priceDomain.Price, 0, len(d.Cartitems))
for _, item := range d.Cartitems {
prices = append(prices, item.TotalTaxAmount())
}
result, _ := priceDomain.SumAll(prices...)
return result
}
//SubTotalNet - returns SubTotalNet
func (d Delivery) SubTotalNet() priceDomain.Price {
prices := make([]priceDomain.Price, 0, len(d.Cartitems))
for _, item := range d.Cartitems {
prices = append(prices, item.RowPriceNet)
}
result, _ := priceDomain.SumAll(prices...)
return result
}
//SumTotalDiscountAmount - returns SumTotalDiscountAmount
func (d Delivery) SumTotalDiscountAmount() priceDomain.Price {
prices := make([]priceDomain.Price, 0, len(d.Cartitems))
for _, item := range d.Cartitems {
prices = append(prices, item.TotalDiscountAmount())
}
result, _ := priceDomain.SumAll(prices...)
return result
}
//SumNonItemRelatedDiscountAmount returns SumNonItemRelatedDiscountAmount
func (d Delivery) SumNonItemRelatedDiscountAmount() priceDomain.Price {
prices := make([]priceDomain.Price, 0, len(d.Cartitems))
for _, item := range d.Cartitems {
prices = append(prices, item.NonItemRelatedDiscountAmount())
}
result, _ := priceDomain.SumAll(prices...)
return result
}
//SumItemRelatedDiscountAmount - returns SumItemRelatedDiscountAmount
func (d Delivery) SumItemRelatedDiscountAmount() priceDomain.Price {
prices := make([]priceDomain.Price, 0, len(d.Cartitems))
for _, item := range d.Cartitems {
prices = append(prices, item.ItemRelatedDiscountAmount())
}
result, _ := priceDomain.SumAll(prices...)
return result
}
//SubTotalGrossWithDiscounts returns SubTotalGrossWithDiscounts
func (d Delivery) SubTotalGrossWithDiscounts() priceDomain.Price {
price, _ := d.SubTotalGross().Add(d.SumTotalDiscountAmount())
return price
}
//SubTotalNetWithDiscounts - returns SubTotalNet With Discounts
func (d Delivery) SubTotalNetWithDiscounts() priceDomain.Price {
price, _ := d.SubTotalNet().Add(d.SumTotalDiscountAmount())
return price
}
//HasItems - returns true if there are items under the delivery
func (d Delivery) HasItems() bool {
return len(d.Cartitems) > 0
}
//Copy - use to set the values for the new delivery from an existing delivery by copying it
func (f *DeliveryBuilder) Copy(d *Delivery) *DeliveryBuilder {
f.init()
f.deliveryInBuilding.Cartitems = d.Cartitems
f.deliveryInBuilding.ShippingItem = d.ShippingItem
f.deliveryInBuilding.DeliveryInfo = d.DeliveryInfo
return f
}
//AddItem adds an item to the delivery
func (f *DeliveryBuilder) AddItem(i Item) *DeliveryBuilder {
f.init()
f.deliveryInBuilding.Cartitems = append(f.deliveryInBuilding.Cartitems, i)
return f
}
//SetShippingItem - sets the delivery ShippingItem
func (f *DeliveryBuilder) SetShippingItem(i ShippingItem) *DeliveryBuilder {
f.init()
f.deliveryInBuilding.ShippingItem = i
return f
}
//SetDeliveryInfo - sets DeliveryInfo
func (f *DeliveryBuilder) SetDeliveryInfo(i DeliveryInfo) *DeliveryBuilder {
f.init()
f.deliveryInBuilding.DeliveryInfo = i
return f
}
//SetDeliveryCode - sets the deliverycode (dont need to be called if SetDeliveryInfo has a code set already)
func (f *DeliveryBuilder) SetDeliveryCode(code string) *DeliveryBuilder {
f.init()
f.deliveryInBuilding.DeliveryInfo.Code = code
return f
}
//Build - main Factory method
func (f *DeliveryBuilder) Build() (*Delivery, error) {
if f.deliveryInBuilding == nil {
return nil, errors.New("Nothing in building")
}
if f.deliveryInBuilding.DeliveryInfo.Code == "" {
return nil, errors.New("DeliveryInfo.Code is not allowed empty")
}
return f.deliveryInBuilding, nil
}
func (f *DeliveryBuilder) init() {
if f.deliveryInBuilding == nil {
f.deliveryInBuilding = &Delivery{}
}
}
func (f *DeliveryBuilder) reset() {
f.deliveryInBuilding = nil
}
// TotalWithDiscountInclTax - the price the customer need to pay for the shipping
func (s ShippingItem) TotalWithDiscountInclTax() priceDomain.Price {
price, _ := s.PriceNet.Add(s.TaxAmount)
discounts, _ := s.AppliedDiscounts.Sum()
price, _ = price.Add(discounts)
return price.GetPayable()
}
// Tax - the Tax of the shipping
func (s ShippingItem) Tax() Tax {
return Tax{
Type: "tax",
Amount: s.TaxAmount,
}
}
// GetAdditionalData returns additional data
func (di DeliveryInfo) GetAdditionalData(key string) string {
attribute := di.AdditionalData[key]
return attribute
}
// AdditionalDataKeys lists all available keys
func (di DeliveryInfo) AdditionalDataKeys() []string {
res := make([]string, len(di.AdditionalData))
i := 0
for k := range di.AdditionalData {
res[i] = k
i++
}
return res
}
// GetAdditionalDeliveryInfo returns additional delivery info
func (di DeliveryInfo) GetAdditionalDeliveryInfo(key string) json.RawMessage {
attribute := di.AdditionalDeliveryInfos[key]
return attribute
}
// AdditionalDeliveryInfoKeys lists all available keys
func (di DeliveryInfo) AdditionalDeliveryInfoKeys() []string {
res := make([]string, len(di.AdditionalDeliveryInfos))
i := 0
for k := range di.AdditionalDeliveryInfos {
res[i] = k
i++
}
return res
}