@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
package/dist/Attachment.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import Entity from "./Entity";
|
|
2
2
|
import Ids from "./Ids";
|
|
3
|
-
// A file attached to an entity: a photo of a business, product or package. Not
|
|
4
|
-
// a Photo (attachment type and url) built from it.
|
|
3
|
+
// A file attached to an entity: a photo of a business, product or package. Not exported from
|
|
4
|
+
// index.ts: a UI is given a Photo (attachment type and url) built from it.
|
|
5
5
|
export default class Attachment extends Entity {
|
|
6
6
|
constructor(header, _attachmentType, _owningEntityId, _fileHash, _fileExtension, _fileName) {
|
|
7
7
|
super(header);
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { Constants as proto_Constants } from "./generated/Constants";
|
|
2
2
|
import { InvalidArgumentException } from "./Exceptions";
|
|
3
3
|
// Where a business's attachment files live, the signature that reads them, and when both expire.
|
|
4
|
-
// The signature has no getter, only createFileUrl exposes it.
|
|
5
|
-
// scoped, read-only and short-lived, unlike AuthTokens' userToken and linkToken.
|
|
4
|
+
// The signature has no getter, and only createFileUrl exposes it. A signature in a url is safe
|
|
5
|
+
// because it is scoped, read-only and short-lived, unlike AuthTokens' userToken and linkToken.
|
|
6
6
|
export default class AttachmentBaseUrl {
|
|
7
7
|
constructor(_resourceUrl, _sasToken, expiry) {
|
|
8
8
|
this._resourceUrl = _resourceUrl;
|
|
9
9
|
this._sasToken = _sasToken;
|
|
10
|
-
// The protobuf
|
|
11
|
-
//
|
|
10
|
+
// The protobuf specifies no trailing slash and no query string. Either would build a url that
|
|
11
|
+
// fetches nothing, so both are checked.
|
|
12
12
|
if (_resourceUrl.length === 0 || _resourceUrl.endsWith("/"))
|
|
13
13
|
throw new InvalidArgumentException(`Not a container url: ${_resourceUrl}`);
|
|
14
14
|
if (_resourceUrl.includes("?"))
|
package/dist/AuthTokens.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
// The credentials a request is signed with: the fields of the AuthTokens message, nothing
|
|
2
|
-
//
|
|
3
|
-
//
|
|
1
|
+
// The credentials a request is signed with: the fields of the AuthTokens message, with nothing
|
|
2
|
+
// derived. It is a class because it is held and cached. ProtoMap converts one into the message
|
|
3
|
+
// passed to a Service. Every value is a bearer credential, so never log one.
|
|
4
4
|
export default class AuthTokens {
|
|
5
5
|
constructor(_authenticationMethod, _authenticationToken, _userToken, _linkToken) {
|
|
6
6
|
this._authenticationMethod = _authenticationMethod;
|
|
@@ -13,13 +13,13 @@ export default class AuthTokens {
|
|
|
13
13
|
get authenticationMethod() {
|
|
14
14
|
return this._authenticationMethod;
|
|
15
15
|
}
|
|
16
|
-
//
|
|
17
|
-
//
|
|
16
|
+
// The identity provider's token for the caller. Empty when read back from the cache, which stores
|
|
17
|
+
// no copy. See ProtoMap.authTokensToBlob.
|
|
18
18
|
get authenticationToken() {
|
|
19
19
|
return this._authenticationToken;
|
|
20
20
|
}
|
|
21
|
-
//
|
|
22
|
-
//
|
|
21
|
+
// Identifies the MeraBills user. This server issues it, so it is null until the first profile
|
|
22
|
+
// sync out returns one.
|
|
23
23
|
get userToken() {
|
|
24
24
|
return this._userToken;
|
|
25
25
|
}
|
package/dist/Business.js
CHANGED
|
@@ -48,8 +48,8 @@ export default class Business extends Entity {
|
|
|
48
48
|
this._photos = value;
|
|
49
49
|
}
|
|
50
50
|
// The orders this customer has placed, open and completed (fulfilled or cancelled). Order.status
|
|
51
|
-
//
|
|
52
|
-
// does not sort them.
|
|
51
|
+
// is the order's progress, and Order.isDeleted is true once the business has completed it. The
|
|
52
|
+
// library does not sort them.
|
|
53
53
|
get orders() {
|
|
54
54
|
return this._orders;
|
|
55
55
|
}
|
|
@@ -57,8 +57,8 @@ export default class Business extends Entity {
|
|
|
57
57
|
this._orders = value;
|
|
58
58
|
}
|
|
59
59
|
// What this customer has picked and not yet ordered. Null until something is put in a cart, and
|
|
60
|
-
// null again once
|
|
61
|
-
//
|
|
60
|
+
// null again once the cart is ordered. The cart message itself stays, with no list. Only the list
|
|
61
|
+
// is exposed here. The fulfillment mode is chosen when an order is placed and is stored on the
|
|
62
62
|
// Order (see defaultFulfillmentMode).
|
|
63
63
|
get cart() {
|
|
64
64
|
return this._cart;
|
|
@@ -66,9 +66,9 @@ export default class Business extends Entity {
|
|
|
66
66
|
set cart(value) {
|
|
67
67
|
this._cart = value;
|
|
68
68
|
}
|
|
69
|
-
//
|
|
70
|
-
// createOrder
|
|
71
|
-
//
|
|
69
|
+
// The fulfillment mode to preselect for the next order: the mode of the last order placed, which
|
|
70
|
+
// Model.createOrder writes to the party preferences message. DELIVERY when there is no party
|
|
71
|
+
// preferences message.
|
|
72
72
|
get defaultFulfillmentMode() {
|
|
73
73
|
return this._defaultFulfillmentMode;
|
|
74
74
|
}
|
|
@@ -84,12 +84,13 @@ export default class Business extends Entity {
|
|
|
84
84
|
}
|
|
85
85
|
// A scratch copy of the cart to edit, priced from this business's catalog and settings (see
|
|
86
86
|
// CartEditor). It reads catalog, settings and cart once, so a Business republished mid-edit needs
|
|
87
|
-
// a new editor.
|
|
88
|
-
// Model.updateCartLocalOnly.
|
|
87
|
+
// a new editor. Editing does not change this business. The cart changes only when the editor's
|
|
88
|
+
// built list is passed to Model.updateCartLocalOnly.
|
|
89
89
|
createCartEditor() {
|
|
90
90
|
return new CartEditor(this._catalog, this._settings, this._cart);
|
|
91
91
|
}
|
|
92
|
-
// Where this business's photo files live.
|
|
92
|
+
// Where this business's photo files live. The server renews it, and it is tracked by its expiry,
|
|
93
|
+
// not by an entity cursor.
|
|
93
94
|
get attachmentBaseUrl() {
|
|
94
95
|
return this._attachmentBaseUrl;
|
|
95
96
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
// An identifier of a business, such as a GSTIN, and its kind.
|
|
2
|
-
// PackageIdentifier
|
|
1
|
+
// An identifier of a business, such as a GSTIN, and its kind. It is a pair of type and value, not a
|
|
2
|
+
// map keyed by kind, as in PackageIdentifier. Java identifies kinds by string id, so the enum
|
|
3
|
+
// numbering exists only in the protocol.
|
|
3
4
|
export default class BusinessIdentifier {
|
|
4
5
|
constructor(_type, _value) {
|
|
5
6
|
this._type = _type;
|
package/dist/BusinessLink.js
CHANGED
|
@@ -44,8 +44,9 @@ class BusinessLink extends Entity {
|
|
|
44
44
|
get isExpirySet() {
|
|
45
45
|
return this._expiry !== BusinessLink.NO_EXPIRY;
|
|
46
46
|
}
|
|
47
|
-
// True once
|
|
48
|
-
// AttachmentBaseUrl.hasExpired
|
|
47
|
+
// True once expiry is more than Constants.MAX_CLOCK_SKEW in the past. The skew delays expiry
|
|
48
|
+
// here, whereas AttachmentBaseUrl.hasExpired advances it. A false positive here would lock out an
|
|
49
|
+
// entitled customer.
|
|
49
50
|
get hasExpired() {
|
|
50
51
|
if (!this.isExpirySet)
|
|
51
52
|
return false;
|
package/dist/CartEditor.js
CHANGED
|
@@ -6,33 +6,36 @@ import { InvalidArgumentException } from "./Exceptions";
|
|
|
6
6
|
import Ids from "./Ids";
|
|
7
7
|
import ShoppingList, { ShoppingListItem, validatePricing } from "./ShoppingList";
|
|
8
8
|
import TaxHelper from "./TaxHelper";
|
|
9
|
-
// Builds the cart a customer is filling
|
|
10
|
-
//
|
|
11
|
-
//
|
|
9
|
+
// Builds the cart a customer is filling. It works in packages, quantities as the customer enters
|
|
10
|
+
// them (not multiplied by Constants.QUANTITY_FACTOR) and prices from the catalog. Get one from
|
|
11
|
+
// Business.createCartEditor, edit it, then pass build() to Model.updateCartLocalOnly.
|
|
12
12
|
//
|
|
13
|
-
// Mutable, and the only mutable
|
|
14
|
-
// changes no Business, store or server until a built list is
|
|
15
|
-
// snapshot, read once at construction, so a sync
|
|
16
|
-
//
|
|
13
|
+
// Mutable, and the only mutable class in this object model. It is a scratch copy: editing it
|
|
14
|
+
// changes no Business, store or server until a built list is passed to Model. It is also a
|
|
15
|
+
// snapshot, read once at construction, so a sync during an edit does not change the prices. Make
|
|
16
|
+
// a new one when the Business is republished.
|
|
17
17
|
//
|
|
18
|
-
// EVERY PRICE HERE INCLUDES TAXES
|
|
19
|
-
// the taxes the settings name for it, so a customer pays what the
|
|
20
|
-
// item stores a price WITHOUT taxes, so building one takes them
|
|
21
|
-
// ShoppingListItem.setDetailsFromTaxInclusivePrices does (
|
|
22
|
-
//
|
|
18
|
+
// EVERY PRICE HERE INCLUDES TAXES. A package's price includes its product's taxes, and the
|
|
19
|
+
// delivery charge includes the taxes the settings name for it, so a customer pays what the
|
|
20
|
+
// catalog shows. A shopping list item stores a price WITHOUT taxes, so building one takes them
|
|
21
|
+
// out, as Java ShoppingListItem.setDetailsFromTaxInclusivePrices does (with the same amounts to
|
|
22
|
+
// the unit, see TaxHelper). A total includes its taxes. They are reported separately and never
|
|
23
|
+
// added to it.
|
|
23
24
|
class CartEditor {
|
|
24
|
-
//
|
|
25
|
-
//
|
|
25
|
+
// It takes the catalog, settings and cart instead of the Business, so a caller can build a cart
|
|
26
|
+
// against ones that are not the currently published ones.
|
|
26
27
|
//
|
|
27
|
-
// Every line is priced
|
|
28
|
-
// repriced since, and an order freezes the price when it is placed.
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
//
|
|
28
|
+
// Every line is priced from the catalog, not from the price the cart held: the merchant may have
|
|
29
|
+
// repriced since, and an order freezes the price when it is placed.
|
|
30
|
+
//
|
|
31
|
+
// A line goes to unavailableItems, for a UI to show the customer, when the catalog cannot price
|
|
32
|
+
// it (package withdrawn, disabled or without a price). A cart that was read back is not known to
|
|
33
|
+
// be one this class could have made, so a line also goes there when setQuantity would reject it:
|
|
34
|
+
// a zero or invalid quantity, a fraction of a package not sold loose, a quantity too large for
|
|
35
|
+
// the wire, a quantity whose amount at today's price is too large for the protocol, or a line
|
|
36
|
+
// past the most a cart holds (the first lines are kept, in pick order).
|
|
37
|
+
// The charge lines of the starting list (see ShoppingListItem.isReservedPackage) are dropped and
|
|
38
|
+
// not listed, because build() adds the charges the settings give.
|
|
36
39
|
constructor(_catalog, _settings, cart) {
|
|
37
40
|
this._catalog = _catalog;
|
|
38
41
|
this._settings = _settings;
|
|
@@ -40,8 +43,8 @@ class CartEditor {
|
|
|
40
43
|
// shoppingListOrder
|
|
41
44
|
this._quantities = new Map();
|
|
42
45
|
this._inventoryLocationId = cart?.inventoryLocationId ?? 0;
|
|
43
|
-
// goodsItems is in the order the customer picked. existingItems is
|
|
44
|
-
// by package id, so
|
|
46
|
+
// goodsItems is in the order the customer picked. existingItems is in the order the server
|
|
47
|
+
// sends it, by package id, so reading from it would reorder the cart
|
|
45
48
|
const unavailableItems = [];
|
|
46
49
|
for (const item of cart?.goodsItems ?? []) {
|
|
47
50
|
if (item.isReservedPackage)
|
|
@@ -63,8 +66,8 @@ class CartEditor {
|
|
|
63
66
|
}
|
|
64
67
|
return Object.freeze(lines);
|
|
65
68
|
}
|
|
66
|
-
// The lines of the starting cart that
|
|
67
|
-
//
|
|
69
|
+
// The lines of the starting cart that could not be kept (see the constructor). Empty for a cart
|
|
70
|
+
// built from nothing, and no edit adds to it.
|
|
68
71
|
get unavailableItems() {
|
|
69
72
|
return this._unavailableItems;
|
|
70
73
|
}
|
|
@@ -86,11 +89,12 @@ class CartEditor {
|
|
|
86
89
|
getQuantity(item) {
|
|
87
90
|
return this._quantities.get(item.id) ?? 0;
|
|
88
91
|
}
|
|
89
|
-
// Sets how much of one package the cart holds; zero
|
|
90
|
-
// cannot buy (disabled, or priced in no list this storefront sells
|
|
91
|
-
// of a package that is not sold loose, and a quantity past what the
|
|
92
|
-
//
|
|
93
|
-
//
|
|
92
|
+
// Sets how much of one package the cart holds; zero removes it. Throws InvalidArgumentException
|
|
93
|
+
// for a package a customer cannot buy (disabled, or priced in no list this storefront sells
|
|
94
|
+
// from), a fractional quantity of a package that is not sold loose, and a quantity past what the
|
|
95
|
+
// wire can carry. See checkQuantity for the full list. Whether a package is sold loose comes from
|
|
96
|
+
// the catalog's package, because the Package passed in may be older than the catalog. Returns
|
|
97
|
+
// this, so edits chain.
|
|
94
98
|
setQuantity(item, quantity) {
|
|
95
99
|
const wireQuantity = CartEditor.toWireQuantity(quantity);
|
|
96
100
|
// Less than half a thousandth rounds to zero on the wire, a line that costs nothing, so it
|
|
@@ -102,8 +106,8 @@ class CartEditor {
|
|
|
102
106
|
this._quantities.set(item.id, this.checkQuantity(item.id, quantity, wireQuantity));
|
|
103
107
|
return this;
|
|
104
108
|
}
|
|
105
|
-
// Adds to
|
|
106
|
-
//
|
|
109
|
+
// Adds to the quantity of this package in the cart, as a quantity stepper does. A negative
|
|
110
|
+
// amount subtracts, to a minimum of zero.
|
|
107
111
|
add(item, quantity = 1) {
|
|
108
112
|
return this.setQuantity(item, Math.max(0, this.getQuantity(item) + quantity));
|
|
109
113
|
}
|
|
@@ -114,8 +118,8 @@ class CartEditor {
|
|
|
114
118
|
this._quantities.clear();
|
|
115
119
|
return this;
|
|
116
120
|
}
|
|
117
|
-
//
|
|
118
|
-
//
|
|
121
|
+
// The sum of the lines' final prices: taxes included, offers taken off, delivery not included. A
|
|
122
|
+
// customer sees it as the price of the cart.
|
|
119
123
|
get itemsTotal() {
|
|
120
124
|
return ShoppingList.sumOfFinalPrices(this.lines.map((line) => line.item));
|
|
121
125
|
}
|
|
@@ -170,27 +174,29 @@ class CartEditor {
|
|
|
170
174
|
return remaining > 0n ? amountFromScaledValue(remaining) : 0;
|
|
171
175
|
}
|
|
172
176
|
// The tax inside deliveryCharge, from GeneralSettings.deliveryChargeTaxes. Those are the
|
|
173
|
-
// business's own, unrelated to a product's
|
|
177
|
+
// business's own, unrelated to a product's, and are already included in the charge. Zero when
|
|
174
178
|
// delivery is free.
|
|
175
179
|
get deliveryTax() {
|
|
176
180
|
return this.buildDeliveryChargeItem(this.getDeliveryCharge(this.itemsTotal), 0)?.totalTax ?? 0;
|
|
177
181
|
}
|
|
178
|
-
// What the cart comes to, taxes included. Delivery is
|
|
179
|
-
// leaves it out and a checkout page
|
|
180
|
-
// build()
|
|
182
|
+
// What the cart comes to, taxes included. Delivery is included only when withDeliveryCharge is
|
|
183
|
+
// true: a cart page leaves it out and a checkout page includes it, as in build(). It is the total
|
|
184
|
+
// of the list build() returns, and so of an order placed from it.
|
|
181
185
|
total(withDeliveryCharge = false) {
|
|
182
186
|
return this.build(withDeliveryCharge).totalAmount;
|
|
183
187
|
}
|
|
184
|
-
// The cart as a list, to write (Model.updateCartLocalOnly
|
|
185
|
-
//
|
|
188
|
+
// The cart as a list, to write (Model.updateCartLocalOnly or Model.updateCart) and to SHOW: every
|
|
189
|
+
// price, total and tax a cart or checkout page displays can be read off it. Model.createOrder
|
|
190
|
+
// takes no list: it orders the cart as stored.
|
|
186
191
|
// Each line stores its price without taxes and is flagged original, as Java ShoppingList.addItem
|
|
187
|
-
// does before a list is final. The list
|
|
192
|
+
// does before a list is final. The list is not final: Model.createOrder sends it final.
|
|
188
193
|
//
|
|
189
194
|
// withDeliveryCharge adds the delivery charge as a line (ReservedPackage.DELIVERY_CHARGE_PACKAGE)
|
|
190
|
-
// and sets ShoppingList.autoAddDeliveryCharge. A cart page leaves it out (the default)
|
|
191
|
-
// page
|
|
192
|
-
// waived: at zero, with the regular charge (less its taxes) as its priceDiscount, so a
|
|
193
|
-
// show FREE with the original struck through (Java
|
|
195
|
+
// and sets ShoppingList.autoAddDeliveryCharge. A cart page leaves it out (the default). A
|
|
196
|
+
// checkout page whose customer has chosen delivery passes true. The line is added even when the
|
|
197
|
+
// charge is waived: at zero, with the regular charge (less its taxes) as its priceDiscount, so a
|
|
198
|
+
// bill can show FREE with the original struck through (Java
|
|
199
|
+
// ShoppingCartViewModel.setDeliveryCharge).
|
|
194
200
|
// An empty cart gets no line: a list holding only a delivery charge is not an order, and Java
|
|
195
201
|
// removes that line when it is all that is left.
|
|
196
202
|
build(withDeliveryCharge = false) {
|
|
@@ -203,8 +209,8 @@ class CartEditor {
|
|
|
203
209
|
if (delivery !== null)
|
|
204
210
|
items.push(delivery);
|
|
205
211
|
// The total is the sum of the final prices of the lines sent, tax included, as ShoppingList.proto
|
|
206
|
-
// defines it. There is no list-wide discount: an offer comes off a line, and a storefront
|
|
207
|
-
// no other.
|
|
212
|
+
// defines it. There is no list-wide discount: an offer comes off a line, and a storefront
|
|
213
|
+
// applies no other.
|
|
208
214
|
return new ShoppingList(false, delivery !== null, false, this._inventoryLocationId, this._catalog.priceList?.id ?? MERABILLS_ONLINE_PRICE_LIST_ID, ShoppingList.sumOfFinalPrices(items), 0, items.length, 0, Object.freeze(items), Object.freeze([]));
|
|
209
215
|
}
|
|
210
216
|
// The delivery charge as a line: one unit at what it costs now (charge: the regular charge, or
|
|
@@ -236,13 +242,14 @@ class CartEditor {
|
|
|
236
242
|
return charge; // Never free, at any total
|
|
237
243
|
return itemsTotal >= freeDeliveryTotal ? 0 : charge;
|
|
238
244
|
}
|
|
239
|
-
// The package this id
|
|
245
|
+
// The package with this id, or null unless a customer can buy it
|
|
240
246
|
getBuyablePackage(packageId) {
|
|
241
247
|
const item = this._catalog.getPackage(packageId);
|
|
242
248
|
return item !== null && item.isBuyable ? item : null;
|
|
243
249
|
}
|
|
244
|
-
// Keeps a line of a cart that was read, as setQuantity would, and
|
|
245
|
-
// that cannot be kept goes to unavailableItems,
|
|
250
|
+
// Keeps a line of a cart that was read, as setQuantity would, and returns whether it could. A
|
|
251
|
+
// line that cannot be kept goes to unavailableItems instead of throwing, because it is not a
|
|
252
|
+
// caller's error.
|
|
246
253
|
tryKeep(item) {
|
|
247
254
|
try {
|
|
248
255
|
const wireQuantity = CartEditor.toWireQuantity(item.quantity);
|
|
@@ -258,18 +265,18 @@ class CartEditor {
|
|
|
258
265
|
}
|
|
259
266
|
}
|
|
260
267
|
// A quantity travels as an int32 multiplied by Constants.QUANTITY_FACTOR (Java rounds to it with
|
|
261
|
-
// NumberHelper.floatToInt), so the
|
|
262
|
-
//
|
|
263
|
-
// have.
|
|
268
|
+
// NumberHelper.floatToInt), so the smallest quantity is a thousandth. This converts a quantity
|
|
269
|
+
// to that. The editor stores the quantity the wire holds, so getQuantity reports the quantity the
|
|
270
|
+
// order will have.
|
|
264
271
|
static toWireQuantity(quantity) {
|
|
265
272
|
if (!Number.isFinite(quantity) || quantity < 0)
|
|
266
273
|
throw new InvalidArgumentException(`${quantity} is not a quantity`);
|
|
267
274
|
return Math.round(quantity * proto_Constants.QUANTITY_FACTOR);
|
|
268
275
|
}
|
|
269
|
-
//
|
|
270
|
-
// InvalidArgumentException
|
|
271
|
-
//
|
|
272
|
-
//
|
|
276
|
+
// Returns the quantity to store for this much of a package, as the wire holds it. Throws
|
|
277
|
+
// InvalidArgumentException when the cart cannot have it: the catalog does not sell the package,
|
|
278
|
+
// it is not sold loose and the quantity is a fraction, the quantity or its amount is more than
|
|
279
|
+
// the protocol carries, or the cart is full. Everything is checked against the catalog's
|
|
273
280
|
// package, which the order is priced from.
|
|
274
281
|
checkQuantity(packageId, quantity, wireQuantity) {
|
|
275
282
|
const buyable = this.getBuyablePackage(packageId);
|
|
@@ -277,19 +284,19 @@ class CartEditor {
|
|
|
277
284
|
throw new InvalidArgumentException(`Package ${Ids.toStorageHexString(packageId)} cannot be bought from this catalog`);
|
|
278
285
|
if (!buyable.isSoldLoose && !Number.isInteger(quantity))
|
|
279
286
|
throw new InvalidArgumentException(`${buyable.name} is not sold loose, so it cannot be bought ${quantity} at a time`);
|
|
280
|
-
// The most of one package a cart can hold.
|
|
287
|
+
// The most of one package a cart can hold. Rejected here so it cannot overflow on the wire
|
|
281
288
|
if (wireQuantity > CartEditor.MAX_WIRE_QUANTITY)
|
|
282
289
|
throw new InvalidArgumentException(`${quantity} is more than the ${CartEditor.MAX_WIRE_QUANTITY / proto_Constants.QUANTITY_FACTOR} the protocol can carry`);
|
|
283
|
-
// A list holds at most Constants.SHOPPING_LIST_MAX_ITEMS items, and the delivery charge
|
|
284
|
-
// (Java ShoppingList.addItem counts it like any other, and a server
|
|
285
|
-
// over). Where the business delivers, one place is
|
|
286
|
-
// could not be ordered for delivery.
|
|
290
|
+
// A list holds at most Constants.SHOPPING_LIST_MAX_ITEMS items, and the delivery charge counts
|
|
291
|
+
// as one (Java ShoppingList.addItem counts it like any other, and a server rejects a list one
|
|
292
|
+
// line over). Where the business delivers, one place is reserved for it: a cart filled to the
|
|
293
|
+
// limit could not be ordered for delivery.
|
|
287
294
|
const maxLines = proto_Constants.SHOPPING_LIST_MAX_ITEMS - (this.isDeliveryOffered ? 1 : 0);
|
|
288
295
|
if (!this._quantities.has(packageId) && this._quantities.size >= maxLines)
|
|
289
296
|
throw new InvalidArgumentException(`A cart holds at most ${maxLines} lines`);
|
|
290
|
-
// Java
|
|
291
|
-
// it, and so does pricing an item here.
|
|
292
|
-
//
|
|
297
|
+
// Java rejects an amount past Transaction.MAX_AMOUNT (validateAmount) in the item that reaches
|
|
298
|
+
// it, and so does pricing an item here. The line is priced before it is kept, so the error is
|
|
299
|
+
// raised here and not when a list is written.
|
|
293
300
|
const stored = wireQuantity / proto_Constants.QUANTITY_FACTOR;
|
|
294
301
|
try {
|
|
295
302
|
validatePricing(new CartLine(buyable, this._catalog.getProduct(buyable.productId), stored).item);
|
|
@@ -310,7 +317,7 @@ export default CartEditor;
|
|
|
310
317
|
// holds, rebuilt on every read of CartEditor.lines.
|
|
311
318
|
//
|
|
312
319
|
// Everything is read off item, the line as a shopping list would store it, so the cart page shows
|
|
313
|
-
//
|
|
320
|
+
// the same prices as the order and no price is computed twice.
|
|
314
321
|
export class CartLine {
|
|
315
322
|
constructor(_package, _product, _quantity) {
|
|
316
323
|
this._package = _package;
|
|
@@ -321,7 +328,8 @@ export class CartLine {
|
|
|
321
328
|
get package() {
|
|
322
329
|
return this._package;
|
|
323
330
|
}
|
|
324
|
-
// Null when the package's product is not in the catalog
|
|
331
|
+
// Null when the package's product is not in the catalog. A buyable package always has its
|
|
332
|
+
// product there.
|
|
325
333
|
get product() {
|
|
326
334
|
return this._product;
|
|
327
335
|
}
|
|
@@ -329,7 +337,7 @@ export class CartLine {
|
|
|
329
337
|
return this._quantity;
|
|
330
338
|
}
|
|
331
339
|
// This line as a shopping list holds it: priced without taxes, with the taxes beside it. Show
|
|
332
|
-
// this to show the line as an order does. Its
|
|
340
|
+
// this to show the line as an order does. Its shoppingListOrder is always 0 and means nothing.
|
|
333
341
|
get item() {
|
|
334
342
|
this._item ?? (this._item = this.toShoppingListItem(0));
|
|
335
343
|
return this._item;
|
|
@@ -340,8 +348,8 @@ export class CartLine {
|
|
|
340
348
|
return includedTaxes(this._product?.taxes ?? []);
|
|
341
349
|
}
|
|
342
350
|
// What one unit costs, taxes included: the offer price where the package is on offer, else the
|
|
343
|
-
// regular price. This is the catalog's price to within tax rounding (a thousandth or two,
|
|
344
|
-
//
|
|
351
|
+
// regular price. This is the catalog's price to within tax rounding (a thousandth or two, too
|
|
352
|
+
// small to show). A buyable package always has a price, so no default price is used.
|
|
345
353
|
get unitPrice() {
|
|
346
354
|
return this.item.unitPriceWithTaxes;
|
|
347
355
|
}
|
|
@@ -372,8 +380,8 @@ export class CartLine {
|
|
|
372
380
|
get finalPrice() {
|
|
373
381
|
return this.item.finalPrice;
|
|
374
382
|
}
|
|
375
|
-
// This line as the protocol carries it. CartEditor.build calls it
|
|
376
|
-
//
|
|
383
|
+
// This line as the protocol carries it. CartEditor.build calls it with the line's position as
|
|
384
|
+
// shoppingListOrder. Taxes are taken out of the price here (see buildItem).
|
|
377
385
|
toShoppingListItem(shoppingListOrder) {
|
|
378
386
|
const price = this._package.price;
|
|
379
387
|
return buildItem(this._package.id, this._quantity, price?.finalPrice ?? 0, price?.regularPrice ?? 0, this._product?.taxes ?? [], shoppingListOrder);
|
|
@@ -384,13 +392,13 @@ export class CartLine {
|
|
|
384
392
|
function includedTaxes(taxes) {
|
|
385
393
|
return Object.freeze(taxes.filter((tax) => Number.isFinite(tax.rate) && tax.rate > 0));
|
|
386
394
|
}
|
|
387
|
-
//
|
|
388
|
-
// out of a price, as Java ShoppingListItem.setDetailsFromTaxInclusivePrices does. The offer
|
|
389
|
-
// and the regular price each have their taxes removed, and the price discount is the
|
|
390
|
-
// those two, which is not the difference of the prices with taxes. The
|
|
391
|
-
// ProductSelectionCard
|
|
392
|
-
// lines with no package), no stock keeping or expiry (a
|
|
393
|
-
// original, as in build.
|
|
395
|
+
// Builds a shopping list item from prices that include taxes, so this is where CartEditor takes a
|
|
396
|
+
// tax out of a price, as Java ShoppingListItem.setDetailsFromTaxInclusivePrices does. The offer
|
|
397
|
+
// price and the regular price each have their taxes removed, and the price discount is the
|
|
398
|
+
// difference of those two, which is not the difference of the prices with taxes. The other fields
|
|
399
|
+
// are as Java's ProductSelectionCard sets them on a new item: no discount besides the price
|
|
400
|
+
// discount, no description (that is for lines with no package), no stock keeping or expiry (a
|
|
401
|
+
// storefront is not sent them). The item is flagged original, as in build.
|
|
394
402
|
// A price is a whole number of thousandths and each tax is rounded, so some prices cannot be
|
|
395
403
|
// reached. The item then has the nearest price (TaxHelper.removeTaxes).
|
|
396
404
|
function buildItem(packageId, quantity, offerPriceWithTaxes, regularPriceWithTaxes, taxes, shoppingListOrder) {
|
package/dist/Catalog.d.ts
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import Item from "./Item";
|
|
2
2
|
import Package from "./Package";
|
|
3
|
+
import Price from "./Price";
|
|
3
4
|
import PriceList from "./PriceList";
|
|
4
5
|
import Product from "./Product";
|
|
5
6
|
import ProductCategory from "./ProductCategory";
|
|
6
7
|
export default class Catalog {
|
|
7
|
-
|
|
8
|
-
constructor(categories: ReadonlyArray<ProductCategory>, products: ReadonlyArray<Product>, _priceList: PriceList | null);
|
|
8
|
+
constructor(categories: ReadonlyArray<ProductCategory>, products: ReadonlyArray<Product>, packages: ReadonlyArray<Package>, priceLists: ReadonlyArray<PriceList>, prices: ReadonlyArray<Price>);
|
|
9
9
|
get categories(): ReadonlyArray<ProductCategory>;
|
|
10
10
|
get products(): ReadonlyArray<Product>;
|
|
11
|
+
get currentSalePriceListId(): bigint;
|
|
12
|
+
private set currentSalePriceListId(value);
|
|
11
13
|
get priceList(): PriceList | null;
|
|
12
14
|
get productCount(): number;
|
|
13
15
|
get packageCount(): number;
|
|
@@ -19,10 +21,20 @@ export default class Catalog {
|
|
|
19
21
|
private static compareNames;
|
|
20
22
|
private static compareIds;
|
|
21
23
|
private static comparePrices;
|
|
24
|
+
private applySalePriceList;
|
|
22
25
|
static readonly EMPTY: Catalog;
|
|
23
|
-
private readonly
|
|
24
|
-
private readonly
|
|
25
|
-
private readonly
|
|
26
|
-
private readonly
|
|
27
|
-
private readonly
|
|
26
|
+
private readonly _allCategories;
|
|
27
|
+
private readonly _allProducts;
|
|
28
|
+
private readonly _uncategorized;
|
|
29
|
+
private readonly _candidatePackages;
|
|
30
|
+
private readonly _priceListMap;
|
|
31
|
+
private _currentSalePriceListId;
|
|
32
|
+
private _categories;
|
|
33
|
+
private _products;
|
|
34
|
+
private _categoryMap;
|
|
35
|
+
private _productMap;
|
|
36
|
+
private _packageMap;
|
|
37
|
+
}
|
|
38
|
+
export interface UpdatableCatalog {
|
|
39
|
+
set currentSalePriceListId(value: bigint);
|
|
28
40
|
}
|