@peabody-soft/mbs-model-ts 1.7.0 → 1.8.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 (64) hide show
  1. package/dist/Attachment.js +2 -2
  2. package/dist/AttachmentBaseUrl.js +4 -4
  3. package/dist/AuthTokens.js +7 -7
  4. package/dist/Business.js +11 -10
  5. package/dist/BusinessIdentifier.js +3 -2
  6. package/dist/BusinessLink.js +3 -2
  7. package/dist/CartEditor.js +89 -81
  8. package/dist/Catalog.d.ts +19 -7
  9. package/dist/Catalog.js +121 -41
  10. package/dist/Constants.d.ts +1 -0
  11. package/dist/Constants.js +7 -5
  12. package/dist/ContainerNames.js +6 -6
  13. package/dist/Entity.js +4 -3
  14. package/dist/Enums.d.ts +1 -0
  15. package/dist/Enums.js +3 -2
  16. package/dist/Exceptions.js +2 -2
  17. package/dist/GeneralSettings.d.ts +5 -4
  18. package/dist/GeneralSettings.js +17 -15
  19. package/dist/HttpStatusCodes.js +7 -7
  20. package/dist/Ids.js +4 -5
  21. package/dist/Item.js +8 -5
  22. package/dist/LiveData.js +2 -2
  23. package/dist/Measures.js +3 -2
  24. package/dist/Message.js +1 -1
  25. package/dist/MockDatabase.js +6 -5
  26. package/dist/Model.js +166 -162
  27. package/dist/Order.js +10 -12
  28. package/dist/Package.d.ts +6 -2
  29. package/dist/Package.js +10 -5
  30. package/dist/PackageIdentifier.js +2 -2
  31. package/dist/Price.js +6 -5
  32. package/dist/PriceList.d.ts +13 -0
  33. package/dist/PriceList.js +34 -2
  34. package/dist/Product.d.ts +6 -2
  35. package/dist/Product.js +15 -10
  36. package/dist/Profile.js +15 -14
  37. package/dist/ProtoMap.js +189 -206
  38. package/dist/ServiceExceptions.js +6 -6
  39. package/dist/ServiceProxy.js +15 -14
  40. package/dist/ShoppingList.js +102 -100
  41. package/dist/SyncCache.js +76 -68
  42. package/dist/SyncKeys.js +5 -5
  43. package/dist/SyncState.js +14 -6
  44. package/dist/Tax.js +11 -10
  45. package/dist/TaxHelper.js +9 -8
  46. package/dist/TaxType.js +7 -6
  47. package/dist/UserProfile.js +7 -7
  48. package/dist/WorkingDays.js +21 -18
  49. package/dist/generated/BusinessSyncOutResponse.d.ts +4 -1
  50. package/dist/generated/Constants.d.ts +0 -4
  51. package/dist/generated/Constants.js +0 -4
  52. package/dist/generated/GeneralSettings.d.ts +5 -4
  53. package/dist/generated/GeneralSettings.js +8 -6
  54. package/dist/generated/Item.d.ts +4 -3
  55. package/dist/generated/Message.d.ts +5 -0
  56. package/dist/generated/OnlineStoreTheme.d.ts +33 -0
  57. package/dist/generated/OnlineStoreTheme.js +38 -0
  58. package/dist/generated/PriceList.d.ts +3 -2
  59. package/dist/generated/ProfileSyncOutRequest.d.ts +16 -15
  60. package/dist/generated/ProfileSyncOutResponse.d.ts +4 -6
  61. package/dist/generated/SalesOrderPayload.d.ts +6 -3
  62. package/dist/index.d.ts +4 -0
  63. package/dist/index.js +11 -8
  64. package/package.json +2 -2
@@ -2,10 +2,10 @@
2
2
  // A caller is expected to catch them and decide what to do: sign in again, retry, or show the shop
3
3
  // it has cached. They mirror Java com.merabills.repository.client.exception, so both clients of
4
4
  // this server describe a failure the same way.
