@peabody-soft/mbs-model-ts 1.3.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/dist/CartEditor.d.ts +10 -5
  2. package/dist/CartEditor.js +232 -125
  3. package/dist/Catalog.js +26 -18
  4. package/dist/Enums.d.ts +1 -1
  5. package/dist/Enums.js +2 -1
  6. package/dist/GeneralSettings.d.ts +4 -2
  7. package/dist/GeneralSettings.js +18 -7
  8. package/dist/Ids.d.ts +2 -0
  9. package/dist/Ids.js +10 -0
  10. package/dist/Message.js +3 -2
  11. package/dist/Model.d.ts +5 -0
  12. package/dist/Model.js +263 -50
  13. package/dist/Package.js +2 -2
  14. package/dist/Price.js +6 -2
  15. package/dist/Product.d.ts +5 -3
  16. package/dist/Product.js +30 -18
  17. package/dist/Profile.d.ts +1 -0
  18. package/dist/Profile.js +91 -4
  19. package/dist/ProtoMap.d.ts +15 -8
  20. package/dist/ProtoMap.js +267 -140
  21. package/dist/ShoppingList.d.ts +30 -1
  22. package/dist/ShoppingList.js +345 -35
  23. package/dist/SyncCache.d.ts +9 -0
  24. package/dist/SyncCache.js +93 -30
  25. package/dist/SyncState.js +1 -1
  26. package/dist/Tax.d.ts +12 -4
  27. package/dist/Tax.js +33 -4
  28. package/dist/TaxHelper.d.ts +12 -0
  29. package/dist/TaxHelper.js +93 -0
  30. package/dist/TaxType.d.ts +7 -0
  31. package/dist/TaxType.js +32 -0
  32. package/dist/WorkingDays.d.ts +3 -2
  33. package/dist/WorkingDays.js +16 -12
  34. package/dist/generated/AttachmentType.d.ts +19 -1
  35. package/dist/generated/AttachmentType.js +18 -0
  36. package/dist/generated/AuthTokens.d.ts +3 -1
  37. package/dist/generated/BusinessLink.d.ts +4 -1
  38. package/dist/generated/BusinessSyncInRequest.d.ts +11 -3
  39. package/dist/generated/BusinessSyncInResponse.d.ts +5 -4
  40. package/dist/generated/BusinessSyncOutRequest.d.ts +37 -10
  41. package/dist/generated/BusinessSyncOutRequest.js +42 -29
  42. package/dist/generated/BusinessSyncOutResponse.d.ts +14 -1
  43. package/dist/generated/Configuration.d.ts +2 -1
  44. package/dist/generated/CreateUserRequest.d.ts +8 -2
  45. package/dist/generated/Entity.d.ts +7 -3
  46. package/dist/generated/GeneralSettings.d.ts +14 -4
  47. package/dist/generated/GeneralSettings.js +9 -1
  48. package/dist/generated/Message.d.ts +4 -2
  49. package/dist/generated/Product.d.ts +10 -1
  50. package/dist/generated/Product.js +9 -1
  51. package/dist/generated/ProfileSyncOutRequest.d.ts +17 -1
  52. package/dist/generated/ProfileSyncOutResponse.d.ts +8 -3
  53. package/dist/generated/SalesOrderPayload.d.ts +7 -2
  54. package/dist/generated/ShoppingList.d.ts +6 -3
  55. package/dist/generated/ShoppingListItem.d.ts +21 -6
  56. package/dist/generated/Tax.d.ts +4 -4
  57. package/dist/generated/Tax.js +7 -8
  58. package/dist/generated/WorkingDays.d.ts +9 -1
  59. package/dist/generated/WorkingDays.js +12 -4
  60. package/dist/generated/WorkingHoursRange.d.ts +2 -2
  61. package/dist/index.d.ts +2 -2
  62. package/dist/index.js +4 -3
  63. package/package.json +55 -55
  64. package/dist/generated/TaxType.d.ts +0 -25
  65. package/dist/generated/TaxType.js +0 -30
