@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.
Files changed (52) hide show
  1. package/dist/Address.js +8 -11
  2. package/dist/Attachment.js +7 -8
  3. package/dist/AttachmentBaseUrl.js +10 -10
  4. package/dist/AuthTokens.js +12 -7
  5. package/dist/Business.js +19 -19
  6. package/dist/BusinessIdentifier.js +2 -2
  7. package/dist/BusinessLink.js +16 -16
  8. package/dist/BusinessProfile.js +4 -4
  9. package/dist/CartEditor.js +137 -166
  10. package/dist/Catalog.js +19 -25
  11. package/dist/Constants.js +10 -10
  12. package/dist/ContainerNames.js +10 -11
  13. package/dist/Entity.js +11 -9
  14. package/dist/Enums.js +2 -2
  15. package/dist/Exceptions.js +2 -2
  16. package/dist/GeneralSettings.js +23 -21
  17. package/dist/HttpStatusCodes.js +10 -11
  18. package/dist/Ids.js +24 -30
  19. package/dist/Item.js +6 -7
  20. package/dist/LatLong.js +3 -3
  21. package/dist/LiveData.js +6 -6
  22. package/dist/Measures.js +2 -2
  23. package/dist/Message.js +6 -6
  24. package/dist/MockDatabase.js +2 -2
  25. package/dist/Model.js +294 -365
  26. package/dist/Order.js +20 -17
  27. package/dist/Package.js +7 -8
  28. package/dist/PackageIdentifier.js +2 -2
  29. package/dist/PaymentMethodDetail.js +4 -4
  30. package/dist/Photo.js +6 -7
  31. package/dist/Price.js +8 -9
  32. package/dist/PriceList.js +2 -2
  33. package/dist/Product.js +17 -22
  34. package/dist/ProductCategory.js +3 -4
  35. package/dist/Profile.js +26 -27
  36. package/dist/ProtoMap.d.ts +3 -0
  37. package/dist/ProtoMap.js +288 -294
  38. package/dist/Routes.js +3 -3
  39. package/dist/ServiceExceptions.js +11 -12
  40. package/dist/ServiceProxy.js +19 -23
  41. package/dist/ShoppingList.js +112 -130
  42. package/dist/SyncCache.d.ts +3 -0
  43. package/dist/SyncCache.js +92 -75
  44. package/dist/SyncKeys.js +8 -8
  45. package/dist/SyncState.js +5 -5
  46. package/dist/Tax.js +16 -21
  47. package/dist/TaxHelper.js +22 -30
  48. package/dist/TaxType.js +8 -9
  49. package/dist/UserProfile.js +4 -4
  50. package/dist/WorkingDays.js +15 -15
  51. package/dist/index.js +9 -9
  52. package/package.json +1 -1
