@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.
- package/dist/Attachment.js +2 -2
- package/dist/AttachmentBaseUrl.js +4 -4
- package/dist/AuthTokens.js +7 -7
- package/dist/Business.js +11 -10
- package/dist/BusinessIdentifier.js +3 -2
- package/dist/BusinessLink.js +3 -2
- package/dist/CartEditor.js +89 -81
- package/dist/Catalog.d.ts +19 -7
- package/dist/Catalog.js +121 -41
- package/dist/Constants.d.ts +1 -0
- package/dist/Constants.js +7 -5
- package/dist/ContainerNames.js +6 -6
- package/dist/Entity.js +4 -3
- package/dist/Enums.d.ts +1 -0
- package/dist/Enums.js +3 -2
- package/dist/Exceptions.js +2 -2
- package/dist/GeneralSettings.d.ts +5 -4
- package/dist/GeneralSettings.js +17 -15
- package/dist/HttpStatusCodes.js +7 -7
- package/dist/Ids.js +4 -5
- package/dist/Item.js +8 -5
- package/dist/LiveData.js +2 -2
- package/dist/Measures.js +3 -2
- package/dist/Message.js +1 -1
- package/dist/MockDatabase.js +6 -5
- package/dist/Model.js +166 -162
- package/dist/Order.js +10 -12
- package/dist/Package.d.ts +6 -2
- package/dist/Package.js +10 -5
- package/dist/PackageIdentifier.js +2 -2
- package/dist/Price.js +6 -5
- package/dist/PriceList.d.ts +13 -0
- package/dist/PriceList.js +34 -2
- package/dist/Product.d.ts +6 -2
- package/dist/Product.js +15 -10
- package/dist/Profile.js +15 -14
- package/dist/ProtoMap.js +189 -206
- package/dist/ServiceExceptions.js +6 -6
- package/dist/ServiceProxy.js +15 -14
- package/dist/ShoppingList.js +102 -100
- package/dist/SyncCache.js +76 -68
- package/dist/SyncKeys.js +5 -5
- package/dist/SyncState.js +14 -6
- package/dist/Tax.js +11 -10
- package/dist/TaxHelper.js +9 -8
- package/dist/TaxType.js +7 -6
- package/dist/UserProfile.js +7 -7
- package/dist/WorkingDays.js +21 -18
- package/dist/generated/BusinessSyncOutResponse.d.ts +4 -1
- package/dist/generated/Constants.d.ts +0 -4
- package/dist/generated/Constants.js +0 -4
- package/dist/generated/GeneralSettings.d.ts +5 -4
- package/dist/generated/GeneralSettings.js +8 -6
- package/dist/generated/Item.d.ts +4 -3
- package/dist/generated/Message.d.ts +5 -0
- package/dist/generated/OnlineStoreTheme.d.ts +33 -0
- package/dist/generated/OnlineStoreTheme.js +38 -0
- package/dist/generated/PriceList.d.ts +3 -2
- package/dist/generated/ProfileSyncOutRequest.d.ts +16 -15
- package/dist/generated/ProfileSyncOutResponse.d.ts +4 -6
- package/dist/generated/SalesOrderPayload.d.ts +6 -3
- package/dist/index.d.ts +4 -0
- package/dist/index.js +11 -8
- 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
|
|
6
|
-
// without testing a number.
|
|
7
|
-
// A call reached the server and the server refused it. Thrown directly for a status code
|
|
8
|
-
//
|
|
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
|
|
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
|
-
//
|
|
83
|
+
// HttpStatusCodes.ts maps each status code to its class.
|
package/dist/ServiceProxy.js
CHANGED
|
@@ -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
|
|
17
|
-
// One call is one page
|
|
18
|
-
//
|
|
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
|
|
21
|
-
//
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
54
|
-
// the message, so
|
|
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
|
-
//
|
|
83
|
-
//
|
|
84
|
-
//
|
|
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 {
|
package/dist/ShoppingList.js
CHANGED
|
@@ -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
|
|
12
|
-
//
|
|
13
|
-
//
|
|
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
|
|
16
|
-
// Java's arithmetic to the unit for the items that are here, so they match the wire
|
|
17
|
-
// items were sent. A list can arrive without its items, and then only the wire total
|
|
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
|
-
//
|
|
24
|
-
//
|
|
25
|
-
//
|
|
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
|
|
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
|
|
48
|
-
// ReservedPackage.DELIVERY_CHARGE_PACKAGE, rather than
|
|
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
|
|
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
|
|
61
|
-
//
|
|
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
|
|
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
|
-
//
|
|
75
|
-
//
|
|
76
|
-
//
|
|
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
|
|
103
|
-
// received. The charge lines are in chargeItems, and the items
|
|
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.
|
|
110
|
-
//
|
|
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,
|
|
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
|
|
123
|
-
//
|
|
124
|
-
//
|
|
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.
|
|
129
|
-
// ShoppingListItem.change, except for a legacy order
|
|
130
|
-
// isOriginal (CartEditor, up to 1.4.0). Every line of such an order
|
|
131
|
-
//
|
|
132
|
-
// change the business can
|
|
133
|
-
//
|
|
134
|
-
//
|
|
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
|
|
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
|
|
147
|
-
//
|
|
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
|
|
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
|
|
161
|
-
//
|
|
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
|
-
//
|
|
181
|
-
//
|
|
182
|
-
//
|
|
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
|
|
188
|
-
// with a price discount like any other, as in the merchant app's total row. So
|
|
189
|
-
//
|
|
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
|
-
//
|
|
197
|
-
// stable dependency) and the
|
|
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
|
|
225
|
-
// those, never unitPrice, which is not what a customer is charged
|
|
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
|
|
228
|
-
// times quantity) to a whole amount, then each tax
|
|
229
|
-
// of Transaction.MAX_AMOUNT or more throws an InvalidArgumentException
|
|
230
|
-
//
|
|
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.
|
|
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
|
|
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.
|
|
280
|
-
// a cart is
|
|
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
|
|
285
|
-
// modified: deleted (and in ShoppingList.deletedItems). Not original but
|
|
286
|
-
// original
|
|
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:
|
|
292
|
-
//
|
|
293
|
-
// To show a list use ShoppingList.changeOf, which also
|
|
294
|
-
// flagged
|
|
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
|
-
//
|
|
331
|
-
//
|
|
332
|
-
//
|
|
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
|
|
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
|
|
361
|
-
// discount taken off: what a customer would have paid
|
|
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
|
|
367
|
-
//
|
|
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
|
|
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
|
|
382
|
-
//
|
|
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
|
-
//
|
|
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
|
|
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
|
-
//
|
|
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
|
|
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
|
|
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,
|
|
441
|
-
// an item
|
|
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
|
|
444
|
-
//
|
|
445
|
-
//
|
|
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
|
|
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
|
|
469
|
-
//
|
|
470
|
-
//
|
|
471
|
-
//
|
|
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
|
}
|