package/dist/Product.js CHANGED
@@ -2,7 +2,7 @@ import Item from "./Item";
2
2
  // A product in the catalog: goods, a service, a raw material or a fixed asset. Built complete
3
3
  // (photos/packages resolved first) so everything here is readonly except category (via parentItemId).
4
4
  export default class Product extends Item {
5
- constructor(header, name, description, productCategoryId, isDisabled, _productType, _brand, _isManufacturedByBusiness, _manufacturer, _manufacturerAddress, _taxes, _features, _photos, _packages) {
5
+ constructor(header, name, description, productCategoryId, isDisabled, _productType, _brand, _isManufacturedByBusiness, _manufacturer, _manufacturerAddress, _taxes, _features, _isForOfflineSaleOnly, _photos, packages) {
6
6
  super(header, name, description, productCategoryId, isDisabled);
7
7
  this._productType = _productType;
8
8
  this._brand = _brand;
@@ -11,8 +11,11 @@ export default class Product extends Item {
11
11
  this._manufacturerAddress = _manufacturerAddress;
12
12
  this._taxes = _taxes;
13
13
  this._features = _features;
14
+ this._isForOfflineSaleOnly = _isForOfflineSaleOnly;
14
15
  this._photos = _photos;
15
- this._packages = _packages;
16
+ // A package that cannot be bought is of no use to anyone who has this product, so it is not
17
+ // kept - whoever builds a product, and whatever they hand it
18
+ this._packages = Object.freeze(packages.filter((pkg) => pkg.isBuyable));
16
19
  }
17
20
  // The category this product is in, or Constants.ENTITY_ID_NULL when it is in none
18
21
  get productCategoryId() {
@@ -24,8 +27,8 @@ export default class Product extends Item {
24
27
  get brand() {
25
28
  return this._brand;
26
29
  }
27
- // Null when the business hasn't said either way - the three states are why this isn't a plain
28
- // boolean.
30
+ // False only when the business says it does not make this product itself: what is sent is false,
31
+ // or nothing, which Product.proto says is true.
29
32
  get isManufacturedByBusiness() {
30
33
  return this._isManufacturedByBusiness;
31
34
  }
@@ -35,6 +38,8 @@ export default class Product extends Item {
35
38
  get manufacturerAddress() {
36
39
  return this._manufacturerAddress;
37
40
  }
41
+ // The taxes INCLUDED in the price of every package of this product (Price) - not added to it.
42
+ // A UI shows a price as it is, and shows these only as what a bill's total includes.
38
43
  get taxes() {
39
44
  return this._taxes;
40
45
  }
@@ -42,22 +47,32 @@ export default class Product extends Item {
42
47
  get features() {
43
48
  return this._features;
44
49
  }
50
+ // True when the business sells this product only offline: the server sends such a product like any
51
+ // other, flagged, and it is for this model to not offer it to a customer - see isBuyable. A catalog
52
+ // never holds one, so only a product that is built by hand, not one that is read from a catalog,
53
+ // can be seen to say so.
54
+ get isForOfflineSaleOnly() {
55
+ return this._isForOfflineSaleOnly;
56
+ }
45
57
  get photos() {
46
58
  return this._photos;
47
59
  }
48
- // Every variant of this product, in the order they arrived - ordering them for display is a UI
49
- // decision, see Catalog.comparePackages. A product with no packages cannot be bought.
60
+ // Every buyable variant of this product, in the order they arrived - ordering them for display
61
+ // is a UI decision, see Catalog.comparePackages. A variant that cannot be bought is not here, and
62
+ // a product with none cannot be bought.
50
63
  get packages() {
51
64
  return this._packages;
52
65
  }
53
- // The cheapest final price across variants (what a listing shows as "from"), or null if none has
54
- // a price. Compares final price, not regular, since that's what a customer would actually pay.
66
+ // The cheapest final price across variants (what a listing shows as "from"), or null if there
67
+ // are no variants. Compares final price, not regular, since that's what a customer would
68
+ // actually pay. Every variant is taxed at this product's rates, so prices with taxes in them
69
+ // order the variants as prices without would.
55
70
  get lowestPrice() {
56
71
  let result = null;
57
72
  for (const pkg of this._packages) {
58
73
  const price = pkg.price;
59
74
  if (price === null)
60
- continue; // This variant cannot be bought
75
+ continue; // Not so of a buyable variant, though the type does not say
61
76
  if (result === null || price.finalPrice < result.finalPrice)
62
77
  result = price;
63
78
  }
@@ -70,15 +85,12 @@ export default class Product extends Item {
70
85
  return true;
71
86
  return false;
72
87
  }
73
- // True when this product itself is enabled and at least one variant is buyable. The server
74
- // still filters out draft and non-public products before they ever arrive, but isDisabled can
75
- // change after an item exists, so it is checked here rather than trusted to have been filtered.
88
+ // True when this product itself is enabled, is not one that the business sells only offline, and
89
+ // it has at least one buyable variant, which is to say any variant, since it keeps no other. The
90
+ // server still filters out draft and non-public products before they ever arrive, but isDisabled
91
+ // can change after an item exists, and the server does not filter out what is sold only offline
92
+ // (it sends the flag), so both are checked here rather than trusted to have been filtered.
76
93
  get isBuyable() {
77
- if (this.isDisabled)
78
- return false;
79
- for (const pkg of this._packages)
80
- if (pkg.isBuyable)
81
- return true;
82
- return false;
94
+ return !this.isDisabled && !this._isForOfflineSaleOnly && this._packages.length > 0;
83
95
  }
84
96
  }
package/dist/Profile.d.ts CHANGED
@@ -18,3 +18,4 @@ export default class Profile {
18
18
  private readonly _businessMap;
19
19
  private readonly _businessLinkMap;
20
20
  }
21
+ export declare function canonicalizeBusinessId(businessId: string): string;
package/dist/Profile.js CHANGED
@@ -15,8 +15,16 @@ export default class Profile {
15
15
  this._businesses = Object.freeze(businesses.filter((business) => linkedBusinessIds.has(business.id)));
16
16
  for (const business of this._businesses)
17
17
  this._businessMap.set(business.id, business);
18
- for (const businessLink of this._businessLinks)
19
- this._businessLinkMap.set(businessLink.businessId, businessLink);
18
+ // One link per business is reached through, and it is the one that names this customer. A profile can hold more
19
+ // than one link to a business - one that is not this customer's is not what a customer writes to a business with, nor
20
+ // what a link token of theirs signs - so a link that does not name this customer does not take the place of one that
21
+ // does, whichever comes last. Where none names them (no user is held yet), the last is kept.
22
+ const isOwn = (link) => this._user !== null && link.linkedEntityId === this._user.id;
23
+ for (const businessLink of this._businessLinks) {
24
+ const held = this._businessLinkMap.get(businessLink.businessId);
25
+ if (held === undefined || isOwn(businessLink) || !isOwn(held))
26
+ this._businessLinkMap.set(businessLink.businessId, businessLink);
27
+ }
20
28
  }
21
29
  // The id of the profile container this was built from, and what Model.signIn was given
22
30
  get profileId() {
@@ -42,10 +50,13 @@ export default class Profile {
42
50
  getBusinessLink(businessEntityId) {
43
51
  return this._businessLinkMap.get(businessEntityId) ?? null;
44
52
  }
45
- // The same, by the url friendly business id a storefront is reached by
53
+ // The same, by the url friendly business id a storefront is reached by - however it is written in the url, which
54
+ // the server finds a business by whatever its case, and which the one here is not to fail: a customer who has joined
55
+ // it is not to be taken for one who has not, and asked to join it again
46
56
  getBusinessLinkFor(businessId) {
57
+ const wanted = canonicalizeBusinessId(businessId);
47
58
  for (const business of this._businesses)
48
- if (business.businessId === businessId)
59
+ if (canonicalizeBusinessId(business.businessId) === wanted)
49
60
  return this.getBusinessLink(business.id);
50
61
  return null;
51
62
  }
@@ -53,3 +64,79 @@ export default class Profile {
53
64
  return `Profile(profileId=${this._profileId}, user=${this._user?.formattedName ?? "none"}, businesses=${this._businesses.length})`;
54
65
  }
55
66
  }
67
+ // A business id as the server makes it canonical (Java Business.canonicalizeBusinessId) to find one by it: with its spaces
68
+ // normalized the way Java StringHelper.normalizeSpace does, and in lower case. A url is typed, and linked to, in any case, and a
69
+ // business is the same business in all of them.
70
+ // The spaces are not only those at either end, because a business id can hold some: Java makes one out of the display name when
71
+ // none is given ("Warm Loaf_1a2b3c4d"), and takes whatever is typed, so a url that has two spaces where the id has one is still
72
+ // that business. normalizeSpace is followed step for step, so as to give what Java gives for every string, quirks included: it
73
+ // leaves a string alone that has no whitespace at either end and none twice in a row, and a non-breaking space is not whitespace
74
+ // to it, so one between two words stays as it was, and is made a plain space only when something else of the string is normalized.
75
+ // What Java then refuses, an id that is not between four and sixty-four characters long, is not refused here, since this is only
76
+ // the comparison: the server answers a read of one with a NotFoundException, as it does for a business that does not exist.
77
+ export function canonicalizeBusinessId(businessId) {
78
+ return normalizeSpace(businessId).toLowerCase();
79
+ }
80
+ // Java StringHelper.normalizeSpace, which answers null where this answers an empty string: a string of nothing but whitespace
81
+ function normalizeSpace(input) {
82
+ if (!needsNormalization(input))
83
+ return input;
84
+ let result = "";
85
+ let whitespaceCount = 0;
86
+ let isAtStart = true;
87
+ for (let index = 0; index < input.length; ++index) {
88
+ const unit = input.charCodeAt(index);
89
+ if (isWhitespace(unit)) {
90
+ if (whitespaceCount === 0 && !isAtStart)
91
+ result += " ";
92
+ ++whitespaceCount;
93
+ }
94
+ else {
95
+ isAtStart = false;
96
+ result += unit === NO_BREAK_SPACE ? " " : input.charAt(index);
97
+ whitespaceCount = 0;
98
+ }
99
+ }
100
+ if (isAtStart)
101
+ return "";
102
+ // Java String.trim, which takes every character up to a space off either end, and not the trim of a script, which takes the
103
+ // spaces of Unicode. The space that stands for whitespace at the end goes with it: Java drops that one first, to the same end
104
+ let first = 0;
105
+ let last = result.length;
106
+ while (first < last && result.charCodeAt(first) <= SPACE)
107
+ ++first;
108
+ while (last > first && result.charCodeAt(last - 1) <= SPACE)
109
+ --last;
110
+ return result.slice(first, last);
111
+ }
112
+ // Java StringHelper.needsNormalization: whitespace at either end, or two together between
113
+ function needsNormalization(input) {
114
+ if (input.length === 0)
115
+ return false;
116
+ if (isWhitespace(input.charCodeAt(0)) || isWhitespace(input.charCodeAt(input.length - 1)))
117
+ return true;
118
+ let wasWhitespace = false;
119
+ for (let index = 1; index < input.length - 1; ++index) {
120
+ const isNowWhitespace = isWhitespace(input.charCodeAt(index));
121
+ if (isNowWhitespace && wasWhitespace)
122
+ return true;
123
+ wasWhitespace = isNowWhitespace;
124
+ }
125
+ return false;
126
+ }
127
+ // Java Character.isWhitespace: the spaces of Unicode that do not hold a place (so not the no-break ones, U+00A0, U+2007 and
128
+ // U+202F) and the controls that are spaces. Not what a regular expression's \s is, which has the no-break spaces and the
129
+ // byte order mark, and has not the four separators U+001C to U+001F
130
+ function isWhitespace(unit) {
131
+ if (unit === SPACE)
132
+ return true;
133
+ if (unit >= 0x09 && unit <= 0x0d)
134
+ return true; // Tab, line feed, vertical tab, form feed and carriage return
135
+ if (unit >= 0x1c && unit <= 0x1f)
136
+ return true; // The four separators
137
+ if (unit >= 0x2000 && unit <= 0x200a)
138
+ return unit !== 0x2007;
139
+ return unit === 0x1680 || unit === 0x2028 || unit === 0x2029 || unit === 0x205f || unit === 0x3000;
140
+ }
141
+ const SPACE = 0x20;
142
+ const NO_BREAK_SPACE = 0xa0;
@@ -34,25 +34,32 @@ export declare function getProfileResponseEntities(response: proto_ProfileSyncOu
34
34
  export declare function businessResponseToCacheWrite(response: proto_BusinessSyncOutResponse): CacheWrite;
35
35
  export declare function profileResponseToCacheWrite(response: proto_ProfileSyncOutResponse): CacheWrite;
36
36
  export declare function getBusinessResponseAttachmentBaseUrlExpiry(response: proto_BusinessSyncOutResponse | null): bigint;
37
- export declare function orderToBlob(order: proto_Message): StoredBlob;
37
+ export declare function orderToBlob(order: proto_Message, heldETag: bigint | null): StoredBlob;
38
38
  export declare function isPartyPreferencesMessage(message: proto_Message): boolean;
39
39
  export declare function getMessageUpdatedAt(message: proto_Message): bigint;
40
- export declare function partyPreferencesToBlob(partyPreferences: proto_Message): StoredBlob;
40
+ export declare function getMessageId(message: proto_Message): bigint;
41
+ export declare function partyPreferencesToBlob(partyPreferences: proto_Message, heldETag: bigint | null): StoredBlob;
41
42
  export declare function businessResponseFromBlobs(blobs: ReadonlyArray<StoredBlob>): proto_BusinessSyncOutResponse | null;
42
43
  export declare function profileResponseFromBlobs(blobs: ReadonlyArray<StoredBlob>): proto_ProfileSyncOutResponse | null;
43
44
  export declare function orderMessagesFromBlobs(blobs: ReadonlyArray<StoredBlob>): ReadonlyArray<proto_Message>;
44
45
  export declare function partyPreferencesMessageFromMessages(messages: ReadonlyArray<proto_Message>): proto_Message | null;
45
46
  export declare function partyPreferencesMessageFromBlobs(blobs: ReadonlyArray<StoredBlob>): proto_Message | null;
46
- export declare function buildBusinessSyncOutRequest(businessId: string, syncInState: SyncState, attachmentBaseUrlExpiry: bigint): proto_BusinessSyncOutRequest;
47
- export declare function buildBusinessSyncOutRequestFromCache(businessId: string, cached: proto_BusinessSyncOutResponse | null): proto_BusinessSyncOutRequest;
47
+ export declare function buildBusinessSyncOutRequest(business: string | bigint, syncInState: SyncState, attachmentBaseUrlExpiry: bigint): proto_BusinessSyncOutRequest;
48
+ export declare function cursorsToBlobs(cursors: SyncState, movedKeys: ReadonlySet<number>): Array<StoredBlob>;
49
+ export declare function cursorsFromBlobs(keys: ReadonlyArray<number>, blobs: ReadonlyArray<StoredBlob | null>): SyncState;
50
+ export declare function raiseBusinessCursors(cursors: SyncState, response: proto_BusinessSyncOutResponse): ReadonlySet<number>;
48
51
  export declare function buildBusinessSyncInRequest(messageUpdateNumber: bigint, messages: ReadonlyArray<proto_Message>): proto_BusinessSyncInRequest;
49
52
  export declare function getSyncInResponseMessages(response: proto_BusinessSyncInResponse): ReadonlyArray<proto_Message>;
50
53
  export declare function buildProfileSyncOutRequest(syncInState: SyncState, joinBusinessId?: bigint): proto_ProfileSyncOutRequest;
51
- export declare function buildProfileSyncOutRequestFromCache(cached: proto_ProfileSyncOutResponse | null, joinBusinessId?: bigint): proto_ProfileSyncOutRequest;
54
+ export declare function raiseProfileCursors(cursors: SyncState, response: proto_ProfileSyncOutResponse): ReadonlySet<number>;
52
55
  export declare function buildUser(userProfile: UserProfile): proto_User;
53
56
  export declare function buildUpdateUser(userProfile: UserProfile): proto_User;
54
57
  export declare function hasJoinedBusinessLink(joinBusinessId: bigint, response: proto_ProfileSyncOutResponse): boolean;
55
58
  export declare function hasJoinedBusiness(joinBusinessId: bigint, response: proto_ProfileSyncOutResponse): boolean;
59
+ export declare function getBusinessResponseCountryCode(response: proto_BusinessSyncOutResponse | null): number | undefined;
60
+ export declare function getBusinessResponseBusinessEntityId(response: proto_BusinessSyncOutResponse | null): bigint | null;
61
+ export declare function getBusinessResponseBusinessId(response: proto_BusinessSyncOutResponse | null): string | null;
62
+ export declare function isBusinessResponseOnlineShoppingOn(response: proto_BusinessSyncOutResponse | null): boolean;
56
63
  export declare function buildAuthTokens(authTokens: AuthTokens): proto_AuthTokens;
57
64
  export declare function authTokensFromProto(authTokens: proto_AuthTokens): AuthTokens;
58
65
  export declare function buildAuthTokensBytes(authTokens: proto_AuthTokens): Uint8Array;
@@ -65,10 +72,10 @@ export declare function nextBusinessSyncOutRequest(request: proto_BusinessSyncOu
65
72
  export declare function nextProfileSyncOutRequest(request: proto_ProfileSyncOutRequest, response: proto_ProfileSyncOutResponse): proto_ProfileSyncOutRequest | null;
66
73
  export declare function buildBusiness(response: proto_BusinessSyncOutResponse): Business | null;
67
74
  export declare function buildProfile(profileId: string, response: proto_ProfileSyncOutResponse): Profile | null;
68
- export declare function buildOrder(message: proto_Message): Order | null;
69
- export declare function buildShoppingList(message: proto_Message): ShoppingList;
75
+ export declare function buildOrder(message: proto_Message, countryCode: number): Order | null;
76
+ export declare function buildShoppingList(message: proto_Message, countryCode: number): ShoppingList;
70
77
  export declare function buildPartyPreferencesMessage(messageId: bigint, existing: proto_Message | null, senderId: bigint, shoppingList: ShoppingList | null, fulfillmentMode: proto_FulfillmentMode | null, now: bigint): proto_Message;
71
78
  export declare function buildOrderMessage(orderId: bigint, senderId: bigint, shoppingList: ShoppingList, fulfillmentMode: proto_FulfillmentMode, deliveryAddressId: bigint, deliveryAddress: Address | null, note: string | null, creationDate: bigint): proto_Message;
72
79
  export declare function messagesToCacheWrite(messages: ReadonlyArray<proto_Message>): CacheWrite;
73
- export declare function highestCachedMessageUpdateNumber(blobs: ReadonlyArray<StoredBlob>): bigint;
74
80
  export declare function hasShoppingListChanged(stored: proto_Message | null, shoppingList: ShoppingList): boolean;
81
+ export declare function entityETag(updateNumber: bigint, updatedAt: bigint): bigint;