5
- // Each carries the status code. The derived classes let a caller catch the one case it can handle
6
- // without testing a number.
7
- // A call reached the server and the server refused it. Thrown directly for a status code none of the
8
- // classes below names, so an unrecognised failure is still a ServiceException.
5
+ // Each ServiceException carries the status code. The derived classes let a caller catch the one
6
+ // case it can handle without testing a number.
7
+ // A call reached the server and the server refused it. Thrown directly for a status code that no
8
+ // class below covers, so an unrecognised failure is still a ServiceException.
9
9
  export class ServiceException extends Error {
10
10
  constructor(httpStatusCode, message) {
11
11
  super(message);
@@ -21,7 +21,7 @@ export class BadRequestException extends ServiceException {
21
21
  }
22
22
  }
23
23
  BadRequestException.STATUS_CODE = 400;
24
- // HTTP 401 - the credentials were missing, expired or not the caller's to present
24
+ // HTTP 401 - the credentials were missing, expired or did not belong to the caller
25
25
  export class UnauthorizedException extends ServiceException {
26
26
  constructor(message) {
27
27
  super(UnauthorizedException.STATUS_CODE, message);
@@ -80,4 +80,4 @@ export class NetworkConnectionException extends Error {
80
80
  this.name = new.target.name;
81
81
  }
82
82
  }
83
- // The status code each of these stands for is mapped in HttpStatusCodes.ts.
83
+ // HttpStatusCodes.ts maps each status code to its class.
@@ -13,12 +13,12 @@ import Routes from "./Routes";
13
13
  import { NetworkConnectionException } from "./ServiceExceptions";
14
14
  // A Service that reaches a real MeraBills server. Every call is one POST to the route named after
15
15
  // it, with the encoded request message as the body and the encoded response message as the answer.
16
- // This class translates nothing and reads no field of either.
17
- // One call is one page: the loop over a paged sync out belongs to whoever caches what comes back,
18
- // so each page is written as it arrives (see Model.businessSyncIn).
16
+ // This class only encodes and decodes the messages, and reads none of their fields.
17
+ // One call is one page. The caller that caches the pages runs the loop over a paged sync out, so
18
+ // that each page is written as it arrives (see Model.businessSyncIn).
19
19
  export default class ServiceProxy {
20
- // The base url is everything before the route, with or without a trailing slash or a path of its
21
- // own. The route is joined onto it, so a server reached under a prefix works.
20
+ // The base url is everything before the route. It may end in a slash and may include a path. The
21
+ // route is appended to it, so a server reached under a prefix works.
22
22
  constructor(serviceBaseUrl, fetchFunction = null) {
23
23
  this._serviceBaseUrl = serviceBaseUrl.replace(TRAILING_SLASHES, "");
24
24
  this._fetchFunction = fetchFunction;
@@ -35,23 +35,23 @@ export default class ServiceProxy {
35
35
  async createUser(request, authTokens) {
36
36
  return this.post(Routes.CREATE_USER, proto_CreateUserRequest, request, proto_ProfileSyncOutResponse, authTokens);
37
37
  }
38
- // The User is the whole request, there being no wrapper message for one
38
+ // The User is the whole request, since there is no wrapper message for it
39
39
  async updateUser(user, authTokens) {
40
40
  return this.post(Routes.UPDATE_USER, proto_User, user, proto_ProfileSyncOutResponse, authTokens);
41
41
  }
42
- // One call: encode, send, turn a refusal into its exception, decode. The message types are
43
- // passed in so every route is sent and read the same way.
42
+ // One call: encode, send, throw the matching exception on an error status, decode. The message
43
+ // types are passed in so every route is sent and read the same way.
44
44
  async post(route, requestType, request, responseType, authTokens) {
45
45
  const response = await this.send(route, requestType.toBinary(request), authTokens);
46
- // The body of a failure is not a response message, so it has to be dealt with before decoding
46
+ // The body of a failure is not a response message, so the status is checked before decoding
47
47
  if (isError(response.status))
48
48
  throwOnError(response.status, await ServiceProxy.readErrorMessage(route, response));
49
49
  return responseType.fromBinary(new Uint8Array(await response.arrayBuffer()));
50
50
  }
51
51
  async send(route, body, authTokens) {
52
52
  const headers = { [CONTENT_TYPE_HEADER]: CONTENT_TYPE_PROTOBUF };
53
- // An anonymous visitor gets no header, not an empty one. The protocol has no anonymous form of
54
- // the message, so the absence says nobody claims to be calling.
53
+ // An anonymous visitor sends no header rather than an empty one. The protocol has no anonymous
54
+ // form of the message, so a missing header means the caller is anonymous.
55
55
  if (authTokens !== null)
56
56
  headers[AUTHORIZATION_HEADER] = toBase64(buildAuthTokensBytes(authTokens));
57
57
  try {
@@ -79,9 +79,10 @@ export default class ServiceProxy {
79
79
  // Bound, because fetch called detached from globalThis is refused by browsers
80
80
  return platform.bind(globalThis);
81
81
  }
82
- // What the response says went wrong, if anything. This protocol has no error message of its own
83
- // yet, so a failure body can be anything (a stack trace, an HTML page) and only its start is
84
- // worth carrying. A body that cannot be read adds nothing to the status code.
82
+ // The message for a failed response: the route, plus the start of the body when there is one. The
83
+ // protocol defines no error message, so a failure body can be anything (a stack trace, an HTML
84
+ // page) and only its first MAX_ERROR_MESSAGE_LENGTH characters are kept. A body that cannot be
85
+ // read is left out.
85
86
  static async readErrorMessage(route, response) {
86
87
  let body;
87
88
  try {
@@ -8,21 +8,22 @@ import TaxHelper from "./TaxHelper";
8
8
  // A list of packages being bought: a shopping cart, or what an order contains.
9
9
  //
10
10
  // Every unit price on a list is WITHOUT taxes, with the taxes worked out on top (see
11
- // ShoppingListItem). Each item also offers the price WITH taxes that a customer is shown.
12
- // Everything that adds up (line totals, the list's total, a discount) already has its taxes in it.
13
- // The taxes inside a total are available by type (computedTaxes).
11
+ // ShoppingListItem). Each item also has the price WITH taxes that a customer is shown.
12
+ // Line totals, the list's total and a discount are all amounts with taxes included. computedTaxes
13
+ // gives the taxes inside a total, by type.
14
14
  //
15
- // Wire totals are authoritative: totalAmount is the number to display. The computed ones reproduce
16
- // Java's arithmetic to the unit for the items that are here, so they match the wire whenever the
17
- // items were sent. A list can arrive without its items, and then only the wire total covers it.
15
+ // Wire totals are authoritative: totalAmount is the number to display. The computed totals
16
+ // reproduce Java's arithmetic to the unit for the items that are here, so they match the wire
17
+ // whenever the items were sent. A list can arrive without its items, and then only the wire total
18
+ // is complete.
18
19
  class ShoppingList {
19
20
  // For ProtoMap, which sees totals still multiplied by Constants.AMOUNT_FACTOR.
20
21
  static fromScaledValues(isFinal, autoAddDeliveryCharge, autoAddServiceCharge, inventoryLocationId, priceListId, scaledTotalAmountBeforeDiscount, scaledDiscount, existingItemCount, deletedItemCount, existingItems, deletedItems) {
21
22
  return new ShoppingList(isFinal, autoAddDeliveryCharge, autoAddServiceCharge, inventoryLocationId, priceListId, amountFromScaledValue(scaledTotalAmountBeforeDiscount), amountFromScaledValue(scaledDiscount), existingItemCount, deletedItemCount, existingItems, deletedItems);
22
23
  }
23
- // What these items come to, taxes included and each item's own discount taken off: the sum of
24
- // their finalPrice, which a list of them has as totalAmountBeforeDiscount. Added in scaled values
25
- // and unscaled once, so adding unscaled numbers adds no rounding error.
24
+ // The sum of finalPrice over these items: taxes included, each item's discount taken off. A list
25
+ // of them has this as totalAmountBeforeDiscount. It adds in scaled values and unscales once, to
26
+ // avoid floating point rounding error.
26
27
  static sumOfFinalPrices(items) {
27
28
  return amountFromScaledValue(sumOf(items, (pricing) => pricing.finalPrice));
28
29
  }
@@ -39,17 +40,17 @@ class ShoppingList {
39
40
  this._existingItems = _existingItems;
40
41
  this._deletedItems = _deletedItems;
41
42
  }
42
- // True once the customer is done editing, after which the business may edit it. Only a final
43
- // list can have deleted items. It is never unset.
43
+ // True once the customer has finished editing, after which the business may edit it. Only a
44
+ // final list can have deleted items. It is never unset.
44
45
  get isFinal() {
45
46
  return this._isFinal;
46
47
  }
47
- // True when the delivery charge is a line of its own, carrying the reserved package id
48
- // ReservedPackage.DELIVERY_CHARGE_PACKAGE, rather than being added to the total
48
+ // True when the delivery charge is an item with the reserved package id
49
+ // ReservedPackage.DELIVERY_CHARGE_PACKAGE, rather than an amount added to the total
49
50
  get autoAddDeliveryCharge() {
50
51
  return this._autoAddDeliveryCharge;
51
52
  }
52
- // The same, for ReservedPackage.SERVICE_CHARGE_PACKAGE
53
+ // The same for the service charge, with ReservedPackage.SERVICE_CHARGE_PACKAGE
53
54
  get autoAddServiceCharge() {
54
55
  return this._autoAddServiceCharge;
55
56
  }
@@ -57,8 +58,10 @@ class ShoppingList {
57
58
  get inventoryLocationId() {
58
59
  return this._inventoryLocationId;
59
60
  }
60
- // The price list these prices came from, which is the one a storefront sells from: a cart with
61
- // none uses Constants.MERABILLS_ONLINE_PRICE_LIST_ID, the list Catalog.priceList holds.
61
+ // The id of the price list the cart is priced from. A storefront names
62
+ // Constants.MERABILLS_ONLINE_PRICE_LIST_ID, the list in Catalog.priceList, for every line, including
63
+ // one priced from that list's parent. CartEditor falls back to that id when the catalog has no
64
+ // price list.
62
65
  get priceListId() {
63
66
  return this._priceListId;
64
67
  }
@@ -66,15 +69,13 @@ class ShoppingList {
66
69
  get totalAmountBeforeDiscount() {
67
70
  return this._totalAmountBeforeDiscount;
68
71
  }
69
- // The discount on the whole list, not on one line. It comes off a total that already has its
70
- // taxes in it.
72
+ // The discount on the whole list, not on one line. It comes off a total that includes taxes.
71
73
  get discount() {
72
74
  return this._discount;
73
75
  }
74
- // What the customer owes, taxes included. Derived from the authoritative totals above, so it is
75
- // the number to display. Never less than zero, as in Java ShoppingList.getTotalAmount: a discount
76
- // greater than the total leaves nothing to pay. Worked out in scaled values, so there is no
77
- // floating point error.
76
+ // The amount to pay, taxes included, and the number to display. Calculated from the wire totals
77
+ // above. Never less than zero, as in Java ShoppingList.getTotalAmount: a discount greater than
78
+ // the total gives zero. It subtracts in scaled values to avoid floating point error.
78
79
  get totalAmount() {
79
80
  const total = amountToScaledValue(this._totalAmountBeforeDiscount) - amountToScaledValue(this._discount);
80
81
  return total > 0n ? amountFromScaledValue(total) : 0;
@@ -99,15 +100,15 @@ class ShoppingList {
99
100
  return this._deletedItems;
100
101
  }
101
102
  // The existing items that are packages bought from the catalog, in the order of the list
102
- // (shoppingListOrder). The server sends them by package id, so that differs from the order
103
- // received. The charge lines are in chargeItems, and the items taken out since the list was made
104
- // final are in removedItems.
103
+ // (shoppingListOrder). The server sends items in package id order, so this differs from the
104
+ // order received. The charge lines are in chargeItems, and the items deleted or replaced since
105
+ // the list was made final are in removedItems.
105
106
  get goodsItems() {
106
107
  return this.derived.goods;
107
108
  }
108
109
  // The existing items that are charge lines (the delivery charge and the service charge), in the
109
- // order of the list. The other two reserved ids, for a discount and a total, are summary lines a
110
- // list does not carry as items. They are discount and totalAmount.
110
+ // order of the list. A list does not carry the other two reserved ids, for a discount and a
111
+ // total, as items. It has them as discount and totalAmount.
111
112
  get chargeItems() {
112
113
  return this.derived.charges;
113
114
  }
@@ -115,23 +116,23 @@ class ShoppingList {
115
116
  get deliveryCharge() {
116
117
  return (this._existingItems.find((item) => item.reservedPackage === proto_ReservedPackage.DELIVERY_CHARGE_PACKAGE) ?? null);
117
118
  }
118
- // The service charge, in the same way
119
+ // The service charge item, or null when this list has none
119
120
  get serviceCharge() {
120
121
  return (this._existingItems.find((item) => item.reservedPackage === proto_ReservedPackage.SERVICE_CHARGE_PACKAGE) ?? null);
121
122
  }
122
- // The items the business took out of the list after the customer made it final, or replaced with
123
- // a changed one, as the customer ordered them. It is deletedItems in the order of the list. A
124
- // deleted charge line is among them, and is told apart by reservedPackage.
123
+ // The items the business deleted or replaced after the customer made the list final, as the
124
+ // customer ordered them. This is deletedItems in the order of the list. A deleted charge line is
125
+ // included, and reservedPackage identifies it.
125
126
  get removedItems() {
126
127
  return this.derived.removed;
127
128
  }
128
- // What the business has done to one line of this list since the customer made it final. It is
129
- // ShoppingListItem.change, except for a legacy order, written by a storefront that did not set
130
- // isOriginal (CartEditor, up to 1.4.0). Every line of such an order reads as added by the
131
- // business. A final list with no original, modified or deleted line is taken to be one, since no
132
- // change the business can make leaves that (adding a line keeps the others original, taking one
133
- // out leaves it in deletedItems, changing one leaves the old line there too).
134
- // Show a list's marks with this, not ShoppingListItem.change.
129
+ // What the business has done to one line of this list since the customer made it final. This is
130
+ // ShoppingListItem.change, except for a legacy order: one written by a storefront that did not
131
+ // set isOriginal (CartEditor, up to 1.4.0). Every line of such an order has change ADDED. A
132
+ // final list with no original, modified or deleted line is taken to be a legacy order, because
133
+ // no change by the business can produce that state: adding a line keeps the others original,
134
+ // deleting one leaves it in deletedItems, and changing one leaves the old line there too.
135
+ // Use this to show a list's marks, not ShoppingListItem.change.
135
136
  changeOf(item) {
136
137
  const change = item.change;
137
138
  if (change !== ItemChange.ADDED)
@@ -139,12 +140,12 @@ class ShoppingList {
139
140
  return this.derived.isUntouchedLegacyOrder ? ItemChange.NONE : ItemChange.ADDED;
140
141
  }
141
142
  // Sum of finalPrice over existingItems, taxes included. Not authoritative (the wire total is),
142
- // and only covers the whole list when existingItems.length equals existingItemCount.
143
+ // and complete only when existingItems.length equals existingItemCount.
143
144
  get computedTotal() {
144
145
  return ShoppingList.sumOfFinalPrices(this._existingItems);
145
146
  }
146
- // The same for the goods alone: what the packages come to, without the charges on top of them.
147
- // This is what the free delivery threshold is measured against.
147
+ // The same for goodsItems alone: what the packages come to, without the charges. The free
148
+ // delivery threshold is compared with this.
148
149
  get computedGoodsTotal() {
149
150
  return ShoppingList.sumOfFinalPrices(this.goodsItems);
150
151
  }
@@ -154,12 +155,12 @@ class ShoppingList {
154
155
  return amountFromScaledValue(sumOf(this._existingItems, (pricing) => pricing.totalTax));
155
156
  }
156
157
  // The taxes inside computedTotal, one entry per TYPE of tax, in the order of the types, added up
157
- // over every item. A type none of the items has is not here. An amount can be zero, for a type
158
+ // over every item. A type none of the items has is not listed. An amount can be zero, for a type
158
159
  // that applies only to free items. Java ShoppingList.getTotalTaxesByTaxCode leaves such a type
159
160
  // out (JavaVectorsTest pins that), and a bill shows no line for it.
160
- // A discount does not change the taxes: it comes off a price that already has them in it.
161
- // Worked out once, like derived but kept apart: this prices the items, and an item that cannot be
162
- // priced must not stop the list's lines being listed.
161
+ // A discount does not change the taxes, because it comes off a price that already includes them.
162
+ // Computed once, like derived but stored apart from it: this prices the items, and an item that
163
+ // cannot be priced must not stop the list's lines from being listed.
163
164
  get computedTaxes() {
164
165
  const existing = TAXES.get(this);
165
166
  if (existing !== undefined)
@@ -177,24 +178,24 @@ class ShoppingList {
177
178
  TAXES.set(this, created);
178
179
  return created;
179
180
  }
180
- // What the existing items come to at their regular prices, with their taxes and none of their own
181
- // discounts taken off: the total had nothing been on offer. Compared with totalAmount it gives
182
- // what the customer saved. Same caveat as computedTotal.
181
+ // The existing items at their regular prices, taxes included and no item discounts taken off:
182
+ // the total if nothing had been on offer. Its difference from totalAmount is what the customer
183
+ // saved (see savings). Same caveat as computedTotal.
183
184
  get computedTotalWithoutDiscounts() {
184
185
  return amountFromScaledValue(sumOf(this._existingItems, (pricing) => pricing.finalPriceWithoutDiscounts));
185
186
  }
186
187
  // What the offers on the items, the discounts on them and the discount on the list took off,
187
- // taxes included, shown on a bill as savings. A waived delivery charge counts too, being an item
188
- // with a price discount like any other, as in the merchant app's total row. So a basket can save
189
- // more once delivery is in the list. Zero when nothing was taken off, never negative. Same caveat
190
- // as computedTotal: it needs the items.
188
+ // taxes included. A bill shows it as savings. A waived delivery charge counts too, as an item
189
+ // with a price discount like any other, as in the merchant app's total row. So the savings can
190
+ // rise once the delivery charge is in the list. Zero when nothing was taken off, never negative.
191
+ // Same caveat as computedTotal: it needs the items.
191
192
  get savings() {
192
193
  const saved = sumOf(this._existingItems, (pricing) => pricing.finalPriceWithoutDiscounts) -
193
194
  amountToScaledValue(this.totalAmount);
194
195
  return saved > 0n ? amountFromScaledValue(saved) : 0;
195
196
  }
196
- // Worked out once, since the list never changes, so a UI reads the same array every time (a
197
- // stable dependency) and the list is not sorted at every render
197
+ // Computed once, because the list never changes. A UI gets the same array on every read (a
198
+ // stable dependency), and the items are not sorted on every render.
198
199
  get derived() {
199
200
  const existing = DERIVED.get(this);
200
201
  if (existing !== undefined)
@@ -221,13 +222,13 @@ export default ShoppingList;
221
222
  //
222
223
  // THE PRICE IS STORED WITHOUT TAXES: unitPrice and priceDiscount are before taxes, which are worked
223
224
  // out on top, as in Java ShoppingListItem and ShoppingListItem.proto. A customer sees prices WITH
224
- // taxes, so CartEditor takes them out on the way in and the *WithTaxes members put them back. Show
225
- // those, never unitPrice, which is not what a customer is charged for a unit.
225
+ // taxes, so CartEditor takes them out on the way in and the *WithTaxes members add them back. Show
226
+ // those, never unitPrice, which leaves out taxes and so is not what a customer is charged.
226
227
  //
227
- // The arithmetic is Java's to the unit and rounds in the same places: the tax base (unit price
228
- // times quantity) to a whole amount, then each tax on its own, half up (see TaxHelper). An amount
229
- // of Transaction.MAX_AMOUNT or more throws an InvalidArgumentException when something first reads
230
- // a price off the item, as in Java (see validatePricing).
228
+ // The arithmetic matches Java to the unit and rounds in the same places: the tax base (unit price
229
+ // times quantity) to a whole amount, then each tax separately, half up (see TaxHelper). An amount
230
+ // of Transaction.MAX_AMOUNT or more throws an InvalidArgumentException the first time a price is
231
+ // read off the item, as in Java (see validatePricing).
231
232
  export class ShoppingListItem {
232
233
  // For ProtoMap, which sees a quantity multiplied by Constants.QUANTITY_FACTOR and prices
233
234
  // multiplied by Constants.AMOUNT_FACTOR.
@@ -254,11 +255,11 @@ export class ShoppingListItem {
254
255
  return this._packageId;
255
256
  }
256
257
  // True when this line is a charge/summary (delivery, service charge, discount, or total) rather
257
- // than a catalog package. A UI must label such lines itself.
258
+ // than a catalog package. Such a line has no catalog name, so a UI must label it.
258
259
  get isReservedPackage() {
259
260
  return Ids.isReservedPackageId(this._packageId);
260
261
  }
261
- // Which of the four that is, and null for a catalog package
262
+ // Which of the four reserved ids this is, or null for a catalog package
262
263
  get reservedPackage() {
263
264
  return this.isReservedPackage
264
265
  ? Number(Ids.getLowPart(this._packageId))
@@ -276,22 +277,23 @@ export class ShoppingListItem {
276
277
  get shoppingListOrder() {
277
278
  return this._shoppingListOrder;
278
279
  }
279
- // True when this line was created before the list was made final. Everything a customer puts in
280
- // a cart is, which separates it from what the business adds or changes afterwards.
280
+ // True when this line was created before the list was made final. Every line a customer puts in
281
+ // a cart is original. Lines the business adds or changes afterwards are not.
281
282
  get isOriginal() {
282
283
  return this._isOriginal;
283
284
  }
284
- // True when this line has been deleted or replaced since the list was made final. Original and
285
- // modified: deleted (and in ShoppingList.deletedItems). Not original but modified: replaced an
286
- // original one. Neither: added by the business.
285
+ // True when the business deleted this line, or made it to replace another, since the list was
286
+ // made final. Original and modified: deleted (and in ShoppingList.deletedItems). Not original but
287
+ // modified: the replacement of an original line. Neither original nor modified: added by the
288
+ // business.
287
289
  get isModified() {
288
290
  return this._isModified;
289
291
  }
290
292
  // What the business has done to this line since the customer made the list final, read off
291
- // isOriginal and isModified as the merchant app does: a "+" beside a line it added, an "x" beside
292
- // one it changed, and the old line listed apart as removed. This reads the line's own flags only.
293
- // To show a list use ShoppingList.changeOf, which also knows that an order written before 1.5.0
294
- // flagged its lines wrongly.
293
+ // isOriginal and isModified as the merchant app does: it marks an added line with "+", a changed
294
+ // line with "x", and lists the old line apart as removed. This uses the line's own flags only.
295
+ // To show a list use ShoppingList.changeOf, which also handles orders written before 1.5.0 that
296
+ // flagged their lines wrongly.
295
297
  get change() {
296
298
  if (this._isOriginal)
297
299
  return this._isModified ? ItemChange.REMOVED : ItemChange.NONE;
@@ -326,10 +328,10 @@ export class ShoppingListItem {
326
328
  return amountFromScaledValue(pricingOf(this).regularUnitPrice);
327
329
  }
328
330
  // What one unit costs a customer: unitPrice with the taxes on it, the offer price when the
329
- // package is on offer. It is the price the customer saw when adding it, and the one to show
330
- // wherever the item is shown. Each tax is rounded, so not every price with taxes can be reached.
331
- // When it cannot, this is the nearest that can, which differs from the catalog's by a thousandth
332
- // or so (Java ShoppingListItem.setDetailsFromTaxInclusivePrices).
331
+ // package is on offer. It is the price the customer saw when adding it, and the one to show for
332
+ // the item. Each tax is rounded, so not every price with taxes can be reached. When it cannot,
333
+ // this is the nearest price that can, which differs from the catalog's by a thousandth or so
334
+ // (Java ShoppingListItem.setDetailsFromTaxInclusivePrices).
333
335
  get unitPriceWithTaxes() {
334
336
  return amountFromScaledValue(pricingOf(this).unitPriceWithTaxes);
335
337
  }
@@ -344,7 +346,7 @@ export class ShoppingListItem {
344
346
  return amountFromScaledValue(pricingOf(this).taxBase);
345
347
  }
346
348
  // Each tax on this line, in the order of taxes: that tax's rate on amount, rounded half up. All
347
- // zero when amount is, since a tax only applies to something that costs. Included in finalPrice.
349
+ // zero when amount is, since a tax applies only to an amount above zero. Included in finalPrice.
348
350
  get taxAmounts() {
349
351
  return pricingOf(this).unscaledTaxes;
350
352
  }
@@ -357,20 +359,20 @@ export class ShoppingListItem {
357
359
  get finalPrice() {
358
360
  return amountFromScaledValue(pricingOf(this).finalPrice);
359
361
  }
360
- // What this line would add at the package's regular price, taxes included, with none of its own
361
- // discount taken off: what a customer would have paid had nothing been on offer. Strike it
362
+ // What this line would add at the package's regular price, taxes included, with the line's
363
+ // discount not taken off: what a customer would have paid if nothing had been on offer. Strike it
362
364
  // through beside finalPrice when it is greater (Java calculateFinalPriceWithoutDiscounts).
363
365
  get finalPriceWithoutDiscounts() {
364
366
  return amountFromScaledValue(pricingOf(this).finalPriceWithoutDiscounts);
365
367
  }
366
- // True when this line costs less than at the regular price with none of its discount, so
367
- // finalPriceWithoutDiscounts is worth striking through beside finalPrice
368
+ // True when finalPrice is less than finalPriceWithoutDiscounts, so the latter can be struck
369
+ // through beside finalPrice
368
370
  get isDiscounted() {
369
371
  const pricing = pricingOf(this);
370
372
  return pricing.finalPriceWithoutDiscounts > pricing.finalPrice;
371
373
  }
372
374
  // True when this line costs nothing though it would have cost something: a delivery charge that
373
- // has been waived, a package given away. A bill reads FREE, with finalPriceWithoutDiscounts
375
+ // has been waived, a package given away. A bill shows FREE, with finalPriceWithoutDiscounts
374
376
  // struck through.
375
377
  get isFree() {
376
378
  const pricing = pricingOf(this);
@@ -378,8 +380,8 @@ export class ShoppingListItem {
378
380
  }
379
381
  }
380
382
  // What the business has done to a line of an order since the customer made it final. The merchant
381
- // app marks the second and third with a "+" and an "x" and explains them in a legend, and lists the
382
- // fourth under "Removed by merchant".
383
+ // app marks ADDED with a "+" and MODIFIED with an "x", explains them in a legend, and lists
384
+ // REMOVED under "Removed by merchant".
383
385
  export var ItemChange;
384
386
  (function (ItemChange) {
385
387
  // As the customer ordered it. Every line of a list that is not final yet is this.
@@ -393,11 +395,11 @@ export var ItemChange;
393
395
  // ShoppingList.deletedItems.
394
396
  ItemChange[ItemChange["REMOVED"] = 3] = "REMOVED";
395
397
  })(ItemChange || (ItemChange = {}));
396
- // Kept beside the item so it stays a plain value: two equal items compare equal whether or not
397
- // either has been priced.
398
+ // Held in a WeakMap rather than a field, so the item stays a plain value: two equal items compare
399
+ // equal whether or not either has been priced.
398
400
  const PRICINGS = new WeakMap();
399
401
  const DERIVED = new WeakMap();
400
- // The taxes of a list by type, which only a list whose items can be priced has
402
+ // The taxes of a list by type. See computedTaxes for why this is stored apart from DERIVED.
401
403
  const TAXES = new WeakMap();
402
404
  function pricingOf(item) {
403
405
  const existing = PRICINGS.get(item);
@@ -423,26 +425,26 @@ function pricingOf(item) {
423
425
  finalPrice: discounted > 0n ? discounted : 0n,
424
426
  finalPriceWithoutDiscounts: TaxHelper.addTaxes(scaledProduct(regularUnitPrice, item.quantity), rates),
425
427
  };
426
- // Before it is kept, so that an item that cannot be priced is refused every time it is asked
428
+ // Validate before storing, so an item that cannot be priced throws on every read
427
429
  validateAmounts(created);
428
430
  PRICINGS.set(item, created);
429
431
  return created;
430
432
  }
431
433
  // Throws an InvalidArgumentException unless every amount the item works out is one the protocol can
432
- // carry. CartEditor calls it before keeping a line. Anything else finds out when it first reads a
433
- // price off the item.
434
+ // carry. CartEditor calls it before storing a line's quantity. Other code gets the exception the
435
+ // first time it reads a price off the item.
434
436
  export function validatePricing(item) {
435
437
  pricingOf(item);
436
438
  }
437
- // Java Transaction.validateAmount refuses an amount of MAX_AMOUNT or more. This applies it to the
439
+ // Java Transaction.validateAmount rejects an amount of MAX_AMOUNT or more. This applies it to the
438
440
  // amounts Java checks on an item: each tax and the final price (recalculate), and the results of
439
441
  // calculateFinalPriceWithoutDiscounts, calculateUnitPriceWithTaxes and
440
- // calculateUnitRegularPriceWithTaxes. They are checked together, once, where the item is priced, so
441
- // an item answers as Java does however it was made (a cart line or the wire) and hands out no
442
+ // calculateUnitRegularPriceWithTaxes. They are checked together, once, when the item is priced.
443
+ // So an item behaves as in Java however it was made (a cart line or the wire), and never returns a
442
444
  // number the protocol cannot carry.
443
- // The amounts are not independent. finalPriceWithoutDiscounts is the cost at the regular price,
444
- // never less than the offer price, and each tax and the final price are parts of it. So none of
445
- // them is over the limit unless that cost is (test/JavaVectors.ts shows this item by item).
445
+ // The amounts are related. finalPriceWithoutDiscounts is the cost at the regular price, never less
446
+ // than the offer price, and each tax and the final price are parts of it. So none of them is over
447
+ // the limit unless that cost is (test/JavaVectors.ts shows this item by item).
446
448
  function validateAmounts(pricing) {
447
449
  const amounts = [
448
450
  ...pricing.taxes.map((tax) => ["tax", tax]),
@@ -458,17 +460,17 @@ function validateAmounts(pricing) {
458
460
  // A scaled amount times a quantity, rounded to a whole amount as Java does:
459
461
  // Math.round(((double) amount) * NumberHelper.intToFloat(scaledQuantity, QUANTITY_FACTOR)). The
460
462
  // quantity is a FLOAT there, so it is one here (fround), and a quantity like 0.7 is not quite 0.7
461
- // in either. That matters only where the product lands on a half.
463
+ // in either. That changes the result only when the product is a half.
462
464
  function scaledProduct(scaledAmount, quantity) {
463
465
  const scaledQuantity = quantityToScaledValue(quantity);
464
466
  const floatQuantity = Math.fround(Math.fround(scaledQuantity) / proto_Constants.QUANTITY_FACTOR);
465
467
  const product = Math.round(Number(scaledAmount) * floatQuantity);
466
468
  return Number.isFinite(product) ? BigInt(product) : 0n;
467
469
  }
468
- // A copy in the order of the list. Java sorts the existing items by shoppingListOrder before
469
- // showing a list (ShoppingListAdapter), and an order arrives with its items in package id order.
470
- // The merchant app shows the removed items in package id order, having no better one. This library
471
- // uses the order they had, which is the customer's.
470
+ // A copy sorted by shoppingListOrder. Java sorts the existing items this way before showing a
471
+ // list (ShoppingListAdapter), and an order arrives with its items in package id order. The
472
+ // merchant app shows the removed items in package id order. This library uses their
473
+ // shoppingListOrder, which is the order the customer made.
472
474
  function sortedByOrder(items) {
473
475
  return [...items].sort((left, right) => left.shoppingListOrder - right.shoppingListOrder);
474
476
  }