@@ -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 rather than
10
- // package ids, quantities rather than quantities multiplied by Constants.QUANTITY_FACTOR, and
11
- // prices taken off the catalog rather than worked out and passed in. Get one from
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 deliberately the only mutable thing in this object model: it is a scratch copy of
15
- // the cart, not the cart. Nothing here writes anything - editing an editor changes no Business, no
16
- // store and nothing on the server until a built list is pushed. It is also a snapshot: it reads the
17
- // catalog, settings and cart once, at construction, so a sync arriving mid-edit does not move
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. The price of a package includes the taxes of its product, and
21
- // the delivery charge the taxes the settings name for it, so what a customer sees in the catalog
22
- // is what they pay, and every total below is a total with its taxes already in it. A shopping list
23
- // item is the other way round - it stores a price WITHOUT taxes and works the taxes out on top -
24
- // so building one takes the taxes out of the price, exactly as Java ShoppingListItem
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 of these; this takes the three things it needs
30
- // rather than the Business itself, so a caller can build a cart against a catalog and settings
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 now, not off what the cart was holding: a cart lives
34
- // until it is ordered, the merchant may have repriced in between, and what an order freezes is
35
- // the price at the moment it is placed. Lines the catalog can no longer price - a package
36
- // withdrawn, disabled, or left without a price - are dropped, and left in unavailableItems for a
37
- // UI to tell the customer about. So is a line that cannot be bought as it is, which setQuantity
38
- // would have refused, a cart that was read not being known to be one this class could have made:
39
- // a quantity that is nothing or is not one, a fraction of what is not sold loose, more than the
40
- // wire carries, or that comes to more than the protocol can at today's price, and any line past
41
- // the most a cart holds (the first lines are kept, being in the order picked). The charge lines
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, which is what keeps a cart in the order the customer filled it and what
49
- // gives each line its shoppingListOrder
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
- // In the order of the list, which is the order the customer picked them in: the goods items are in it, and the items
53
- // of the list are not, the server sending those by package id, so a cart that is read back would reorder itself
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 the order it was picked in, each line priced as it would be
64
- // bought right now. Rebuilt on every read, so a line held across an edit is stale.
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 this editor started from that its catalog can no longer price - see the
76
- // note on the constructor. Empty for a cart built from nothing, and never added to by an edit.
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
- // How many lines the cart has, which is what a cart badge counts - not how many units are in it
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. A package a customer cannot
99
- // buy - one disabled, or priced in no list this storefront sells from - is refused, as is a
100
- // fractional quantity of a package that is not sold loose, and a quantity past what the wire can
101
- // carry. Whether a package is sold loose is the catalog's word, and not that of the Package
102
- // passed in, which may be one held from before the catalog changed. Returns this, so edits chain.
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 is nothing on the wire - a line of zero, which would cost nothing
106
- // - so it takes the line out, as zero does
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 already holds of this package, which is what a quantity stepper does.
115
- // A negative amount takes away, and never below nothing.
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 already taken off: the sum of the final
127
- // prices of the lines, and what a customer sees to be the price of what is in the cart. Delivery
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, which is what a bill shows as savings: what they would
133
- // have come to at their regular prices, less what they come to. Zero when nothing is on offer.
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. They are part of the total and are not
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 - a bill shows a line for each
144
- // that is not zero. withDeliveryCharge is as for total: the taxes in the delivery charge are
145
- // counted only when it is.
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 at all. False means no delivery charge exists to add, whatever
150
- // build() is asked for, and a UI has no fulfillment choice to offer either.
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 would cost, taxes included: the business's flat charge, or zero
155
- // where it has been waived by the cart reaching freeDeliveryTotal, or where the business delivers
156
- // free anyway. Always zero when the business does not deliver.
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 - the price a bill strikes through when it reads FREE.
161
- // Zero where the business delivers free anyway, in which case there is nothing to strike through.
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, whether because the business never charges or
166
- // because this cart has reached the threshold
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 would have to add for delivery to stop being charged, which is the
171
- // "add X more for free delivery" note. Zero once it is already free, and null when the business
172
- // has set no threshold, so there is no amount that would ever make it free.
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
- // Tax on the delivery charge: how much of deliveryCharge is the taxes GeneralSettings
185
- // .deliveryChargeTaxes carries - which are the business's own and have nothing to do with a
186
- // product's. Included in the charge, not added to it. Zero when delivery is free, there being
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, the way a cart
192
- // page leaves it out and a checkout page puts it in - the same choice build() takes. It is the
193
- // total of the list build() answers, so it is the total an order placed from it carries.
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: what Model.updateCartLocalOnly and Model.updateCart take, and
198
- // what a later Model.createOrder turns into an order. It is also the list to SHOW - every price,
199
- // total and tax a cart or a checkout page displays can be read off it, which is how a placed
200
- // order is shown too.
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
- // Each line is an item that stores the price without taxes, as Java does, and is flagged
203
- // original: a customer's lines are created before the list is final (Java ShoppingList.addItem
204
- // sets isOriginal to !isFinal(), and the order becomes final only after its items are in it), and
205
- // an item that is not original is what the merchant app marks as added by the business.
206
- //
207
- // withDeliveryCharge decides whether the delivery charge rides along as a line of its own,
208
- // carrying ReservedPackage.DELIVERY_CHARGE_PACKAGE and flagged by
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 written off the lines being sent, the way ShoppingList.proto defines it: the
230
- // sum of their final prices, tax included. There is no list-wide discount - an offer comes off
231
- // a line, and a storefront gives no other.
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 of it, at what it costs now (charge - the regular charge, or
235
- // zero once waived), with the waived amount as its price discount - the same shape as a package
236
- // bought on offer, which is what lets a bill render it with no special case beyond its label.
237
- // Null when the business does not deliver. The charge in the settings includes the settings'
238
- // delivery charge taxes, so they come out of it like those of any other price.
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 rather than treating it as
245
- // a refund, and so does this. Zero when the business does not deliver.
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 against a cart whose goods come to this much, which is the whole of the policy: the
252
- // regular charge, or nothing once the goods reach the threshold. Java ShoppingCartViewModel
253
- // .calculateDeliveryCharge compares the same total, taxes included and the delivery line left out.
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, whatever the cart totals
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: a line
269
- // that cannot be kept is not a caller's mistake to be refused but one for unavailableItems
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 to that:
285
- // NumberHelper.floatToInt), so the least there is is a thousandth. This is a quantity as that,
286
- // and what is kept is the quantity the wire will hold, so getQuantity never reports one the
287
- // order will not have.
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, which is what the wire will hold of it - or an
294
- // InvalidArgumentException saying why the cart cannot have it: the package is not one the catalog
295
- // sells, or it is not sold loose and the quantity is a fraction, or the quantity, or what it comes
296
- // to, is more than the protocol carries, or the cart is full. All of it is read off the catalog's
297
- // package, which is what the order will be priced from, and not off the caller's.
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
- // This is the most of one package a cart can hold, and it is refused here rather than silently
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
- // of them (Java ShoppingList.addItem counts it like any other, and a server refuses a list a
310
- // line over). So where the business delivers, one place is kept for it: a cart that could be
311
- // filled to the limit could not be ordered for delivery.
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, which is done before the line is kept rather than when
317
- // a list cannot be written
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 is the most that a quantity multiplied by
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
- // - with the prices the catalog gives it now, taxes included. A read-only view over what
336
- // CartEditor holds, rebuilt on every read of CartEditor.lines rather than kept.
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 here is read off item, the line as a shopping list would store it, so the cart page
339
- // shows exactly what the order will: no price is worked out twice.
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 only for a package whose product is not in the catalog, which a buyable package's never is
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 the way an order shows it. Its place in the list is not meaningful here.
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 product this package belongs to, which its price includes. A rate that is not
364
- // greater than zero is no tax, as in Java, and is not here.
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, to within the rounding of taxes that a price with
370
- // taxes in it is subject to (a thousandth or two, which no one is shown). A buyable package
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 - what is struck through when it is on offer
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, not this.
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, and what a customer pays for it: the price of a unit
398
- // multiplied by the quantity, with its taxes already in it
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. Only CartEditor.build calls this, which is what assigns
403
- // the order. Taxes come out of the price on the way (see buildItem).
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 is to carry them: only those with a rate, which is what
410
- // Java does with the taxes of a product (TaxHelper.getTaxCodesAndRates drops the rest).
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
- // The one place a price with taxes in it becomes a shopping list item, and so the one place
415
- // CartEditor takes a tax out of a price. The item stores the prices without them, as Java
416
- // ShoppingListItem.setDetailsFromTaxInclusivePrices does: the offer price and the regular price
417
- // each have their taxes removed, and what the offer takes off is the difference of the two - which
418
- // is not the difference of the prices with taxes. Everything but the price is as Java's
419
- // ProductSelectionCard leaves a new item: unit quantity or the customer's, no discount of its own,
420
- // no description (that is for lines that have no package), and no stock keeping or expiry, a
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: the item then has the price nearest to it (TaxHelper.removeTaxes).
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);