@peabody-soft/mbs-model-ts 1.5.0 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Address.js +8 -11
- package/dist/Attachment.js +7 -8
- package/dist/AttachmentBaseUrl.js +10 -10
- package/dist/AuthTokens.js +12 -7
- package/dist/Business.js +19 -19
- package/dist/BusinessIdentifier.js +2 -2
- package/dist/BusinessLink.js +16 -16
- package/dist/BusinessProfile.js +4 -4
- package/dist/CartEditor.js +137 -166
- package/dist/Catalog.js +19 -25
- package/dist/Constants.js +10 -10
- package/dist/ContainerNames.js +10 -11
- package/dist/Entity.js +11 -9
- package/dist/Enums.js +2 -2
- package/dist/Exceptions.js +2 -2
- package/dist/GeneralSettings.js +23 -21
- package/dist/HttpStatusCodes.js +10 -11
- package/dist/Ids.js +24 -30
- package/dist/Item.js +6 -7
- package/dist/LatLong.js +3 -3
- package/dist/LiveData.js +6 -6
- package/dist/Measures.js +2 -2
- package/dist/Message.js +6 -6
- package/dist/MockDatabase.js +2 -2
- package/dist/Model.js +294 -365
- package/dist/Order.js +20 -17
- package/dist/Package.js +7 -8
- package/dist/PackageIdentifier.js +2 -2
- package/dist/PaymentMethodDetail.js +4 -4
- package/dist/Photo.js +6 -7
- package/dist/Price.js +8 -9
- package/dist/PriceList.js +2 -2
- package/dist/Product.js +17 -22
- package/dist/ProductCategory.js +3 -4
- package/dist/Profile.js +26 -27
- package/dist/ProtoMap.d.ts +3 -0
- package/dist/ProtoMap.js +288 -294
- package/dist/Routes.js +3 -3
- package/dist/ServiceExceptions.js +11 -12
- package/dist/ServiceProxy.js +19 -23
- package/dist/ShoppingList.js +112 -130
- package/dist/SyncCache.d.ts +3 -0
- package/dist/SyncCache.js +92 -75
- package/dist/SyncKeys.js +8 -8
- package/dist/SyncState.js +5 -5
- package/dist/Tax.js +16 -21
- package/dist/TaxHelper.js +22 -30
- package/dist/TaxType.js +8 -9
- package/dist/UserProfile.js +4 -4
- package/dist/WorkingDays.js +15 -15
- package/dist/index.js +9 -9
- package/package.json +1 -1
package/dist/CartEditor.js
CHANGED
|
@@ -6,51 +6,42 @@ import { InvalidArgumentException } from "./Exceptions";
|
|
|
6
6
|
import Ids from "./Ids";
|
|
7
7
|
import ShoppingList, { ShoppingListItem, validatePricing } from "./ShoppingList";
|
|
8
8
|
import TaxHelper from "./TaxHelper";
|
|
9
|
-
// Builds the cart a customer is filling, in the terms a UI has to hand: packages
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
// Business.createCartEditor, edit it, then hand build() to Model.updateCartLocalOnly.
|
|
9
|
+
// Builds the cart a customer is filling, in the terms a UI has to hand: packages, quantities as
|
|
10
|
+
// the customer means them (not multiplied by Constants.QUANTITY_FACTOR), prices from the catalog.
|
|
11
|
+
// Get one from Business.createCartEditor, edit it, then hand build() to Model.updateCartLocalOnly.
|
|
13
12
|
//
|
|
14
|
-
// Mutable, and
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
// prices under the customer. Make a fresh one when the Business republishes.
|
|
13
|
+
// Mutable, and the only mutable thing in this object model. It is a scratch copy: editing it
|
|
14
|
+
// changes no Business, store or server until a built list is handed to Model. It is also a
|
|
15
|
+
// snapshot, read once at construction, so a sync mid-edit does not move prices under the customer.
|
|
16
|
+
// Make a fresh one when the Business republishes.
|
|
19
17
|
//
|
|
20
|
-
// EVERY PRICE HERE INCLUDES TAXES
|
|
21
|
-
// the
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
// .setDetailsFromTaxInclusivePrices does, and all the arithmetic is Java's to the unit (see
|
|
26
|
-
// TaxHelper). The taxes are there to be shown as what the total includes (taxes), never to be
|
|
27
|
-
// added to it.
|
|
18
|
+
// EVERY PRICE HERE INCLUDES TAXES: a package's includes its product's taxes, the delivery charge
|
|
19
|
+
// the taxes the settings name for it, so a customer pays what the catalog shows. A shopping list
|
|
20
|
+
// item stores a price WITHOUT taxes, so building one takes them out, as Java
|
|
21
|
+
// ShoppingListItem.setDetailsFromTaxInclusivePrices does (to the unit, see TaxHelper). Taxes are
|
|
22
|
+
// shown as what a total includes (taxes), never added to it.
|
|
28
23
|
class CartEditor {
|
|
29
|
-
// Business.createCartEditor is how a UI gets one
|
|
30
|
-
//
|
|
31
|
-
// that are not the ones currently published.
|
|
24
|
+
// Business.createCartEditor is how a UI gets one. It takes the catalog, settings and cart, not
|
|
25
|
+
// the Business, so a caller can build a cart against ones that are not currently published.
|
|
32
26
|
//
|
|
33
|
-
// Every line is priced off the catalog
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
// withdrawn, disabled
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
// of the list it started from (see ShoppingListItem.isReservedPackage) are dropped too, being
|
|
43
|
-
// this class's to work out rather than a customer's to carry: build() puts back whatever the
|
|
44
|
-
// settings now say.
|
|
27
|
+
// Every line is priced off the catalog, not off what the cart held: the merchant may have
|
|
28
|
+
// repriced since, and an order freezes the price when it is placed. A line goes to
|
|
29
|
+
// unavailableItems, for a UI to tell the customer about, when the catalog can no longer price it
|
|
30
|
+
// (package withdrawn, disabled or without a price) or setQuantity would refuse it (a cart that
|
|
31
|
+
// was read is not known to be one this class could have made): a zero or invalid quantity, a
|
|
32
|
+
// fraction of a package not sold loose, more than the wire or the protocol carries at today's
|
|
33
|
+
// price, or a line past the most a cart holds (the first lines are kept, in pick order).
|
|
34
|
+
// The charge lines of the starting list (see ShoppingListItem.isReservedPackage) are dropped, not
|
|
35
|
+
// listed, since build() puts back what the settings now say.
|
|
45
36
|
constructor(_catalog, _settings, cart) {
|
|
46
37
|
this._catalog = _catalog;
|
|
47
38
|
this._settings = _settings;
|
|
48
|
-
// Insertion ordered
|
|
49
|
-
//
|
|
39
|
+
// Insertion ordered: keeps the cart in the order the customer filled it and gives each line its
|
|
40
|
+
// shoppingListOrder
|
|
50
41
|
this._quantities = new Map();
|
|
51
42
|
this._inventoryLocationId = cart?.inventoryLocationId ?? 0;
|
|
52
|
-
//
|
|
53
|
-
//
|
|
43
|
+
// goodsItems is in the order the customer picked. existingItems is not, the server sending it
|
|
44
|
+
// by package id, so a cart read back would reorder itself
|
|
54
45
|
const unavailableItems = [];
|
|
55
46
|
for (const item of cart?.goodsItems ?? []) {
|
|
56
47
|
if (item.isReservedPackage)
|
|
@@ -60,8 +51,8 @@ class CartEditor {
|
|
|
60
51
|
}
|
|
61
52
|
this._unavailableItems = Object.freeze(unavailableItems);
|
|
62
53
|
}
|
|
63
|
-
// What the customer has picked, in
|
|
64
|
-
//
|
|
54
|
+
// What the customer has picked, in pick order, each line priced as it would be bought now.
|
|
55
|
+
// Rebuilt on every read, so a line held across an edit is stale.
|
|
65
56
|
get lines() {
|
|
66
57
|
const lines = [];
|
|
67
58
|
for (const [packageId, quantity] of this._quantities) {
|
|
@@ -72,15 +63,15 @@ class CartEditor {
|
|
|
72
63
|
}
|
|
73
64
|
return Object.freeze(lines);
|
|
74
65
|
}
|
|
75
|
-
// The lines of the cart
|
|
76
|
-
//
|
|
66
|
+
// The lines of the starting cart that the catalog can no longer price (see the constructor).
|
|
67
|
+
// Empty for a cart built from nothing, and no edit adds to it.
|
|
77
68
|
get unavailableItems() {
|
|
78
69
|
return this._unavailableItems;
|
|
79
70
|
}
|
|
80
71
|
get isEmpty() {
|
|
81
72
|
return this._quantities.size === 0;
|
|
82
73
|
}
|
|
83
|
-
//
|
|
74
|
+
// The number of lines, which is what a cart badge counts
|
|
84
75
|
get lineCount() {
|
|
85
76
|
return this._quantities.size;
|
|
86
77
|
}
|
|
@@ -95,15 +86,15 @@ class CartEditor {
|
|
|
95
86
|
getQuantity(item) {
|
|
96
87
|
return this._quantities.get(item.id) ?? 0;
|
|
97
88
|
}
|
|
98
|
-
// Sets how much of one package the cart holds; zero takes it out.
|
|
99
|
-
// buy
|
|
100
|
-
//
|
|
101
|
-
//
|
|
102
|
-
//
|
|
89
|
+
// Sets how much of one package the cart holds; zero takes it out. Refuses a package a customer
|
|
90
|
+
// cannot buy (disabled, or priced in no list this storefront sells from), a fractional quantity
|
|
91
|
+
// of a package that is not sold loose, and a quantity past what the wire can carry. Sold loose is
|
|
92
|
+
// read from the catalog's package, since the Package passed in may predate a catalog change.
|
|
93
|
+
// Returns this, so edits chain.
|
|
103
94
|
setQuantity(item, quantity) {
|
|
104
95
|
const wireQuantity = CartEditor.toWireQuantity(quantity);
|
|
105
|
-
// Less than half a thousandth
|
|
106
|
-
//
|
|
96
|
+
// Less than half a thousandth rounds to zero on the wire, a line that costs nothing, so it
|
|
97
|
+
// takes the line out as zero does
|
|
107
98
|
if (wireQuantity === 0) {
|
|
108
99
|
this._quantities.delete(item.id);
|
|
109
100
|
return this;
|
|
@@ -111,8 +102,8 @@ class CartEditor {
|
|
|
111
102
|
this._quantities.set(item.id, this.checkQuantity(item.id, quantity, wireQuantity));
|
|
112
103
|
return this;
|
|
113
104
|
}
|
|
114
|
-
// Adds to what the cart
|
|
115
|
-
//
|
|
105
|
+
// Adds to what the cart holds of this package, as a quantity stepper does. A negative amount
|
|
106
|
+
// takes away, down to zero at the lowest.
|
|
116
107
|
add(item, quantity = 1) {
|
|
117
108
|
return this.setQuantity(item, Math.max(0, this.getQuantity(item) + quantity));
|
|
118
109
|
}
|
|
@@ -123,53 +114,50 @@ class CartEditor {
|
|
|
123
114
|
this._quantities.clear();
|
|
124
115
|
return this;
|
|
125
116
|
}
|
|
126
|
-
// What the packages come to, taxes included and offers
|
|
127
|
-
// prices
|
|
128
|
-
// is not in it.
|
|
117
|
+
// What the packages come to, taxes included and offers taken off: the sum of the lines' final
|
|
118
|
+
// prices, which a customer sees as the price of the cart. Delivery is not in it.
|
|
129
119
|
get itemsTotal() {
|
|
130
120
|
return ShoppingList.sumOfFinalPrices(this.lines.map((line) => line.item));
|
|
131
121
|
}
|
|
132
|
-
// What the offers took off the packages,
|
|
133
|
-
//
|
|
134
|
-
// Taxes are included on both sides.
|
|
122
|
+
// What the offers took off the packages, shown on a bill as savings: the total at regular prices
|
|
123
|
+
// less the total. Zero when nothing is on offer. Taxes are included on both sides.
|
|
135
124
|
get savings() {
|
|
136
125
|
return this.build().savings;
|
|
137
126
|
}
|
|
138
|
-
// The taxes inside itemsTotal, all types together.
|
|
139
|
-
// added to it: see taxes.
|
|
127
|
+
// The taxes already inside itemsTotal, all types together. See taxes for the split by type.
|
|
140
128
|
get itemsTax() {
|
|
141
129
|
return this.build().computedTotalTax;
|
|
142
130
|
}
|
|
143
|
-
// The taxes the cart's total includes, one entry per TYPE of tax
|
|
144
|
-
// that is not zero. withDeliveryCharge is as for total: the
|
|
145
|
-
//
|
|
131
|
+
// The taxes the cart's total includes, one entry per TYPE of tax. A bill shows a line for each
|
|
132
|
+
// that is not zero. withDeliveryCharge is as for total: the delivery charge's taxes count only
|
|
133
|
+
// when it is true.
|
|
146
134
|
taxes(withDeliveryCharge = false) {
|
|
147
135
|
return this.build(withDeliveryCharge).computedTaxes;
|
|
148
136
|
}
|
|
149
|
-
// True when this business delivers
|
|
150
|
-
//
|
|
137
|
+
// True when this business delivers. When false there is no delivery charge for build() to add,
|
|
138
|
+
// and no fulfillment choice for a UI to offer.
|
|
151
139
|
get isDeliveryOffered() {
|
|
152
140
|
return this._settings.fulfillmentModes.includes(proto_FulfillmentMode.DELIVERY);
|
|
153
141
|
}
|
|
154
|
-
// What delivering this cart
|
|
155
|
-
//
|
|
156
|
-
//
|
|
142
|
+
// What delivering this cart costs, taxes included: the business's flat charge, or zero when the
|
|
143
|
+
// cart has reached freeDeliveryTotal or the business delivers free. Zero when the business does
|
|
144
|
+
// not deliver.
|
|
157
145
|
get deliveryCharge() {
|
|
158
146
|
return this.buildDeliveryChargeItem(this.getDeliveryCharge(this.itemsTotal), 0)?.finalPrice ?? 0;
|
|
159
147
|
}
|
|
160
|
-
// What delivery costs before any waiver
|
|
161
|
-
//
|
|
148
|
+
// What delivery costs before any waiver, which a bill strikes through when it reads FREE. Zero
|
|
149
|
+
// where the business delivers free, so there is nothing to strike through.
|
|
162
150
|
get regularDeliveryCharge() {
|
|
163
151
|
return this.buildDeliveryChargeItem(this.regularCharge, 0)?.finalPrice ?? 0;
|
|
164
152
|
}
|
|
165
|
-
// True when delivering this cart costs nothing,
|
|
166
|
-
//
|
|
153
|
+
// True when delivering this cart costs nothing, because the business never charges or the cart
|
|
154
|
+
// has reached the threshold
|
|
167
155
|
get isDeliveryFree() {
|
|
168
156
|
return this.isDeliveryOffered && this.deliveryCharge === 0;
|
|
169
157
|
}
|
|
170
|
-
// How much more the customer
|
|
171
|
-
//
|
|
172
|
-
//
|
|
158
|
+
// How much more the customer must add for free delivery, for the "add X more for free delivery"
|
|
159
|
+
// note. Zero when delivery is already free. Null when the business does not deliver or has set no
|
|
160
|
+
// threshold, so no amount would make it free.
|
|
173
161
|
get amountToFreeDelivery() {
|
|
174
162
|
if (!this.isDeliveryOffered)
|
|
175
163
|
return null;
|
|
@@ -181,42 +169,30 @@ class CartEditor {
|
|
|
181
169
|
const remaining = amountToScaledValue(freeDeliveryTotal) - amountToScaledValue(this.itemsTotal);
|
|
182
170
|
return remaining > 0n ? amountFromScaledValue(remaining) : 0;
|
|
183
171
|
}
|
|
184
|
-
//
|
|
185
|
-
//
|
|
186
|
-
//
|
|
187
|
-
// nothing to tax.
|
|
172
|
+
// The tax inside deliveryCharge, from GeneralSettings.deliveryChargeTaxes. Those are the
|
|
173
|
+
// business's own, unrelated to a product's. Included in the charge, not added to it. Zero when
|
|
174
|
+
// delivery is free.
|
|
188
175
|
get deliveryTax() {
|
|
189
176
|
return this.buildDeliveryChargeItem(this.getDeliveryCharge(this.itemsTotal), 0)?.totalTax ?? 0;
|
|
190
177
|
}
|
|
191
|
-
// What the cart comes to, taxes included. Delivery is left out unless asked for
|
|
192
|
-
//
|
|
193
|
-
//
|
|
178
|
+
// What the cart comes to, taxes included. Delivery is left out unless asked for: a cart page
|
|
179
|
+
// leaves it out and a checkout page puts it in, as in build(). It is the total of the list
|
|
180
|
+
// build() answers, and so of an order placed from it.
|
|
194
181
|
total(withDeliveryCharge = false) {
|
|
195
182
|
return this.build(withDeliveryCharge).totalAmount;
|
|
196
183
|
}
|
|
197
|
-
// The cart as a list to write
|
|
198
|
-
//
|
|
199
|
-
//
|
|
200
|
-
//
|
|
184
|
+
// The cart as a list, to write (Model.updateCartLocalOnly, Model.updateCart, Model.createOrder)
|
|
185
|
+
// and to SHOW: every price, total and tax a cart or checkout page displays can be read off it.
|
|
186
|
+
// Each line stores its price without taxes and is flagged original, as Java ShoppingList.addItem
|
|
187
|
+
// does before a list is final. The list comes back not final: Model.createOrder makes it so.
|
|
201
188
|
//
|
|
202
|
-
//
|
|
203
|
-
//
|
|
204
|
-
//
|
|
205
|
-
//
|
|
206
|
-
//
|
|
207
|
-
//
|
|
208
|
-
//
|
|
209
|
-
// ShoppingList.autoAddDeliveryCharge. A cart page has no fulfillment chosen yet and so leaves it
|
|
210
|
-
// out, which is the default; a checkout page that has settled on delivery asks for it. The line
|
|
211
|
-
// is added even when the charge has been waived - at zero, with the regular charge (less its
|
|
212
|
-
// taxes, like every price in an item) as its priceDiscount - so a bill can show FREE with the
|
|
213
|
-
// original struck through, which is what Java ShoppingCartViewModel.setDeliveryCharge writes
|
|
214
|
-
// too.
|
|
215
|
-
//
|
|
216
|
-
// Nothing is added to an empty cart: a list holding a delivery charge and no goods is not an
|
|
217
|
-
// order, and Java removes exactly that line when it is all that is left.
|
|
218
|
-
// The list comes back not final. Making it final is placing an order, which is
|
|
219
|
-
// Model.createOrder's to do.
|
|
189
|
+
// withDeliveryCharge adds the delivery charge as a line (ReservedPackage.DELIVERY_CHARGE_PACKAGE)
|
|
190
|
+
// and sets ShoppingList.autoAddDeliveryCharge. A cart page leaves it out (the default), a checkout
|
|
191
|
+
// page that has settled on delivery asks for it. The line is added even when the charge is
|
|
192
|
+
// waived: at zero, with the regular charge (less its taxes) as its priceDiscount, so a bill can
|
|
193
|
+
// show FREE with the original struck through (Java ShoppingCartViewModel.setDeliveryCharge).
|
|
194
|
+
// An empty cart gets no line: a list holding only a delivery charge is not an order, and Java
|
|
195
|
+
// removes that line when it is all that is left.
|
|
220
196
|
build(withDeliveryCharge = false) {
|
|
221
197
|
const items = [];
|
|
222
198
|
for (const line of this.lines)
|
|
@@ -226,38 +202,38 @@ class CartEditor {
|
|
|
226
202
|
: null;
|
|
227
203
|
if (delivery !== null)
|
|
228
204
|
items.push(delivery);
|
|
229
|
-
// The total is
|
|
230
|
-
//
|
|
231
|
-
//
|
|
205
|
+
// The total is the sum of the final prices of the lines sent, tax included, as ShoppingList.proto
|
|
206
|
+
// defines it. There is no list-wide discount: an offer comes off a line, and a storefront gives
|
|
207
|
+
// no other.
|
|
232
208
|
return new ShoppingList(false, delivery !== null, false, this._inventoryLocationId, this._catalog.priceList?.id ?? MERABILLS_ONLINE_PRICE_LIST_ID, ShoppingList.sumOfFinalPrices(items), 0, items.length, 0, Object.freeze(items), Object.freeze([]));
|
|
233
209
|
}
|
|
234
|
-
// The delivery charge as a line: one
|
|
235
|
-
// zero once waived), with the waived amount as its price discount
|
|
236
|
-
// bought on offer,
|
|
237
|
-
//
|
|
238
|
-
//
|
|
210
|
+
// The delivery charge as a line: one unit at what it costs now (charge: the regular charge, or
|
|
211
|
+
// zero once waived), with the waived amount as its price discount. That is the shape of a package
|
|
212
|
+
// bought on offer, so a bill renders it with no special case beyond its label. Null when the
|
|
213
|
+
// business does not deliver. The settings' charge includes the delivery charge taxes, so they
|
|
214
|
+
// come out of it like those of any other price.
|
|
239
215
|
buildDeliveryChargeItem(charge, shoppingListOrder) {
|
|
240
216
|
if (!this.isDeliveryOffered)
|
|
241
217
|
return null;
|
|
242
218
|
return buildItem(Ids.buildId(proto_Constants.PACKAGE_ENTITY_TYPE_ID, BigInt(proto_ReservedPackage.DELIVERY_CHARGE_PACKAGE)), 1, charge, this.regularCharge, this._settings.deliveryChargeTaxes, shoppingListOrder);
|
|
243
219
|
}
|
|
244
|
-
// The charge before any waiver. Java clamps a negative charge to zero
|
|
245
|
-
//
|
|
220
|
+
// The charge before any waiver. Java clamps a negative charge to zero (no refund), and so does
|
|
221
|
+
// this. Zero when the business does not deliver.
|
|
246
222
|
get regularCharge() {
|
|
247
223
|
if (!this.isDeliveryOffered)
|
|
248
224
|
return 0;
|
|
249
225
|
return Math.max(0, this._settings.deliveryCharge);
|
|
250
226
|
}
|
|
251
|
-
// The charge
|
|
252
|
-
//
|
|
253
|
-
//
|
|
227
|
+
// The charge for a cart whose goods come to itemsTotal: the regular charge, or nothing once the
|
|
228
|
+
// goods reach the threshold. Java ShoppingCartViewModel.calculateDeliveryCharge compares the same
|
|
229
|
+
// total, taxes included and the delivery line left out.
|
|
254
230
|
getDeliveryCharge(itemsTotal) {
|
|
255
231
|
const charge = this.regularCharge;
|
|
256
232
|
if (charge === 0)
|
|
257
233
|
return 0; // Free for everyone, or not delivered at all
|
|
258
234
|
const freeDeliveryTotal = this._settings.freeDeliveryTotal;
|
|
259
235
|
if (freeDeliveryTotal <= 0)
|
|
260
|
-
return charge; // Never free,
|
|
236
|
+
return charge; // Never free, at any total
|
|
261
237
|
return itemsTotal >= freeDeliveryTotal ? 0 : charge;
|
|
262
238
|
}
|
|
263
239
|
// The package this id names, and null unless a customer could actually buy it right now
|
|
@@ -265,8 +241,8 @@ class CartEditor {
|
|
|
265
241
|
const item = this._catalog.getPackage(packageId);
|
|
266
242
|
return item !== null && item.isBuyable ? item : null;
|
|
267
243
|
}
|
|
268
|
-
// Keeps a line of a cart that was read, as setQuantity would, and says whether it could
|
|
269
|
-
// that cannot be kept is not
|
|
244
|
+
// Keeps a line of a cart that was read, as setQuantity would, and says whether it could. A line
|
|
245
|
+
// that cannot be kept goes to unavailableItems, as it is not the caller's mistake.
|
|
270
246
|
tryKeep(item) {
|
|
271
247
|
try {
|
|
272
248
|
const wireQuantity = CartEditor.toWireQuantity(item.quantity);
|
|
@@ -281,40 +257,39 @@ class CartEditor {
|
|
|
281
257
|
throw error;
|
|
282
258
|
}
|
|
283
259
|
}
|
|
284
|
-
// A quantity travels as an int32 multiplied by Constants.QUANTITY_FACTOR (Java rounds it
|
|
285
|
-
// NumberHelper.floatToInt), so the least
|
|
286
|
-
//
|
|
287
|
-
//
|
|
260
|
+
// A quantity travels as an int32 multiplied by Constants.QUANTITY_FACTOR (Java rounds to it with
|
|
261
|
+
// NumberHelper.floatToInt), so the least is a thousandth. This converts a quantity to that. What
|
|
262
|
+
// is kept is the quantity the wire holds, so getQuantity never reports one the order will not
|
|
263
|
+
// have.
|
|
288
264
|
static toWireQuantity(quantity) {
|
|
289
265
|
if (!Number.isFinite(quantity) || quantity < 0)
|
|
290
266
|
throw new InvalidArgumentException(`${quantity} is not a quantity`);
|
|
291
267
|
return Math.round(quantity * proto_Constants.QUANTITY_FACTOR);
|
|
292
268
|
}
|
|
293
|
-
// The quantity to keep for this much of a package,
|
|
294
|
-
// InvalidArgumentException saying why the cart cannot have it: the
|
|
295
|
-
//
|
|
296
|
-
//
|
|
297
|
-
// package, which
|
|
269
|
+
// The quantity to keep for this much of a package, as the wire holds it, or an
|
|
270
|
+
// InvalidArgumentException saying why the cart cannot have it: the catalog does not sell the
|
|
271
|
+
// package, it is not sold loose and the quantity is a fraction, the quantity or what it comes to
|
|
272
|
+
// is more than the protocol carries, or the cart is full. All of it is read off the catalog's
|
|
273
|
+
// package, which the order is priced from.
|
|
298
274
|
checkQuantity(packageId, quantity, wireQuantity) {
|
|
299
275
|
const buyable = this.getBuyablePackage(packageId);
|
|
300
276
|
if (buyable === null)
|
|
301
277
|
throw new InvalidArgumentException(`Package ${Ids.toStorageHexString(packageId)} cannot be bought from this catalog`);
|
|
302
278
|
if (!buyable.isSoldLoose && !Number.isInteger(quantity))
|
|
303
279
|
throw new InvalidArgumentException(`${buyable.name} is not sold loose, so it cannot be bought ${quantity} at a time`);
|
|
304
|
-
//
|
|
305
|
-
// wrapping on the wire
|
|
280
|
+
// The most of one package a cart can hold. Refused here so it does not wrap silently on the wire
|
|
306
281
|
if (wireQuantity > CartEditor.MAX_WIRE_QUANTITY)
|
|
307
282
|
throw new InvalidArgumentException(`${quantity} is more than the ${CartEditor.MAX_WIRE_QUANTITY / proto_Constants.QUANTITY_FACTOR} the protocol can carry`);
|
|
308
283
|
// A list holds at most Constants.SHOPPING_LIST_MAX_ITEMS items, and the delivery charge is one
|
|
309
|
-
//
|
|
310
|
-
//
|
|
311
|
-
//
|
|
284
|
+
// (Java ShoppingList.addItem counts it like any other, and a server refuses a list one line
|
|
285
|
+
// over). Where the business delivers, one place is kept for it: a cart filled to the limit
|
|
286
|
+
// could not be ordered for delivery.
|
|
312
287
|
const maxLines = proto_Constants.SHOPPING_LIST_MAX_ITEMS - (this.isDeliveryOffered ? 1 : 0);
|
|
313
288
|
if (!this._quantities.has(packageId) && this._quantities.size >= maxLines)
|
|
314
289
|
throw new InvalidArgumentException(`A cart holds at most ${maxLines} lines`);
|
|
315
290
|
// Java refuses an amount past Transaction.MAX_AMOUNT (validateAmount) in the item that comes to
|
|
316
|
-
// it, and so does pricing an item here
|
|
317
|
-
// a list
|
|
291
|
+
// it, and so does pricing an item here. It is priced before the line is kept, so the failure
|
|
292
|
+
// does not wait until a list is written.
|
|
318
293
|
const stored = wireQuantity / proto_Constants.QUANTITY_FACTOR;
|
|
319
294
|
try {
|
|
320
295
|
validatePricing(new CartLine(buyable, this._catalog.getProduct(buyable.productId), stored).item);
|
|
@@ -327,16 +302,15 @@ class CartEditor {
|
|
|
327
302
|
return stored;
|
|
328
303
|
}
|
|
329
304
|
}
|
|
330
|
-
// The most an int32 holds, which
|
|
331
|
-
// Constants.QUANTITY_FACTOR can be
|
|
305
|
+
// The most an int32 holds, which bounds a quantity multiplied by Constants.QUANTITY_FACTOR
|
|
332
306
|
CartEditor.MAX_WIRE_QUANTITY = 0x7fffffff;
|
|
333
307
|
export default CartEditor;
|
|
334
|
-
// One line of a cart being edited: the package, the product it is a variant of, and how much of it
|
|
335
|
-
//
|
|
336
|
-
//
|
|
308
|
+
// One line of a cart being edited: the package, the product it is a variant of, and how much of it,
|
|
309
|
+
// with the prices the catalog gives it now, taxes included. A read-only view over what CartEditor
|
|
310
|
+
// holds, rebuilt on every read of CartEditor.lines.
|
|
337
311
|
//
|
|
338
|
-
// Everything
|
|
339
|
-
//
|
|
312
|
+
// Everything is read off item, the line as a shopping list would store it, so the cart page shows
|
|
313
|
+
// what the order will and no price is worked out twice.
|
|
340
314
|
export class CartLine {
|
|
341
315
|
constructor(_package, _product, _quantity) {
|
|
342
316
|
this._package = _package;
|
|
@@ -347,7 +321,7 @@ export class CartLine {
|
|
|
347
321
|
get package() {
|
|
348
322
|
return this._package;
|
|
349
323
|
}
|
|
350
|
-
// Null
|
|
324
|
+
// Null when the package's product is not in the catalog, which a buyable package never has
|
|
351
325
|
get product() {
|
|
352
326
|
return this._product;
|
|
353
327
|
}
|
|
@@ -355,24 +329,23 @@ export class CartLine {
|
|
|
355
329
|
return this._quantity;
|
|
356
330
|
}
|
|
357
331
|
// This line as a shopping list holds it: priced without taxes, with the taxes beside it. Show
|
|
358
|
-
// this to show the line
|
|
332
|
+
// this to show the line as an order does. Its place in the list means nothing here.
|
|
359
333
|
get item() {
|
|
360
334
|
this._item ?? (this._item = this.toShoppingListItem(0));
|
|
361
335
|
return this._item;
|
|
362
336
|
}
|
|
363
|
-
// The taxes of the
|
|
364
|
-
//
|
|
337
|
+
// The taxes of the package's product, which its price includes. A rate not greater than zero is
|
|
338
|
+
// no tax, as in Java, and is left out.
|
|
365
339
|
get taxes() {
|
|
366
340
|
return includedTaxes(this._product?.taxes ?? []);
|
|
367
341
|
}
|
|
368
342
|
// What one unit costs, taxes included: the offer price where the package is on offer, else the
|
|
369
|
-
// regular price. This is the catalog's price
|
|
370
|
-
//
|
|
371
|
-
// always has a price, so this is never guessed.
|
|
343
|
+
// regular price. This is the catalog's price to within tax rounding (a thousandth or two, which
|
|
344
|
+
// no one is shown). A buyable package always has a price, so nothing is guessed.
|
|
372
345
|
get unitPrice() {
|
|
373
346
|
return this.item.unitPriceWithTaxes;
|
|
374
347
|
}
|
|
375
|
-
// The regular price of a unit, taxes included
|
|
348
|
+
// The regular price of a unit, taxes included. Struck through when it is on offer
|
|
376
349
|
get regularUnitPrice() {
|
|
377
350
|
return this.item.regularUnitPriceWithTaxes;
|
|
378
351
|
}
|
|
@@ -380,7 +353,7 @@ export class CartLine {
|
|
|
380
353
|
return this._package.price?.isOnOffer ?? false;
|
|
381
354
|
}
|
|
382
355
|
// What this line costs before its taxes: its tax base. A bill that shows prices with taxes shows
|
|
383
|
-
// finalPrice
|
|
356
|
+
// finalPrice.
|
|
384
357
|
get amount() {
|
|
385
358
|
return this.item.amount;
|
|
386
359
|
}
|
|
@@ -394,34 +367,32 @@ export class CartLine {
|
|
|
394
367
|
get totalTax() {
|
|
395
368
|
return this.item.totalTax;
|
|
396
369
|
}
|
|
397
|
-
// What this line adds to the cart
|
|
398
|
-
//
|
|
370
|
+
// What this line adds to the cart and a customer pays for it: the unit price times the quantity,
|
|
371
|
+
// with its taxes already in it
|
|
399
372
|
get finalPrice() {
|
|
400
373
|
return this.item.finalPrice;
|
|
401
374
|
}
|
|
402
|
-
// This line as the protocol carries it.
|
|
403
|
-
//
|
|
375
|
+
// This line as the protocol carries it. CartEditor.build calls it and assigns the order. Taxes
|
|
376
|
+
// come out of the price on the way (see buildItem).
|
|
404
377
|
toShoppingListItem(shoppingListOrder) {
|
|
405
378
|
const price = this._package.price;
|
|
406
379
|
return buildItem(this._package.id, this._quantity, price?.finalPrice ?? 0, price?.regularPrice ?? 0, this._product?.taxes ?? [], shoppingListOrder);
|
|
407
380
|
}
|
|
408
381
|
}
|
|
409
|
-
// The taxes a price includes, as an item
|
|
410
|
-
//
|
|
382
|
+
// The taxes a price includes, as an item carries them: only those with a rate, as Java does with a
|
|
383
|
+
// product's taxes (TaxHelper.getTaxCodesAndRates drops the rest).
|
|
411
384
|
function includedTaxes(taxes) {
|
|
412
385
|
return Object.freeze(taxes.filter((tax) => Number.isFinite(tax.rate) && tax.rate > 0));
|
|
413
386
|
}
|
|
414
|
-
//
|
|
415
|
-
//
|
|
416
|
-
//
|
|
417
|
-
//
|
|
418
|
-
//
|
|
419
|
-
//
|
|
420
|
-
//
|
|
421
|
-
// storefront being told neither. It is flagged original, for the reason build gives.
|
|
422
|
-
//
|
|
387
|
+
// Where a price with taxes in it becomes a shopping list item, and so where CartEditor takes a tax
|
|
388
|
+
// out of a price, as Java ShoppingListItem.setDetailsFromTaxInclusivePrices does. The offer price
|
|
389
|
+
// and the regular price each have their taxes removed, and the price discount is the difference of
|
|
390
|
+
// those two, which is not the difference of the prices with taxes. The rest is as Java's
|
|
391
|
+
// ProductSelectionCard leaves a new item: no discount of its own, no description (that is for
|
|
392
|
+
// lines with no package), no stock keeping or expiry (a storefront is not told). It is flagged
|
|
393
|
+
// original, as in build.
|
|
423
394
|
// A price is a whole number of thousandths and each tax is rounded, so some prices cannot be
|
|
424
|
-
// reached
|
|
395
|
+
// reached. The item then has the nearest price (TaxHelper.removeTaxes).
|
|
425
396
|
function buildItem(packageId, quantity, offerPriceWithTaxes, regularPriceWithTaxes, taxes, shoppingListOrder) {
|
|
426
397
|
const included = includedTaxes(taxes);
|
|
427
398
|
const rates = TaxHelper.scaledRatesOf(included);
|