@peabody-soft/mbs-model-ts 1.5.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Address.js +8 -11
- package/dist/Attachment.js +7 -8
- package/dist/AttachmentBaseUrl.js +10 -10
- package/dist/AuthTokens.js +12 -7
- package/dist/Business.js +19 -19
- package/dist/BusinessIdentifier.js +2 -2
- package/dist/BusinessLink.js +16 -16
- package/dist/BusinessProfile.js +4 -4
- package/dist/CartEditor.js +137 -166
- package/dist/Catalog.js +19 -25
- package/dist/Constants.js +10 -10
- package/dist/ContainerNames.js +10 -11
- package/dist/Entity.js +11 -9
- package/dist/Enums.js +2 -2
- package/dist/Exceptions.js +2 -2
- package/dist/GeneralSettings.js +22 -21
- package/dist/HttpStatusCodes.js +10 -11
- package/dist/Ids.js +24 -30
- package/dist/Item.js +6 -7
- package/dist/LatLong.js +3 -3
- package/dist/LiveData.js +6 -6
- package/dist/Measures.js +2 -2
- package/dist/Message.js +6 -6
- package/dist/MockDatabase.js +2 -2
- package/dist/Model.js +292 -359
- package/dist/Order.js +20 -17
- package/dist/Package.js +7 -8
- package/dist/PackageIdentifier.js +2 -2
- package/dist/PaymentMethodDetail.js +4 -4
- package/dist/Photo.js +6 -7
- package/dist/Price.js +8 -9
- package/dist/PriceList.js +2 -2
- package/dist/Product.js +17 -22
- package/dist/ProductCategory.js +3 -4
- package/dist/Profile.js +26 -27
- package/dist/ProtoMap.d.ts +3 -0
- package/dist/ProtoMap.js +288 -294
- package/dist/Routes.js +3 -3
- package/dist/ServiceExceptions.js +11 -12
- package/dist/ServiceProxy.js +19 -23
- package/dist/ShoppingList.js +112 -130
- package/dist/SyncCache.d.ts +3 -0
- package/dist/SyncCache.js +92 -75
- package/dist/SyncKeys.js +8 -8
- package/dist/SyncState.js +5 -5
- package/dist/Tax.js +16 -21
- package/dist/TaxHelper.js +22 -30
- package/dist/TaxType.js +8 -9
- package/dist/UserProfile.js +4 -4
- package/dist/WorkingDays.js +15 -15
- package/dist/index.js +9 -9
- package/package.json +1 -1
package/dist/Routes.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// The last path segment of each call this library makes, appended to the base url a ServiceProxy is
|
|
2
|
-
// built with. Named after the server method
|
|
3
|
-
//
|
|
4
|
-
// No route takes an id:
|
|
2
|
+
// built with. Named after the server method, in the kebab case Java Routes.java uses, with a prefix
|
|
3
|
+
// that keeps storefront methods apart from the merchant ones on the same host.
|
|
4
|
+
// No route takes an id: the request message carries everything.
|
|
5
5
|
class Routes {
|
|
6
6
|
}
|
|
7
7
|
Routes.BUSINESS_SYNC_OUT = "shop-business-sync-out";
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
// What a Service call fails with. Unlike the two in Exceptions.ts these are not programming errors
|
|
2
|
-
//
|
|
3
|
-
// it
|
|
4
|
-
//
|
|
5
|
-
// Each carries the status code
|
|
6
|
-
//
|
|
1
|
+
// What a Service call fails with. Unlike the two in Exceptions.ts these are not programming errors.
|
|
2
|
+
// A caller is expected to catch them and decide what to do: sign in again, retry, or show the shop
|
|
3
|
+
// it has cached. They mirror Java com.merabills.repository.client.exception, so both clients of
|
|
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
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
|
|
8
|
+
// classes below names, so an unrecognised failure is still a ServiceException.
|
|
9
9
|
export class ServiceException extends Error {
|
|
10
10
|
constructor(httpStatusCode, message) {
|
|
11
11
|
super(message);
|
|
@@ -70,9 +70,9 @@ export class ServiceUnavailableException extends ServiceException {
|
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
ServiceUnavailableException.STATUS_CODE = 503;
|
|
73
|
-
// The call never got an answer
|
|
74
|
-
// Not a ServiceException,
|
|
75
|
-
// request
|
|
73
|
+
// The call never got an answer: offline, DNS, TLS, a refused connection or one cut short.
|
|
74
|
+
// Not a ServiceException, since there is no status code and the server may have acted on the
|
|
75
|
+
// request. A caller that retries must be prepared for the first attempt to have succeeded.
|
|
76
76
|
export class NetworkConnectionException extends Error {
|
|
77
77
|
constructor(message, reason) {
|
|
78
78
|
super(message);
|
|
@@ -80,5 +80,4 @@ export class NetworkConnectionException extends Error {
|
|
|
80
80
|
this.name = new.target.name;
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
|
-
//
|
|
84
|
-
// in its exception package and the mapping in HttpStatusCodes.
|
|
83
|
+
// The status code each of these stands for is mapped in HttpStatusCodes.ts.
|
package/dist/ServiceProxy.js
CHANGED
|
@@ -12,14 +12,13 @@ import { buildAuthTokensBytes } from "./ProtoMap";
|
|
|
12
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
|
-
// it,
|
|
16
|
-
//
|
|
17
|
-
// One call is one page:
|
|
18
|
-
//
|
|
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).
|
|
19
19
|
export default class ServiceProxy {
|
|
20
|
-
// The base url is everything before the route, with or without a trailing slash
|
|
21
|
-
//
|
|
22
|
-
// under a prefix works without the caller having to know how a route is built.
|
|
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.
|
|
23
22
|
constructor(serviceBaseUrl, fetchFunction = null) {
|
|
24
23
|
this._serviceBaseUrl = serviceBaseUrl.replace(TRAILING_SLASHES, "");
|
|
25
24
|
this._fetchFunction = fetchFunction;
|
|
@@ -40,8 +39,8 @@ export default class ServiceProxy {
|
|
|
40
39
|
async updateUser(user, authTokens) {
|
|
41
40
|
return this.post(Routes.UPDATE_USER, proto_User, user, proto_ProfileSyncOutResponse, authTokens);
|
|
42
41
|
}
|
|
43
|
-
// One call: encode, send, turn a refusal into
|
|
44
|
-
//
|
|
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.
|
|
45
44
|
async post(route, requestType, request, responseType, authTokens) {
|
|
46
45
|
const response = await this.send(route, requestType.toBinary(request), authTokens);
|
|
47
46
|
// The body of a failure is not a response message, so it has to be dealt with before decoding
|
|
@@ -51,8 +50,8 @@ export default class ServiceProxy {
|
|
|
51
50
|
}
|
|
52
51
|
async send(route, body, authTokens) {
|
|
53
52
|
const headers = { [CONTENT_TYPE_HEADER]: CONTENT_TYPE_PROTOBUF };
|
|
54
|
-
// An anonymous visitor
|
|
55
|
-
//
|
|
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.
|
|
56
55
|
if (authTokens !== null)
|
|
57
56
|
headers[AUTHORIZATION_HEADER] = toBase64(buildAuthTokensBytes(authTokens));
|
|
58
57
|
try {
|
|
@@ -63,14 +62,13 @@ export default class ServiceProxy {
|
|
|
63
62
|
});
|
|
64
63
|
}
|
|
65
64
|
catch (reason) {
|
|
66
|
-
// fetch rejects only when no answer arrived
|
|
67
|
-
//
|
|
68
|
-
// that retries has to be prepared for the first attempt to have succeeded.
|
|
65
|
+
// fetch rejects only when no answer arrived. A refusal resolves with a status code. See
|
|
66
|
+
// NetworkConnectionException for what a caller must expect.
|
|
69
67
|
throw new NetworkConnectionException(`The call to ${route} could not be completed`, reason);
|
|
70
68
|
}
|
|
71
69
|
}
|
|
72
|
-
// Resolved per call
|
|
73
|
-
//
|
|
70
|
+
// Resolved per call, not at construction, so a platform that installs fetch after this object is
|
|
71
|
+
// built still works and an absent fetch fails where it is needed.
|
|
74
72
|
getFetchFunction() {
|
|
75
73
|
const supplied = this._fetchFunction;
|
|
76
74
|
if (supplied !== null)
|
|
@@ -82,9 +80,8 @@ export default class ServiceProxy {
|
|
|
82
80
|
return platform.bind(globalThis);
|
|
83
81
|
}
|
|
84
82
|
// What the response says went wrong, if anything. This protocol has no error message of its own
|
|
85
|
-
// yet, so
|
|
86
|
-
//
|
|
87
|
-
// does not already.
|
|
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.
|
|
88
85
|
static async readErrorMessage(route, response) {
|
|
89
86
|
let body;
|
|
90
87
|
try {
|
|
@@ -99,11 +96,10 @@ export default class ServiceProxy {
|
|
|
99
96
|
}
|
|
100
97
|
}
|
|
101
98
|
// A header carries text, so the encoded credentials travel as base64. No scheme prefix: the server
|
|
102
|
-
// reads the whole value as one encoded AuthTokens,
|
|
103
|
-
// as one serialized AuthTokens.
|
|
99
|
+
// reads the whole value as one encoded AuthTokens, as the Java client sends it.
|
|
104
100
|
function toBase64(bytes) {
|
|
105
|
-
// Built a character at a time
|
|
106
|
-
//
|
|
101
|
+
// Built a character at a time, since spreading the array into String.fromCharCode passes it as an
|
|
102
|
+
// argument list and overflows the stack when long enough
|
|
107
103
|
let binary = "";
|
|
108
104
|
for (const byte of bytes)
|
|
109
105
|
binary += String.fromCharCode(byte);
|
package/dist/ShoppingList.js
CHANGED
|
@@ -7,25 +7,22 @@ import { TaxAmount } from "./Tax";
|
|
|
7
7
|
import TaxHelper from "./TaxHelper";
|
|
8
8
|
// A list of packages being bought: a shopping cart, or what an order contains.
|
|
9
9
|
//
|
|
10
|
-
// Every unit price on a list is WITHOUT taxes,
|
|
11
|
-
// ShoppingListItem).
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
// it. The taxes inside a total are available by type (computedTaxes).
|
|
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).
|
|
15
14
|
//
|
|
16
|
-
// Wire totals are authoritative: totalAmount is the number to display. The computed ones
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
// covers the whole list.
|
|
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.
|
|
20
18
|
class ShoppingList {
|
|
21
|
-
//
|
|
22
|
-
// Constants.AMOUNT_FACTOR.
|
|
19
|
+
// For ProtoMap, which sees totals still multiplied by Constants.AMOUNT_FACTOR.
|
|
23
20
|
static fromScaledValues(isFinal, autoAddDeliveryCharge, autoAddServiceCharge, inventoryLocationId, priceListId, scaledTotalAmountBeforeDiscount, scaledDiscount, existingItemCount, deletedItemCount, existingItems, deletedItems) {
|
|
24
21
|
return new ShoppingList(isFinal, autoAddDeliveryCharge, autoAddServiceCharge, inventoryLocationId, priceListId, amountFromScaledValue(scaledTotalAmountBeforeDiscount), amountFromScaledValue(scaledDiscount), existingItemCount, deletedItemCount, existingItems, deletedItems);
|
|
25
22
|
}
|
|
26
23
|
// What these items come to, taxes included and each item's own discount taken off: the sum of
|
|
27
|
-
// their finalPrice, which
|
|
28
|
-
//
|
|
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.
|
|
29
26
|
static sumOfFinalPrices(items) {
|
|
30
27
|
return amountFromScaledValue(sumOf(items, (pricing) => pricing.finalPrice));
|
|
31
28
|
}
|
|
@@ -42,8 +39,8 @@ class ShoppingList {
|
|
|
42
39
|
this._existingItems = _existingItems;
|
|
43
40
|
this._deletedItems = _deletedItems;
|
|
44
41
|
}
|
|
45
|
-
// True once the customer is done editing, after which the business may edit it
|
|
46
|
-
//
|
|
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.
|
|
47
44
|
get isFinal() {
|
|
48
45
|
return this._isFinal;
|
|
49
46
|
}
|
|
@@ -69,21 +66,21 @@ class ShoppingList {
|
|
|
69
66
|
get totalAmountBeforeDiscount() {
|
|
70
67
|
return this._totalAmountBeforeDiscount;
|
|
71
68
|
}
|
|
72
|
-
// The discount on the whole list,
|
|
73
|
-
//
|
|
69
|
+
// The discount on the whole list, not on one line. It comes off a total that already has its
|
|
70
|
+
// taxes in it.
|
|
74
71
|
get discount() {
|
|
75
72
|
return this._discount;
|
|
76
73
|
}
|
|
77
|
-
// What the customer owes, taxes included. Derived
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
-
//
|
|
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.
|
|
81
78
|
get totalAmount() {
|
|
82
79
|
const total = amountToScaledValue(this._totalAmountBeforeDiscount) - amountToScaledValue(this._discount);
|
|
83
80
|
return total > 0n ? amountFromScaledValue(total) : 0;
|
|
84
81
|
}
|
|
85
|
-
// Read from the wire
|
|
86
|
-
//
|
|
82
|
+
// Read from the wire because the protocol sends the count even when it sends no items, so the
|
|
83
|
+
// array length below can differ
|
|
87
84
|
get existingItemCount() {
|
|
88
85
|
return this._existingItemCount;
|
|
89
86
|
}
|
|
@@ -97,25 +94,24 @@ class ShoppingList {
|
|
|
97
94
|
return this._existingItems;
|
|
98
95
|
}
|
|
99
96
|
// The items that were in this list when it was made final and have since been deleted or
|
|
100
|
-
// replaced.
|
|
97
|
+
// replaced. Empty until the list is final.
|
|
101
98
|
get deletedItems() {
|
|
102
99
|
return this._deletedItems;
|
|
103
100
|
}
|
|
104
|
-
// The existing items that are packages bought from the catalog, in the order of the list
|
|
105
|
-
//
|
|
106
|
-
// The charge lines are
|
|
107
|
-
//
|
|
101
|
+
// 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.
|
|
108
105
|
get goodsItems() {
|
|
109
106
|
return this.derived.goods;
|
|
110
107
|
}
|
|
111
|
-
// The existing items that are charge lines
|
|
108
|
+
// The existing items that are charge lines (the delivery charge and the service charge), in the
|
|
112
109
|
// order of the list. The other two reserved ids, for a discount and a total, are summary lines a
|
|
113
|
-
// list does not carry as items
|
|
110
|
+
// list does not carry as items. They are discount and totalAmount.
|
|
114
111
|
get chargeItems() {
|
|
115
112
|
return this.derived.charges;
|
|
116
113
|
}
|
|
117
|
-
// The delivery charge,
|
|
118
|
-
// list has none
|
|
114
|
+
// The delivery charge, an item priced like any other, or null when this list has none
|
|
119
115
|
get deliveryCharge() {
|
|
120
116
|
return (this._existingItems.find((item) => item.reservedPackage === proto_ReservedPackage.DELIVERY_CHARGE_PACKAGE) ?? null);
|
|
121
117
|
}
|
|
@@ -124,20 +120,18 @@ class ShoppingList {
|
|
|
124
120
|
return (this._existingItems.find((item) => item.reservedPackage === proto_ReservedPackage.SERVICE_CHARGE_PACKAGE) ?? null);
|
|
125
121
|
}
|
|
126
122
|
// The items the business took out of the list after the customer made it final, or replaced with
|
|
127
|
-
// a changed one, as the customer ordered them
|
|
128
|
-
// charge line is among them, and
|
|
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.
|
|
129
125
|
get removedItems() {
|
|
130
126
|
return this.derived.removed;
|
|
131
127
|
}
|
|
132
|
-
// What the business has done to one line of this list since the customer made it final
|
|
133
|
-
//
|
|
134
|
-
// isOriginal
|
|
135
|
-
//
|
|
136
|
-
//
|
|
137
|
-
//
|
|
138
|
-
//
|
|
139
|
-
// original, nothing modified and nothing deleted is read as untouched. Show a list's marks with
|
|
140
|
-
// this and not with ShoppingListItem.change.
|
|
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.
|
|
141
135
|
changeOf(item) {
|
|
142
136
|
const change = item.change;
|
|
143
137
|
if (change !== ItemChange.ADDED)
|
|
@@ -160,14 +154,12 @@ class ShoppingList {
|
|
|
160
154
|
return amountFromScaledValue(sumOf(this._existingItems, (pricing) => pricing.totalTax));
|
|
161
155
|
}
|
|
162
156
|
// The taxes inside computedTotal, one entry per TYPE of tax, in the order of the types, added up
|
|
163
|
-
// over every item. A type none of the items has is not here. An amount can be zero
|
|
164
|
-
// applies to
|
|
165
|
-
//
|
|
166
|
-
//
|
|
167
|
-
//
|
|
168
|
-
//
|
|
169
|
-
// Worked out once, like what derived holds, but apart from it: this prices the items, which the
|
|
170
|
-
// lines of a list do not need, and one that cannot be priced is no reason for a list not to list them.
|
|
157
|
+
// over every item. A type none of the items has is not here. An amount can be zero, for a type
|
|
158
|
+
// that applies only to free items. Java ShoppingList.getTotalTaxesByTaxCode leaves such a type
|
|
159
|
+
// 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.
|
|
171
163
|
get computedTaxes() {
|
|
172
164
|
const existing = TAXES.get(this);
|
|
173
165
|
if (existing !== undefined)
|
|
@@ -185,24 +177,24 @@ class ShoppingList {
|
|
|
185
177
|
TAXES.set(this, created);
|
|
186
178
|
return created;
|
|
187
179
|
}
|
|
188
|
-
// What the existing items come to at their regular prices, with their taxes and
|
|
189
|
-
//
|
|
190
|
-
//
|
|
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.
|
|
191
183
|
get computedTotalWithoutDiscounts() {
|
|
192
184
|
return amountFromScaledValue(sumOf(this._existingItems, (pricing) => pricing.finalPriceWithoutDiscounts));
|
|
193
185
|
}
|
|
194
186
|
// What the offers on the items, the discounts on them and the discount on the list took off,
|
|
195
|
-
// taxes included,
|
|
196
|
-
//
|
|
197
|
-
//
|
|
198
|
-
//
|
|
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.
|
|
199
191
|
get savings() {
|
|
200
192
|
const saved = sumOf(this._existingItems, (pricing) => pricing.finalPriceWithoutDiscounts) -
|
|
201
193
|
amountToScaledValue(this.totalAmount);
|
|
202
194
|
return saved > 0n ? amountFromScaledValue(saved) : 0;
|
|
203
195
|
}
|
|
204
|
-
// Worked out once
|
|
205
|
-
//
|
|
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
|
|
206
198
|
get derived() {
|
|
207
199
|
const existing = DERIVED.get(this);
|
|
208
200
|
if (existing !== undefined)
|
|
@@ -227,24 +219,18 @@ ShoppingList.EMPTY = new ShoppingList(false, false, false, 0, MERABILLS_ONLINE_P
|
|
|
227
219
|
export default ShoppingList;
|
|
228
220
|
// One line of a shopping list: the package being bought, how much of it, and at what price.
|
|
229
221
|
//
|
|
230
|
-
// THE PRICE IS STORED WITHOUT TAXES
|
|
231
|
-
//
|
|
232
|
-
//
|
|
233
|
-
//
|
|
234
|
-
// (CartEditor does this, as Java ShoppingListItem.setDetailsFromTaxInclusivePrices does), and the
|
|
235
|
-
// *WithTaxes members below put them back for display. Show those, never unitPrice: unitPrice is
|
|
236
|
-
// what is stored, not what anyone is charged for a unit.
|
|
222
|
+
// THE PRICE IS STORED WITHOUT TAXES: unitPrice and priceDiscount are before taxes, which are worked
|
|
223
|
+
// 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.
|
|
237
226
|
//
|
|
238
|
-
// The arithmetic is Java's to the unit
|
|
239
|
-
//
|
|
240
|
-
//
|
|
241
|
-
//
|
|
242
|
-
// InvalidArgumentException when the item is priced, which is when something first reads a price
|
|
243
|
-
// off it - for each of the taxes, the final price and the other prices it works out (see
|
|
244
|
-
// validatePricing).
|
|
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).
|
|
245
231
|
export class ShoppingListItem {
|
|
246
|
-
//
|
|
247
|
-
//
|
|
232
|
+
// For ProtoMap, which sees a quantity multiplied by Constants.QUANTITY_FACTOR and prices
|
|
233
|
+
// multiplied by Constants.AMOUNT_FACTOR.
|
|
248
234
|
static fromScaledValues(packageId, scaledQuantity, scaledUnitPrice, shoppingListOrder, isOriginal, isModified, isStockKeepingRequired, scaledPriceDiscount, scaledDiscount, taxes, expiryMinutes, description) {
|
|
249
235
|
return new ShoppingListItem(packageId, quantityFromScaledValue(scaledQuantity), amountFromScaledValue(scaledUnitPrice), shoppingListOrder, isOriginal, isModified, isStockKeepingRequired, amountFromScaledValue(scaledPriceDiscount), amountFromScaledValue(scaledDiscount), taxes, expiryMinutes, description);
|
|
250
236
|
}
|
|
@@ -262,8 +248,8 @@ export class ShoppingListItem {
|
|
|
262
248
|
this._expiryMinutes = _expiryMinutes;
|
|
263
249
|
this._description = _description;
|
|
264
250
|
}
|
|
265
|
-
// The package being bought. Four ids are reserved for charge and summary lines
|
|
266
|
-
// catalog
|
|
251
|
+
// The package being bought. Four ids are reserved for charge and summary lines and are not in the
|
|
252
|
+
// catalog. See isReservedPackage.
|
|
267
253
|
get packageId() {
|
|
268
254
|
return this._packageId;
|
|
269
255
|
}
|
|
@@ -281,8 +267,8 @@ export class ShoppingListItem {
|
|
|
281
267
|
get quantity() {
|
|
282
268
|
return this._quantity;
|
|
283
269
|
}
|
|
284
|
-
// What one unit costs WITHOUT taxes,
|
|
285
|
-
//
|
|
270
|
+
// What one unit costs WITHOUT taxes, the offer price when the package is on offer. This is what is
|
|
271
|
+
// stored. For what a customer is charged see unitPriceWithTaxes.
|
|
286
272
|
get unitPrice() {
|
|
287
273
|
return this._unitPrice;
|
|
288
274
|
}
|
|
@@ -291,22 +277,21 @@ export class ShoppingListItem {
|
|
|
291
277
|
return this._shoppingListOrder;
|
|
292
278
|
}
|
|
293
279
|
// True when this line was created before the list was made final. Everything a customer puts in
|
|
294
|
-
// a cart is, which
|
|
280
|
+
// a cart is, which separates it from what the business adds or changes afterwards.
|
|
295
281
|
get isOriginal() {
|
|
296
282
|
return this._isOriginal;
|
|
297
283
|
}
|
|
298
|
-
// True when this line has been deleted or replaced since the list was made final.
|
|
299
|
-
//
|
|
300
|
-
//
|
|
301
|
-
// by the business.
|
|
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.
|
|
302
287
|
get isModified() {
|
|
303
288
|
return this._isModified;
|
|
304
289
|
}
|
|
305
290
|
// What the business has done to this line since the customer made the list final, read off
|
|
306
|
-
// isOriginal and isModified
|
|
307
|
-
//
|
|
308
|
-
//
|
|
309
|
-
//
|
|
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.
|
|
310
295
|
get change() {
|
|
311
296
|
if (this._isOriginal)
|
|
312
297
|
return this._isModified ? ItemChange.REMOVED : ItemChange.NONE;
|
|
@@ -315,9 +300,9 @@ export class ShoppingListItem {
|
|
|
315
300
|
get isStockKeepingRequired() {
|
|
316
301
|
return this._isStockKeepingRequired;
|
|
317
302
|
}
|
|
318
|
-
// What the offer takes off the unit price
|
|
319
|
-
//
|
|
320
|
-
// without taxes
|
|
303
|
+
// What the offer takes off the unit price: the package's regular price less its offer price, both
|
|
304
|
+
// WITHOUT taxes. It is not a discount on this line (see discount). The regular price of one unit
|
|
305
|
+
// without taxes is unitPrice + priceDiscount.
|
|
321
306
|
get priceDiscount() {
|
|
322
307
|
return this._priceDiscount;
|
|
323
308
|
}
|
|
@@ -341,20 +326,20 @@ export class ShoppingListItem {
|
|
|
341
326
|
return amountFromScaledValue(pricingOf(this).regularUnitPrice);
|
|
342
327
|
}
|
|
343
328
|
// What one unit costs a customer: unitPrice with the taxes on it, the offer price when the
|
|
344
|
-
// package is on offer.
|
|
345
|
-
// wherever the item is shown. Each tax is rounded, so
|
|
346
|
-
//
|
|
347
|
-
//
|
|
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).
|
|
348
333
|
get unitPriceWithTaxes() {
|
|
349
334
|
return amountFromScaledValue(pricingOf(this).unitPriceWithTaxes);
|
|
350
335
|
}
|
|
351
|
-
// The same for the regular price of one unit,
|
|
352
|
-
//
|
|
336
|
+
// The same for the regular price of one unit, struck through when this line is on offer
|
|
337
|
+
// (priceDiscount greater than zero)
|
|
353
338
|
get regularUnitPriceWithTaxes() {
|
|
354
339
|
return amountFromScaledValue(pricingOf(this).regularUnitPriceWithTaxes);
|
|
355
340
|
}
|
|
356
|
-
// What this line costs before its taxes and
|
|
357
|
-
//
|
|
341
|
+
// What this line costs before its taxes and discount, the tax base: the unit price times the
|
|
342
|
+
// quantity, rounded to a whole amount as Java does
|
|
358
343
|
get amount() {
|
|
359
344
|
return amountFromScaledValue(pricingOf(this).taxBase);
|
|
360
345
|
}
|
|
@@ -367,19 +352,19 @@ export class ShoppingListItem {
|
|
|
367
352
|
get totalTax() {
|
|
368
353
|
return amountFromScaledValue(pricingOf(this).totalTax);
|
|
369
354
|
}
|
|
370
|
-
// What this line adds to the list:
|
|
371
|
-
//
|
|
355
|
+
// What this line adds to the list: its cost plus the tax on it, less its discount, and not less
|
|
356
|
+
// than zero. This is the definition in ShoppingListItem.proto, and it includes taxes.
|
|
372
357
|
get finalPrice() {
|
|
373
358
|
return amountFromScaledValue(pricingOf(this).finalPrice);
|
|
374
359
|
}
|
|
375
|
-
// What this line would add at the regular price
|
|
376
|
-
//
|
|
377
|
-
//
|
|
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
|
+
// through beside finalPrice when it is greater (Java calculateFinalPriceWithoutDiscounts).
|
|
378
363
|
get finalPriceWithoutDiscounts() {
|
|
379
364
|
return amountFromScaledValue(pricingOf(this).finalPriceWithoutDiscounts);
|
|
380
365
|
}
|
|
381
|
-
// True when this line costs less than
|
|
382
|
-
// finalPriceWithoutDiscounts is
|
|
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
|
|
383
368
|
get isDiscounted() {
|
|
384
369
|
const pricing = pricingOf(this);
|
|
385
370
|
return pricing.finalPriceWithoutDiscounts > pricing.finalPrice;
|
|
@@ -408,8 +393,8 @@ export var ItemChange;
|
|
|
408
393
|
// ShoppingList.deletedItems.
|
|
409
394
|
ItemChange[ItemChange["REMOVED"] = 3] = "REMOVED";
|
|
410
395
|
})(ItemChange || (ItemChange = {}));
|
|
411
|
-
// Kept beside the item
|
|
412
|
-
//
|
|
396
|
+
// Kept beside the item so it stays a plain value: two equal items compare equal whether or not
|
|
397
|
+
// either has been priced.
|
|
413
398
|
const PRICINGS = new WeakMap();
|
|
414
399
|
const DERIVED = new WeakMap();
|
|
415
400
|
// The taxes of a list by type, which only a list whose items can be priced has
|
|
@@ -444,23 +429,20 @@ function pricingOf(item) {
|
|
|
444
429
|
return created;
|
|
445
430
|
}
|
|
446
431
|
// Throws an InvalidArgumentException unless every amount the item works out is one the protocol can
|
|
447
|
-
// carry. CartEditor calls it before
|
|
448
|
-
//
|
|
432
|
+
// carry. CartEditor calls it before keeping a line. Anything else finds out when it first reads a
|
|
433
|
+
// price off the item.
|
|
449
434
|
export function validatePricing(item) {
|
|
450
435
|
pricingOf(item);
|
|
451
436
|
}
|
|
452
|
-
// Java Transaction.validateAmount
|
|
453
|
-
//
|
|
454
|
-
//
|
|
455
|
-
//
|
|
456
|
-
//
|
|
457
|
-
//
|
|
458
|
-
//
|
|
459
|
-
//
|
|
460
|
-
//
|
|
461
|
-
// discount comes off that alone) are parts of it; likewise the regular unit price with taxes is
|
|
462
|
-
// never less than the unit price with taxes. So no tax and no final price is over the limit unless
|
|
463
|
-
// that cost is, which the vectors of test/JavaVectors.ts show item by item.
|
|
437
|
+
// Java Transaction.validateAmount refuses an amount of MAX_AMOUNT or more. This applies it to the
|
|
438
|
+
// amounts Java checks on an item: each tax and the final price (recalculate), and the results of
|
|
439
|
+
// 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
|
+
// 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).
|
|
464
446
|
function validateAmounts(pricing) {
|
|
465
447
|
const amounts = [
|
|
466
448
|
...pricing.taxes.map((tax) => ["tax", tax]),
|
|
@@ -473,20 +455,20 @@ function validateAmounts(pricing) {
|
|
|
473
455
|
if (amount >= MAX_SCALED_AMOUNT)
|
|
474
456
|
throw new InvalidArgumentException(`The ${name} of an item, ${amountFromScaledValue(amount)}, is more than the ${amountFromScaledValue(MAX_SCALED_AMOUNT - 1n)} the protocol can carry`);
|
|
475
457
|
}
|
|
476
|
-
// A scaled amount
|
|
458
|
+
// A scaled amount times a quantity, rounded to a whole amount as Java does:
|
|
477
459
|
// Math.round(((double) amount) * NumberHelper.intToFloat(scaledQuantity, QUANTITY_FACTOR)). The
|
|
478
|
-
// quantity is a FLOAT there, so it is one here
|
|
479
|
-
//
|
|
460
|
+
// 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.
|
|
480
462
|
function scaledProduct(scaledAmount, quantity) {
|
|
481
463
|
const scaledQuantity = quantityToScaledValue(quantity);
|
|
482
464
|
const floatQuantity = Math.fround(Math.fround(scaledQuantity) / proto_Constants.QUANTITY_FACTOR);
|
|
483
465
|
const product = Math.round(Number(scaledAmount) * floatQuantity);
|
|
484
466
|
return Number.isFinite(product) ? BigInt(product) : 0n;
|
|
485
467
|
}
|
|
486
|
-
// A copy in the order of the list. Java sorts the existing items by shoppingListOrder before
|
|
487
|
-
//
|
|
488
|
-
//
|
|
489
|
-
//
|
|
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.
|
|
490
472
|
function sortedByOrder(items) {
|
|
491
473
|
return [...items].sort((left, right) => left.shoppingListOrder - right.shoppingListOrder);
|
|
492
474
|
}
|
package/dist/SyncCache.d.ts
CHANGED
|
@@ -11,13 +11,16 @@ export default class SyncCache {
|
|
|
11
11
|
readBusinessCursors(containerName: string): Promise<SyncState>;
|
|
12
12
|
writeBusiness(containerName: string, response: proto_BusinessSyncOutResponse): Promise<number>;
|
|
13
13
|
storeBusiness(containerName: string, response: proto_BusinessSyncOutResponse): Promise<number>;
|
|
14
|
+
readDeletedBusiness(containerName: string): Promise<proto_BusinessSyncOutResponse | null>;
|
|
14
15
|
readProfile(containerName: string): Promise<proto_ProfileSyncOutResponse | null>;
|
|
16
|
+
readDeletedProfile(containerName: string): Promise<proto_ProfileSyncOutResponse | null>;
|
|
15
17
|
readProfileCursors(containerName: string): Promise<SyncState>;
|
|
16
18
|
writeProfile(containerName: string, response: proto_ProfileSyncOutResponse): Promise<number>;
|
|
17
19
|
storeProfile(containerName: string, response: proto_ProfileSyncOutResponse): Promise<number>;
|
|
18
20
|
readAuthTokens(containerName: string): Promise<AuthTokens | null>;
|
|
19
21
|
writeAuthTokens(containerName: string, authTokens: AuthTokens): Promise<void>;
|
|
20
22
|
readOrders(containerName: string): Promise<ReadonlyArray<proto_Message>>;
|
|
23
|
+
readDeletedOrders(containerName: string): Promise<ReadonlyArray<proto_Message>>;
|
|
21
24
|
readMessageUpdateNumber(containerName: string): Promise<bigint>;
|
|
22
25
|
writeOrder(containerName: string, order: proto_Message): Promise<number>;
|
|
23
26
|
readPartyPreferences(containerName: string): Promise<proto_Message | null>;
|