@peabody-soft/mbs-model-ts 1.7.0 → 1.7.1
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.js +4 -4
- package/dist/Constants.js +2 -2
- package/dist/ContainerNames.js +6 -6
- package/dist/Entity.js +4 -3
- package/dist/Enums.js +2 -2
- package/dist/Exceptions.js +2 -2
- package/dist/GeneralSettings.js +9 -8
- package/dist/HttpStatusCodes.js +7 -7
- package/dist/Ids.js +4 -5
- package/dist/Item.js +6 -4
- 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/PackageIdentifier.js +2 -2
- package/dist/Price.js +6 -5
- package/dist/PriceList.js +1 -1
- package/dist/Product.js +8 -6
- package/dist/Profile.js +15 -14
- package/dist/ProtoMap.js +159 -155
- package/dist/ServiceExceptions.js +6 -6
- package/dist/ServiceProxy.js +15 -14
- package/dist/ShoppingList.js +101 -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/index.js +11 -8
- package/package.json +1 -1
package/dist/ProtoMap.js
CHANGED
|
@@ -98,12 +98,12 @@ export function getProfileResponseEntities(response) {
|
|
|
98
98
|
result.push(toWireEntity(business.entity, proto_Business.toBinary(business)));
|
|
99
99
|
return result;
|
|
100
100
|
}
|
|
101
|
-
// One blob per entity, tombstones included and as sent, plus the attachment base url
|
|
102
|
-
//
|
|
101
|
+
// One blob per entity, tombstones included and as sent, plus a blob for the attachment base url,
|
|
102
|
+
// which belongs to the business and has no entity.
|
|
103
103
|
export function businessResponseToCacheWrite(response) {
|
|
104
104
|
const write = toCacheWrite(getBusinessResponseEntities(response));
|
|
105
|
-
//
|
|
106
|
-
// gets a later url only if one exists.
|
|
105
|
+
// The url's eTag is its expiry, which is also its cursor: a client sends back the expiry it holds
|
|
106
|
+
// and gets a later url only if one exists.
|
|
107
107
|
const attachmentBaseUrl = response.attachmentBaseUrl;
|
|
108
108
|
if (attachmentBaseUrl !== undefined)
|
|
109
109
|
write.blobs.push({
|
|
@@ -122,9 +122,10 @@ export function profileResponseToCacheWrite(response) {
|
|
|
122
122
|
export function getBusinessResponseAttachmentBaseUrlExpiry(response) {
|
|
123
123
|
return response?.attachmentBaseUrl?.expiry ?? 0n;
|
|
124
124
|
}
|
|
125
|
-
// One order as
|
|
126
|
-
//
|
|
127
|
-
// find it. heldETag is the eTag the container holds the order at, or
|
|
125
|
+
// One order as a blob, for a caller holding a message rather than a response. Only the payload says
|
|
126
|
+
// whether a message is an order, so a message without a sales order throws here instead of being
|
|
127
|
+
// stored where a read would not find it. heldETag is the eTag the container holds the order at, or
|
|
128
|
+
// null - see messageToBlob.
|
|
128
129
|
export function orderToBlob(order, heldETag) {
|
|
129
130
|
if (order.payload.oneofKind !== "salesOrder")
|
|
130
131
|
throw new InvalidArgumentException("That message carries no sales order, so it is not an order");
|
|
@@ -134,8 +135,8 @@ export function orderToBlob(order, heldETag) {
|
|
|
134
135
|
export function isPartyPreferencesMessage(message) {
|
|
135
136
|
return message.payload.oneofKind === "partyPreferences";
|
|
136
137
|
}
|
|
137
|
-
// When a message was last written.
|
|
138
|
-
// have no shared version.
|
|
138
|
+
// When a message was last written. Use it to order two copies of one thing held at different ids,
|
|
139
|
+
// which have no shared version.
|
|
139
140
|
export function getMessageUpdatedAt(message) {
|
|
140
141
|
return getEntity(message.entity).updatedAt;
|
|
141
142
|
}
|
|
@@ -149,8 +150,8 @@ export function partyPreferencesToBlob(partyPreferences, heldETag) {
|
|
|
149
150
|
return messageToBlob(partyPreferences, heldETag);
|
|
150
151
|
}
|
|
151
152
|
// Rebuilds the response a business container's blobs make up, as the call returned it. Null when they
|
|
152
|
-
// hold nothing yet. The entity type
|
|
153
|
-
// type is skipped, so a newer server cannot break an older client.
|
|
153
|
+
// hold nothing yet. The entity type is part of each blob id and picks the array a blob goes in, so no
|
|
154
|
+
// type tag is stored. An unknown type is skipped, so a newer server cannot break an older client.
|
|
154
155
|
export function businessResponseFromBlobs(blobs) {
|
|
155
156
|
const response = proto_BusinessSyncOutResponse.create({});
|
|
156
157
|
let hasContent = false;
|
|
@@ -260,9 +261,9 @@ export function orderMessagesFromBlobs(blobs) {
|
|
|
260
261
|
return ordersAmong(messagesFromBlobs(blobs, false));
|
|
261
262
|
}
|
|
262
263
|
// The orders the business has completed, by fulfilling or cancelling. The merchant app completes an
|
|
263
|
-
// order by deleting its message, so these are tombstones
|
|
264
|
-
// customer is shown the order afterwards. A tombstone
|
|
265
|
-
// sent only the header) is
|
|
264
|
+
// order by deleting its message, so these are tombstones. The server sends them whole because the
|
|
265
|
+
// customer is shown the order afterwards. A tombstone without an order (from an older server that
|
|
266
|
+
// sent only the header) is skipped.
|
|
266
267
|
export function deletedOrderMessagesFromBlobs(blobs) {
|
|
267
268
|
return ordersAmong(messagesFromBlobs(blobs, true));
|
|
268
269
|
}
|
|
@@ -270,12 +271,13 @@ function ordersAmong(messages) {
|
|
|
270
271
|
return messages.filter((message) => message.payload.oneofKind === "salesOrder");
|
|
271
272
|
}
|
|
272
273
|
// The party preferences message among these messages, or null. A customer has one per business, so
|
|
273
|
-
// two
|
|
274
|
-
//
|
|
275
|
-
// A BUSINESS ARE NOT PUBLIC.
|
|
274
|
+
// two mean that two callers' messages share a container. The one with the greater update number is
|
|
275
|
+
// returned, an arbitrary choice since nothing in a message says which caller it was sent to.
|
|
276
|
+
// See THE MESSAGES OF A BUSINESS ARE NOT PUBLIC.
|
|
276
277
|
// Split from partyPreferencesMessageFromBlobs so a caller already holding the messages (such as
|
|
277
278
|
// Model.businessSyncIn folding the anonymous container in) need not read the blobs again.
|
|
278
|
-
// buildBusiness uses it too, so the cart shown, amended and carried over
|
|
279
|
+
// buildBusiness uses it too, so the cart that is shown, amended and carried over at sign in always
|
|
280
|
+
// comes from the same message.
|
|
279
281
|
export function partyPreferencesMessageFromMessages(messages) {
|
|
280
282
|
let result = null;
|
|
281
283
|
for (const message of messages) {
|
|
@@ -297,9 +299,9 @@ export function partyPreferencesMessageFromBlobs(blobs) {
|
|
|
297
299
|
// #endregion
|
|
298
300
|
// #region Requests and credentials
|
|
299
301
|
// The request for everything changed since these cursors. A bigint business is the Business entity
|
|
300
|
-
// id, which a caller that has read
|
|
301
|
-
//
|
|
302
|
-
// held, sent back unchanged (zero when none).
|
|
302
|
+
// id, which a caller that has read the business before holds. A string is its url friendly
|
|
303
|
+
// businessId, the only identifier a first read has. attachmentBaseUrlExpiry is not one of the
|
|
304
|
+
// cursors. It is the expiry of the url already held, sent back unchanged (zero when none).
|
|
303
305
|
export function buildBusinessSyncOutRequest(business, syncInState, attachmentBaseUrlExpiry) {
|
|
304
306
|
return createBusinessSyncOutRequest(typeof business === "bigint"
|
|
305
307
|
? { oneofKind: "businessEntityId", businessEntityId: business }
|
|
@@ -333,9 +335,9 @@ export function cursorsToBlobs(cursors, movedKeys) {
|
|
|
333
335
|
return blobs;
|
|
334
336
|
}
|
|
335
337
|
// The cursors a container holds, from the blobs at the cursor ids of keys (in key order, null where
|
|
336
|
-
// none). A container never read, or filled by a version that kept no cursors, gets zeros
|
|
337
|
-
// everything again. That costs bandwidth
|
|
338
|
-
// greater.
|
|
338
|
+
// none). A container never read, or filled by a version that kept no cursors, gets zeros, so the
|
|
339
|
+
// next read fetches everything again. That costs only bandwidth, since the store keeps a blob only
|
|
340
|
+
// when its eTag is greater.
|
|
339
341
|
export function cursorsFromBlobs(keys, blobs) {
|
|
340
342
|
const cursors = new SyncState(keys);
|
|
341
343
|
for (const [index, key] of keys.entries()) {
|
|
@@ -345,15 +347,15 @@ export function cursorsFromBlobs(keys, blobs) {
|
|
|
345
347
|
}
|
|
346
348
|
return cursors;
|
|
347
349
|
}
|
|
348
|
-
// Raises the cursors to what a page of a business read brought, tombstones included, and
|
|
349
|
-
// keys that moved. Only a read moves them.
|
|
350
|
+
// Raises the cursors to what a page of a business read brought, tombstones included, and returns the
|
|
351
|
+
// keys that moved. Only a read moves them. A push does not.
|
|
350
352
|
export function raiseBusinessCursors(cursors, response) {
|
|
351
353
|
return raiseCursorsFrom(cursors, getBusinessResponseEntities(response));
|
|
352
354
|
}
|
|
353
|
-
// The request that pushes what this customer changed. A storefront may push only messages
|
|
355
|
+
// The request that pushes what this customer changed. A storefront may push only messages. One
|
|
354
356
|
// request is one batch, so an order and the cart it emptied go together and a customer is never left
|
|
355
|
-
//
|
|
356
|
-
//
|
|
357
|
+
// with the order and a still full cart. messageUpdateNumber is how far reads of this container have
|
|
358
|
+
// got with messages - see SyncCache.readMessageUpdateNumber.
|
|
357
359
|
export function buildBusinessSyncInRequest(messageUpdateNumber, messages) {
|
|
358
360
|
return proto_BusinessSyncInRequest.create({
|
|
359
361
|
messageUpdateNumber: messageUpdateNumber,
|
|
@@ -366,10 +368,10 @@ export function getSyncInResponseMessages(response) {
|
|
|
366
368
|
return response.messages;
|
|
367
369
|
}
|
|
368
370
|
// The request for everything changed in the caller's profile since these cursors. It names no
|
|
369
|
-
// business, since a profile sync is not scoped to one and has no sync in. joinBusinessId
|
|
370
|
-
//
|
|
371
|
-
// Constants.ENTITY_ID_NULL
|
|
372
|
-
//
|
|
371
|
+
// business, since a profile sync is not scoped to one and has no sync in. joinBusinessId is the
|
|
372
|
+
// entity id of a business to make the caller a customer of while the server answers.
|
|
373
|
+
// Constants.ENTITY_ID_NULL means no join and is the default, since an ordinary read joins nothing.
|
|
374
|
+
// A join belongs to one call, so nextProfileSyncOutRequest leaves it off later pages.
|
|
373
375
|
export function buildProfileSyncOutRequest(syncInState, joinBusinessId = BigInt(proto_Constants.ENTITY_ID_NULL)) {
|
|
374
376
|
return proto_ProfileSyncOutRequest.create({
|
|
375
377
|
userUpdateNumber: syncInState.getUpdateNumber(proto_Constants.USER_ENTITY_TYPE_ID),
|
|
@@ -382,8 +384,8 @@ export function buildProfileSyncOutRequest(syncInState, joinBusinessId = BigInt(
|
|
|
382
384
|
export function raiseProfileCursors(cursors, response) {
|
|
383
385
|
return raiseCursorsFrom(cursors, getProfileResponseEntities(response));
|
|
384
386
|
}
|
|
385
|
-
// The user a sign up
|
|
386
|
-
// server assigns id, createdAt and updatedAt), so none of EntityHeader
|
|
387
|
+
// The user a sign up sends as CreateUserRequest.user, for the server to create. It has no entity yet
|
|
388
|
+
// (the server assigns id, createdAt and updatedAt), so none of EntityHeader is sent.
|
|
387
389
|
export function buildUser(userProfile) {
|
|
388
390
|
return proto_User.create({
|
|
389
391
|
firstName: userProfile.firstName,
|
|
@@ -393,18 +395,18 @@ export function buildUser(userProfile) {
|
|
|
393
395
|
preferredLanguageCode: stringToProto(userProfile.preferredLanguageCode),
|
|
394
396
|
});
|
|
395
397
|
}
|
|
396
|
-
// The user Service.updateUser
|
|
397
|
-
// the entity id, which the
|
|
398
|
-
// call, so it is not sent.
|
|
398
|
+
// The user that Service.updateUser writes over this customer's profile: the fields of buildUser plus
|
|
399
|
+
// the entity id, which the server matches to the caller. The rest of EntityHeader is not read on
|
|
400
|
+
// that call, so it is not sent.
|
|
399
401
|
export function buildUpdateUser(userProfile) {
|
|
400
402
|
return proto_User.create({
|
|
401
403
|
...buildUser(userProfile),
|
|
402
404
|
entity: proto_Entity.create({ id: userProfile.id }),
|
|
403
405
|
});
|
|
404
406
|
}
|
|
405
|
-
// The two things a join must
|
|
406
|
-
//
|
|
407
|
-
//
|
|
407
|
+
// The two things a response to a join must hold, checked separately so a caller can report which one
|
|
408
|
+
// is missing - see Model.requireJoinedBusiness. Both take the id, since both requests that carry a
|
|
409
|
+
// joinBusinessId need them.
|
|
408
410
|
// The membership itself: a live BusinessLink naming that business. A tombstoned link does not count
|
|
409
411
|
// (see OBJECT MODEL), nor does a link to another business. Its linkToken is the only copy of that
|
|
410
412
|
// credential a profile response carries.
|
|
@@ -424,7 +426,7 @@ export function hasJoinedBusinessLink(joinBusinessId, response) {
|
|
|
424
426
|
export function hasJoinedBusiness(joinBusinessId, response) {
|
|
425
427
|
if (joinBusinessId === BigInt(proto_Constants.ENTITY_ID_NULL))
|
|
426
428
|
return false;
|
|
427
|
-
// A
|
|
429
|
+
// A tombstoned business does not count
|
|
428
430
|
for (const business of response.businesses)
|
|
429
431
|
if (getEntity(business.entity).id === joinBusinessId &&
|
|
430
432
|
!isDeletedEntity(getEntity(business.entity)))
|
|
@@ -432,8 +434,8 @@ export function hasJoinedBusiness(joinBusinessId, response) {
|
|
|
432
434
|
return false;
|
|
433
435
|
}
|
|
434
436
|
// The country of the business a response holds, ISO 3166-1 numeric, or undefined when it holds none.
|
|
435
|
-
// A tombstone has no country, and a bare header carries zero
|
|
436
|
-
// cart's taxes are raw codes that mean something only within a country.
|
|
437
|
+
// A tombstone has no country, and a bare header carries zero. Zero must not be taken for a country,
|
|
438
|
+
// because a cart's taxes are raw codes that mean something only within a country.
|
|
437
439
|
export function getBusinessResponseCountryCode(response) {
|
|
438
440
|
const business = response?.business;
|
|
439
441
|
if (business === undefined || isDeletedEntity(getEntity(business.entity)))
|
|
@@ -441,8 +443,8 @@ export function getBusinessResponseCountryCode(response) {
|
|
|
441
443
|
return business.countryCode;
|
|
442
444
|
}
|
|
443
445
|
// The entity id of the business a response holds, or null when it holds none or only a tombstone.
|
|
444
|
-
// Once a business has been read, later requests name it by this id
|
|
445
|
-
// friendly id is only
|
|
446
|
+
// Once a business has been read, later requests name it by this id, which is stable. The url
|
|
447
|
+
// friendly id is used only to find the business the first time.
|
|
446
448
|
export function getBusinessResponseBusinessEntityId(response) {
|
|
447
449
|
const entity = response?.business?.entity;
|
|
448
450
|
if (entity === undefined || isDeletedEntity(entity))
|
|
@@ -465,9 +467,9 @@ export function isBusinessResponseOnlineShoppingOn(response) {
|
|
|
465
467
|
return false;
|
|
466
468
|
return getGeneralSettingsOf(response)?.onlineShoppingEnabled ?? false;
|
|
467
469
|
}
|
|
468
|
-
// The general settings a response holds, as on the wire, or undefined when none
|
|
469
|
-
// configuration that carries them, unless it is deleted.
|
|
470
|
-
//
|
|
470
|
+
// The general settings a response holds, as on the wire, or undefined when none. They come from the
|
|
471
|
+
// one public configuration that carries them, unless it is deleted. buildBusiness and
|
|
472
|
+
// isBusinessResponseOnlineShoppingOn both read them here, so both use the same settings.
|
|
471
473
|
function getGeneralSettingsOf(response) {
|
|
472
474
|
let settings;
|
|
473
475
|
for (const configuration of response.configurations) {
|
|
@@ -480,7 +482,7 @@ function getGeneralSettingsOf(response) {
|
|
|
480
482
|
return settings;
|
|
481
483
|
}
|
|
482
484
|
// The credentials of one call, as on the wire. An absent token is left off the message, since the
|
|
483
|
-
// protocol
|
|
485
|
+
// protocol distinguishes absent from empty.
|
|
484
486
|
export function buildAuthTokens(authTokens) {
|
|
485
487
|
const result = proto_AuthTokens.create({
|
|
486
488
|
authenticationMethod: authTokens.authenticationMethod,
|
|
@@ -503,10 +505,10 @@ export function authTokensFromProto(authTokens) {
|
|
|
503
505
|
export function buildAuthTokensBytes(authTokens) {
|
|
504
506
|
return proto_AuthTokens.toBinary(authTokens);
|
|
505
507
|
}
|
|
506
|
-
//
|
|
507
|
-
// null because a reissued token is not a later version of the old
|
|
508
|
-
// it. The authentication token is never stored: it belongs to the
|
|
509
|
-
// be staler than the one in memory and would outlive the sign out.
|
|
508
|
+
// The credentials as a container stores them: the two tokens this server issued, and the
|
|
509
|
+
// authentication method. The eTag is null because a reissued token is not a later version of the old
|
|
510
|
+
// one, and greater-wins would refuse it. The authentication token is never stored: it belongs to the
|
|
511
|
+
// identity provider, so a copy would be staler than the one in memory and would outlive the sign out.
|
|
510
512
|
export function authTokensToBlob(authTokens) {
|
|
511
513
|
const stored = proto_AuthTokens.create({
|
|
512
514
|
authenticationMethod: authTokens.authenticationMethod,
|
|
@@ -542,13 +544,13 @@ export function refreshAuthTokens(authTokens, issued) {
|
|
|
542
544
|
result.linkToken = issued.linkToken;
|
|
543
545
|
return result;
|
|
544
546
|
}
|
|
545
|
-
// The request for the page after this response, or null when it was the last. A full page
|
|
546
|
-
// short, so the cursors are raised from what arrived and the call repeats. A full page that
|
|
547
|
-
// cursor means the server broke the ascending order it promises. The loop
|
|
548
|
-
// there.
|
|
547
|
+
// The request for the page after this response, or null when it was the last. A full page may have
|
|
548
|
+
// been cut short, so the cursors are raised from what arrived and the call repeats. A full page that
|
|
549
|
+
// raised no cursor means the server broke the ascending order it promises. The loop has no other
|
|
550
|
+
// bound, so it ends there.
|
|
549
551
|
export function nextBusinessSyncOutRequest(request, response) {
|
|
550
552
|
// A short page means the server sent everything. Counting first keeps the ordinary case to nine
|
|
551
|
-
//
|
|
553
|
+
// field reads.
|
|
552
554
|
if (businessResponseEntityCount(response) < proto_Constants.SYNC_OUT_LIMIT)
|
|
553
555
|
return null;
|
|
554
556
|
const cursors = businessSyncOutCursors(request);
|
|
@@ -577,12 +579,12 @@ export function nextBusinessSyncOutRequest(request, response) {
|
|
|
577
579
|
raise(getItem(price.item).entity);
|
|
578
580
|
if (!haveCursorsRisen)
|
|
579
581
|
return null;
|
|
580
|
-
//
|
|
582
|
+
// Use the expiry of a renewed url if this page carried one, else the expiry already sent
|
|
581
583
|
const renewedExpiry = getBusinessResponseAttachmentBaseUrlExpiry(response);
|
|
582
584
|
return createBusinessSyncOutRequest(request.business, cursors, renewedExpiry === 0n ? request.attachmentBaseUrlExpiry : renewedExpiry);
|
|
583
585
|
}
|
|
584
586
|
// The same for a profile read. A joinBusinessId on the first request is left off the later ones,
|
|
585
|
-
// since the call
|
|
587
|
+
// since the first call makes the join.
|
|
586
588
|
export function nextProfileSyncOutRequest(request, response) {
|
|
587
589
|
if (profileResponseEntityCount(response) < proto_Constants.SYNC_OUT_LIMIT)
|
|
588
590
|
return null;
|
|
@@ -601,8 +603,8 @@ export function nextProfileSyncOutRequest(request, response) {
|
|
|
601
603
|
return null;
|
|
602
604
|
return buildProfileSyncOutRequest(cursors);
|
|
603
605
|
}
|
|
604
|
-
// How many entities a response carried, without encoding any. A paging loop
|
|
605
|
-
// and getBusinessResponseEntities would encode each entity to
|
|
606
|
+
// How many entities a response carried, counted without encoding any. A paging loop needs the count
|
|
607
|
+
// for every page, and getBusinessResponseEntities would encode each entity to produce it.
|
|
606
608
|
function businessResponseEntityCount(response) {
|
|
607
609
|
return ((response.business === undefined ? 0 : 1) +
|
|
608
610
|
response.attachments.length +
|
|
@@ -619,7 +621,7 @@ function profileResponseEntityCount(response) {
|
|
|
619
621
|
response.businessLinks.length +
|
|
620
622
|
response.businesses.length);
|
|
621
623
|
}
|
|
622
|
-
// Raises the cursors the entities of one page advance and
|
|
624
|
+
// Raises the cursors the entities of one page advance and returns the keys that moved, each once
|
|
623
625
|
// however many entities raised it. A tombstone counts like any entity, as in raiseCursor.
|
|
624
626
|
function raiseCursorsFrom(cursors, entities) {
|
|
625
627
|
const movedKeys = new Set();
|
|
@@ -628,8 +630,8 @@ function raiseCursorsFrom(cursors, entities) {
|
|
|
628
630
|
movedKeys.add(SyncKeys.keyOf(entity.entityTypeId));
|
|
629
631
|
return movedKeys;
|
|
630
632
|
}
|
|
631
|
-
// Raises the cursor one entity advances and
|
|
632
|
-
// server would send it again
|
|
633
|
+
// Raises the cursor one entity advances and returns whether it moved. A tombstone counts, because
|
|
634
|
+
// otherwise the server would send it again forever.
|
|
633
635
|
function raiseCursor(cursors, entity) {
|
|
634
636
|
if (entity === undefined)
|
|
635
637
|
return false;
|
|
@@ -690,9 +692,9 @@ export function buildBusiness(response) {
|
|
|
690
692
|
updatableBusiness.settings = generalSettingsFromProto(generalSettings, countryCode);
|
|
691
693
|
// The photos of the store itself
|
|
692
694
|
updatableBusiness.photos = takePhotos(attachmentsByOwner, business.id, attachmentBaseUrl);
|
|
693
|
-
// Prices, by the package each applies to.
|
|
695
|
+
// Prices, by the package each applies to. This storefront uses only the MeraBills Online list. A
|
|
694
696
|
// business prices a package in several lists, and a price from another list is not what it sells at
|
|
695
|
-
// here, so it is skipped. A package has one price in that list
|
|
697
|
+
// here, so it is skipped. A package has one price in that list. If a second arrives, the first is
|
|
696
698
|
// kept.
|
|
697
699
|
const pricesByPackage = new Map();
|
|
698
700
|
for (const priceProto of response.prices) {
|
|
@@ -751,9 +753,9 @@ export function buildBusiness(response) {
|
|
|
751
753
|
UpdatableEntity.setParent(productCategory, business);
|
|
752
754
|
productCategories.push(productCategory);
|
|
753
755
|
}
|
|
754
|
-
//
|
|
755
|
-
// carry others. The catalog must
|
|
756
|
-
// name
|
|
756
|
+
// A storefront sells from the MeraBills Online list only, picked by id since the same sync can
|
|
757
|
+
// carry others. The catalog must hold the list the prices above came from. Otherwise a cart would
|
|
758
|
+
// name a list that does not contain them.
|
|
757
759
|
let priceList = null;
|
|
758
760
|
for (const priceListProto of response.priceLists) {
|
|
759
761
|
if (isDeletedItem(priceListProto.item))
|
|
@@ -767,25 +769,25 @@ export function buildBusiness(response) {
|
|
|
767
769
|
updatableBusiness.catalog = new Catalog(productCategories, products, priceList);
|
|
768
770
|
// The orders of this customer, and their party preferences (the cart and the fulfillment mode to
|
|
769
771
|
// default the next order to). A message of any other payload type is dropped. The business completes
|
|
770
|
-
// an order by deleting its message, and the server sends that message whole, so the orders
|
|
771
|
-
// open and the completed ones
|
|
772
|
-
// an older server that sent only the header) is
|
|
772
|
+
// an order by deleting its message, and the server sends that message whole, so the orders include
|
|
773
|
+
// the open and the completed ones. Order.isDeleted tells them apart. A deleted message without an
|
|
774
|
+
// order (from an older server that sent only the header) is skipped.
|
|
773
775
|
const orders = [];
|
|
774
776
|
for (const message of response.messages) {
|
|
775
777
|
// Party preferences are one message out of possibly several, so they are settled below
|
|
776
778
|
if (message.payload.oneofKind === "salesOrder")
|
|
777
779
|
orders.push(orderFromProto(message, message.payload.salesOrder, countryCode));
|
|
778
780
|
}
|
|
779
|
-
// A customer has one party preferences message, so two
|
|
780
|
-
// container.
|
|
781
|
-
// resent cart is read from. A rule
|
|
782
|
-
//
|
|
781
|
+
// A customer has one party preferences message, so two mean two callers' messages share a
|
|
782
|
+
// container. Use the one partyPreferencesMessageFromMessages picks, which is also the one an amended
|
|
783
|
+
// or resent cart is read from. A separate rule here would show one cart and amend another. The newer
|
|
784
|
+
// message wins even when its cart is empty, and the older one's list is not shown.
|
|
783
785
|
const partyPreferences = partyPreferencesMessageFromMessages(response.messages);
|
|
784
786
|
let cart = null;
|
|
785
787
|
let defaultFulfillmentMode = proto_FulfillmentMode.DELIVERY;
|
|
786
788
|
if (partyPreferences !== null && partyPreferences.payload.oneofKind === "partyPreferences") {
|
|
787
789
|
// An emptied cart is a message whose payload has no shopping list, which is what placing an order
|
|
788
|
-
// leaves.
|
|
790
|
+
// leaves. The cart is null then, the same as for a customer who never had one.
|
|
789
791
|
cart = cartShoppingListFromProto(partyPreferences.payload.partyPreferences, countryCode);
|
|
790
792
|
// Taken as the message has it. DELIVERY comes only from the fallback above, even when the message
|
|
791
793
|
// holds UNSPECIFIED_FULFILLMENT_MODE.
|
|
@@ -821,16 +823,16 @@ export function buildProfile(profileId, response) {
|
|
|
821
823
|
return null;
|
|
822
824
|
return new Profile(profileId, user, businessLinks, businesses);
|
|
823
825
|
}
|
|
824
|
-
// One order on its own, for a caller holding a message rather than a whole response. The
|
|
825
|
-
// the business
|
|
826
|
+
// One order on its own, for a caller holding a message rather than a whole response. The caller
|
|
827
|
+
// passes the country of the business the order was placed with, since a tax travels as a raw code.
|
|
826
828
|
export function buildOrder(message, countryCode) {
|
|
827
829
|
if (message.payload.oneofKind !== "salesOrder")
|
|
828
830
|
return null;
|
|
829
831
|
return orderFromProto(message, message.payload.salesOrder, countryCode);
|
|
830
832
|
}
|
|
831
|
-
// The shopping list a cart message carries, empty when the cart was emptied. Business.cart
|
|
832
|
-
//
|
|
833
|
-
//
|
|
833
|
+
// The shopping list a cart message carries, empty when the cart was emptied. Business.cart is null
|
|
834
|
+
// then (see buildBusiness), but the callers here, createOrder and the visitor cart carried over on
|
|
835
|
+
// sign in, need a list. countryCode is the country of the cart's business, as for buildOrder.
|
|
834
836
|
export function buildShoppingList(message, countryCode) {
|
|
835
837
|
if (message.payload.oneofKind !== "partyPreferences")
|
|
836
838
|
throw new InvalidArgumentException("That message carries no party preferences, so it is not a cart");
|
|
@@ -840,28 +842,29 @@ export function buildShoppingList(message, countryCode) {
|
|
|
840
842
|
// #region What a customer pushes
|
|
841
843
|
//
|
|
842
844
|
// A storefront writes one entity type, a Message carrying the cart or an order placed from it. Two
|
|
843
|
-
// rules hold for both, so a pushed message is storable here as well as on the server. The
|
|
844
|
-
//
|
|
845
|
-
// not be found here again.
|
|
846
|
-
// here has the same eTag as the one a later read brings back.
|
|
845
|
+
// rules hold for both, so a pushed message is storable here as well as on the server. The client
|
|
846
|
+
// chooses the id: a sync in returns an update number and no id, so a message the server named could
|
|
847
|
+
// not be found here again. The client also sets updatedAt and the server keeps it, so the copy
|
|
848
|
+
// stored here has the same eTag as the one a later read brings back.
|
|
847
849
|
// This customer's party preferences as a message to push, carrying the cart and optionally the
|
|
848
850
|
// fulfillment mode. An existing message is amended, since its payload can carry fields a storefront
|
|
849
851
|
// cannot read, and dropping them would overwrite that data. Its id is kept, as the server holds the
|
|
850
852
|
// preferences under it. The derived id is used only when there is nothing to amend.
|
|
851
|
-
// A list with no items is pushed as no list, as Java serializeShoppingList
|
|
852
|
-
// item.
|
|
853
|
-
// fulfillmentMode null
|
|
854
|
-
// the merchant app's data. createOrder sets it to the mode of the order that
|
|
853
|
+
// A list with no items is pushed as no list, as Java serializeShoppingList returns null below one
|
|
854
|
+
// item. An emptied cart and a cart never created look the same on the wire.
|
|
855
|
+
// fulfillmentMode null keeps the existing value, the ordinary case: a storefront treats that field
|
|
856
|
+
// as the merchant app's data and leaves it alone. createOrder sets it to the mode of the order that
|
|
857
|
+
// emptied the cart.
|
|
855
858
|
export function buildPartyPreferencesMessage(messageId, existing, senderId, shoppingList, fulfillmentMode, now) {
|
|
856
859
|
const existingEntity = existing === null ? null : getEntity(existing.entity);
|
|
857
860
|
const preferences = existing !== null && existing.payload.oneofKind === "partyPreferences"
|
|
858
861
|
? existing.payload.partyPreferences
|
|
859
862
|
: proto_PartyPreferencesPayload.create();
|
|
860
|
-
// Later than the copy it replaces,
|
|
861
|
-
// the server is stored at the update number of the copy it amends, so updatedAt decides. A
|
|
862
|
-
// clock behind that copy (Constants.MAX_CLOCK_SKEW
|
|
863
|
-
// the edit and leave the customer looking at a stale cart. One millisecond past the
|
|
864
|
-
// and the server keeps what is sent, so both sides agree.
|
|
863
|
+
// Later than the copy it replaces, even if this client's clock is behind it. An amendment written
|
|
864
|
+
// without the server is stored at the update number of the copy it amends, so updatedAt decides. A
|
|
865
|
+
// client clock behind that copy (clocks do differ, see Constants.MAX_CLOCK_SKEW) would make its own
|
|
866
|
+
// store refuse the edit and leave the customer looking at a stale cart. One millisecond past the
|
|
867
|
+
// copy is enough, and the server keeps what is sent, so both sides agree.
|
|
865
868
|
const updatedAt = existingEntity === null || existingEntity.updatedAt < now ? now : existingEntity.updatedAt + 1n;
|
|
866
869
|
const partyPreferences = proto_PartyPreferencesPayload.create({
|
|
867
870
|
...preferences,
|
|
@@ -877,12 +880,12 @@ export function buildPartyPreferencesMessage(messageId, existing, senderId, shop
|
|
|
877
880
|
payload: { oneofKind: "partyPreferences", partyPreferences: partyPreferences },
|
|
878
881
|
});
|
|
879
882
|
}
|
|
880
|
-
// One order as a message to push. Its shopping list
|
|
881
|
-
// SalesOrderViewModel.saveChanges does before creating an order: the customer has
|
|
882
|
-
// and only a final list
|
|
883
|
-
// ordered. The two totals are all that remains of an order completed by an older merchant app,
|
|
884
|
-
// moved the list out of the order. They are filled from the list being sent, not from the
|
|
885
|
-
// carries, which a caller could set to anything.
|
|
883
|
+
// One order as a message to push. Its shopping list is sent as final regardless of the list's
|
|
884
|
+
// isFinal, as Java SalesOrderViewModel.saveChanges does before creating an order: the customer has
|
|
885
|
+
// finished choosing, and only a final list records a later edit as a change instead of altering what
|
|
886
|
+
// was ordered. The two totals are all that remains of an order completed by an older merchant app,
|
|
887
|
+
// which moved the list out of the order. They are filled from the list being sent, not from the
|
|
888
|
+
// counts it carries, which a caller could set to anything.
|
|
886
889
|
export function buildOrderMessage(orderId, senderId, shoppingList, fulfillmentMode, deliveryAddressId, deliveryAddress, note, creationDate) {
|
|
887
890
|
return proto_Message.create({
|
|
888
891
|
entity: pushedEntity(orderId, creationDate, creationDate),
|
|
@@ -906,30 +909,30 @@ export function buildOrderMessage(orderId, senderId, shoppingList, fulfillmentMo
|
|
|
906
909
|
});
|
|
907
910
|
}
|
|
908
911
|
// The messages of a sync in response as a cache write, tombstones included like any other entity
|
|
909
|
-
// array. This stores a push's own messages under the update numbers the server
|
|
912
|
+
// array. This stores a push's own messages under the update numbers the server assigned, with no
|
|
910
913
|
// separate stamping step, since the response carries full entities.
|
|
911
914
|
export function messagesToCacheWrite(messages) {
|
|
912
915
|
return toCacheWrite(messages.map((message) => toWireEntity(message.entity, proto_Message.toBinary(message))));
|
|
913
916
|
}
|
|
914
|
-
// The Entity of something this client is pushing. The update number goes out as zero
|
|
915
|
-
// replaces a caller's own with the one it assigns
|
|
916
|
-
//
|
|
917
|
-
//
|
|
918
|
-
//
|
|
919
|
-
//
|
|
920
|
-
// changes nothing.
|
|
917
|
+
// The Entity of something this client is pushing. The update number goes out as zero, since the
|
|
918
|
+
// server replaces a caller's own with the one it assigns. A message not pushed yet is stored holding
|
|
919
|
+
// zero, and messageToBlob versions it from the copy it amends. updateInfo is zero too: it would be
|
|
920
|
+
// the id of the store that made the change, and the server knows no store of this client. This
|
|
921
|
+
// client does not need it: a read sends these messages back (the server leaves no one's changes out
|
|
922
|
+
// of a storefront's read), and the eTag stored here is the one the server kept, so storing what the
|
|
923
|
+
// read brings back changes nothing.
|
|
921
924
|
function pushedEntity(id, createdAt, updatedAt) {
|
|
922
925
|
return proto_Entity.create({ id: id, createdAt: createdAt, updatedAt: updatedAt });
|
|
923
926
|
}
|
|
924
927
|
// Whether shoppingList differs from what stored holds, so updateCart can skip a push. Compares raw
|
|
925
|
-
// lists, since a message's updatedAt differs on every call. That
|
|
926
|
-
//
|
|
927
|
-
// The server echoes the items in package id order, and the list's own order is each item's
|
|
928
|
-
// shoppingListOrder, compared with the item. So both lists are put in package id order
|
|
929
|
-
// cart not picked in that order would differ from its echo and be pushed again,
|
|
930
|
-
// updatedAt for every other device.
|
|
931
|
-
//
|
|
932
|
-
// unsent local edit still
|
|
928
|
+
// lists, since a message's updatedAt differs on every call. That works because the server echoes a
|
|
929
|
+
// pushed list back with the items it was sent, so stored is what was last sent.
|
|
930
|
+
// The server echoes the items in package id order, and the list's own order is kept in each item's
|
|
931
|
+
// shoppingListOrder, which is compared with the item. So both lists are put in package id order
|
|
932
|
+
// first. Otherwise a cart not picked in that order would differ from its echo and be pushed again,
|
|
933
|
+
// which re-stamps updatedAt for every other device.
|
|
934
|
+
// Also true when stored was never pushed (updateNumber 0, left by updateCartLocalOnly), so an
|
|
935
|
+
// unsent local edit is still pushed.
|
|
933
936
|
export function hasShoppingListChanged(stored, shoppingList) {
|
|
934
937
|
if (stored === null)
|
|
935
938
|
return pushedShoppingList(shoppingList, false) !== undefined;
|
|
@@ -937,7 +940,7 @@ export function hasShoppingListChanged(stored, shoppingList) {
|
|
|
937
940
|
return true;
|
|
938
941
|
return !proto_ShoppingList.equals(inPackageIdOrder(getCartShoppingList(stored)), inPackageIdOrder(pushedShoppingList(shoppingList, false)));
|
|
939
942
|
}
|
|
940
|
-
// The same list with its items by package id, the order
|
|
943
|
+
// The same list with its items sorted by package id, the order the server sends them in
|
|
941
944
|
function inPackageIdOrder(list) {
|
|
942
945
|
if (list === undefined)
|
|
943
946
|
return undefined;
|
|
@@ -954,7 +957,7 @@ function getCartShoppingList(message) {
|
|
|
954
957
|
? message.payload.partyPreferences.shoppingList
|
|
955
958
|
: undefined;
|
|
956
959
|
}
|
|
957
|
-
// A shopping list
|
|
960
|
+
// A shopping list to push, or absent when it holds nothing - see buildPartyPreferencesMessage.
|
|
958
961
|
// Emptiness is judged by the items sent, not the counts, for the reason given at shoppingListToProto.
|
|
959
962
|
function pushedShoppingList(shoppingList, isFinal) {
|
|
960
963
|
if (shoppingList === null)
|
|
@@ -963,9 +966,9 @@ function pushedShoppingList(shoppingList, isFinal) {
|
|
|
963
966
|
return undefined;
|
|
964
967
|
return shoppingListToProto(shoppingList, isFinal);
|
|
965
968
|
}
|
|
966
|
-
// The counts
|
|
967
|
-
// protocol sends a count even where it omits the items, but a push always sends
|
|
968
|
-
//
|
|
969
|
+
// The counts are the lengths of the item arrays, not the list's own counts. A read takes them from
|
|
970
|
+
// the wire because the protocol sends a count even where it omits the items, but a push always sends
|
|
971
|
+
// the items. Counting the arrays means a pushed list cannot disagree with its contents.
|
|
969
972
|
function shoppingListToProto(shoppingList, isFinal) {
|
|
970
973
|
return proto_ShoppingList.create({
|
|
971
974
|
isFinal: isFinal,
|
|
@@ -981,8 +984,8 @@ function shoppingListToProto(shoppingList, isFinal) {
|
|
|
981
984
|
deletedItems: shoppingList.deletedItems.map(shoppingListItemToProto),
|
|
982
985
|
});
|
|
983
986
|
}
|
|
984
|
-
// The mirror of shoppingListItemFromProto. Unscaled model values
|
|
985
|
-
// is sent: amount, totalTax and finalPrice are computed from these fields on both sides.
|
|
987
|
+
// The mirror of shoppingListItemFromProto. Unscaled model values are scaled again, and nothing
|
|
988
|
+
// derived is sent: amount, totalTax and finalPrice are computed from these fields on both sides.
|
|
986
989
|
function shoppingListItemToProto(item) {
|
|
987
990
|
return proto_ShoppingListItem.create({
|
|
988
991
|
packageId: item.packageId,
|
|
@@ -1005,11 +1008,11 @@ function taxToProto(tax) {
|
|
|
1005
1008
|
// #endregion
|
|
1006
1009
|
// #region Entity and item bookkeeping
|
|
1007
1010
|
// One pass over a response's entities, each becoming a blob at its own id, so one read of a container
|
|
1008
|
-
// is enough to sort it by entity type. Every blob is versioned by entityETag, tombstones too.
|
|
1009
|
-
//
|
|
1010
|
-
// deleted and replaces it. An older copy sent again is lesser and
|
|
1011
|
-
// same id is greater and kept. The arrays are mutable so that
|
|
1012
|
-
// the attachment base url.
|
|
1011
|
+
// is enough to sort it by entity type. Every blob is versioned by entityETag, tombstones too. The
|
|
1012
|
+
// server gives a delete an update number like any other write, so a tombstone has a greater update
|
|
1013
|
+
// number than any copy of what it deleted and replaces it. An older copy sent again is lesser and
|
|
1014
|
+
// refused, and a later write of the same id is greater and kept. The arrays are mutable so that
|
|
1015
|
+
// businessResponseToCacheWrite can add the attachment base url.
|
|
1013
1016
|
function toCacheWrite(entities) {
|
|
1014
1017
|
const blobs = [];
|
|
1015
1018
|
for (const entity of entities)
|
|
@@ -1021,11 +1024,12 @@ function toCacheWrite(entities) {
|
|
|
1021
1024
|
return { blobs: blobs, deletedIds: [] };
|
|
1022
1025
|
}
|
|
1023
1026
|
// What a store versions an entity's blob by: the update number, with updatedAt below it, in one
|
|
1024
|
-
// bigint so the greater
|
|
1025
|
-
// deletes included, so it rises with every change
|
|
1026
|
-
// different versions. updatedAt decides between copies of one update
|
|
1027
|
-
// wrote itself, at the update number of the one it amended, shares
|
|
1028
|
-
// bits
|
|
1027
|
+
// bigint so that the store's greater-wins rule orders versions correctly. The server assigns an
|
|
1028
|
+
// update number to every write, deletes included, so it rises with every change regardless of any
|
|
1029
|
+
// clock, and decides between different versions. updatedAt decides between copies of one update
|
|
1030
|
+
// number. Only a copy this client wrote itself, at the update number of the one it amended, shares
|
|
1031
|
+
// one. The value is wider than 64 bits. A bigint holds it exactly, and a store must compare it as a
|
|
1032
|
+
// bigint.
|
|
1029
1033
|
export function entityETag(updateNumber, updatedAt) {
|
|
1030
1034
|
if (updateNumber < 0n || updatedAt < 0n)
|
|
1031
1035
|
throw new InvalidArgumentException("An update number and an updatedAt are never negative");
|
|
@@ -1033,10 +1037,10 @@ export function entityETag(updateNumber, updatedAt) {
|
|
|
1033
1037
|
}
|
|
1034
1038
|
// A message this client wrote has no update number until a server assigns one, and has none in the
|
|
1035
1039
|
// store while unpushed (see pushedEntity). It is versioned at the update number of the copy it
|
|
1036
|
-
// amends, which heldETag carries, so
|
|
1037
|
-
// server
|
|
1038
|
-
// greater update number does. A message that amends nothing is at zero. A message
|
|
1039
|
-
// number
|
|
1040
|
+
// amends, which heldETag carries, so its updatedAt makes it the later copy of that version. If the
|
|
1041
|
+
// server sends the amended copy again, that copy does not replace it, but a later server version
|
|
1042
|
+
// with a greater update number does. A message that amends nothing is at zero. A message that has an
|
|
1043
|
+
// update number is versioned by it, as any entity is.
|
|
1040
1044
|
function messageToBlob(message, heldETag) {
|
|
1041
1045
|
const entity = getEntity(message.entity);
|
|
1042
1046
|
const updateNumber = entity.updateNumber !== 0n
|
|
@@ -1050,9 +1054,9 @@ function messageToBlob(message, heldETag) {
|
|
|
1050
1054
|
bytes: proto_Message.toBinary(message),
|
|
1051
1055
|
};
|
|
1052
1056
|
}
|
|
1053
|
-
// The messages a container holds that are deleted (isDeleted) or live.
|
|
1054
|
-
//
|
|
1055
|
-
// and is never a customer's cart. Each as stored.
|
|
1057
|
+
// The messages a container holds that are deleted (isDeleted true) or live (false). A message is
|
|
1058
|
+
// deleted according to its update number alone, regardless of its payload. A deleted message is a
|
|
1059
|
+
// completed order when it carries one, and is never a customer's cart. Each as stored.
|
|
1056
1060
|
function messagesFromBlobs(blobs, isDeleted = false) {
|
|
1057
1061
|
const result = [];
|
|
1058
1062
|
for (const blob of blobs) {
|
|
@@ -1111,12 +1115,12 @@ function getItemId(item) {
|
|
|
1111
1115
|
}
|
|
1112
1116
|
// #endregion
|
|
1113
1117
|
// #region Values
|
|
1114
|
-
// An absent optional string is null, because Java
|
|
1118
|
+
// An absent optional string is null, because Java distinguishes absent from empty
|
|
1115
1119
|
function stringFromProto(value) {
|
|
1116
1120
|
return value === undefined ? null : value;
|
|
1117
1121
|
}
|
|
1118
|
-
// The
|
|
1119
|
-
// bring the field back as the other.
|
|
1122
|
+
// The reverse of stringFromProto. An empty string goes out absent too: the two mean the same, and
|
|
1123
|
+
// sending one could bring the field back as the other.
|
|
1120
1124
|
function stringToProto(value) {
|
|
1121
1125
|
return value === null || value.length === 0 ? undefined : value;
|
|
1122
1126
|
}
|
|
@@ -1126,9 +1130,9 @@ function attachmentBaseUrlFromProto(urlAndToken) {
|
|
|
1126
1130
|
function addressFromProto(address) {
|
|
1127
1131
|
return new Address(address.id, address.addressType, stringFromProto(address.unitNumber), stringFromProto(address.community), stringFromProto(address.line2), stringFromProto(address.city), stringFromProto(address.state), stringFromProto(address.pinCode), address.countryCode);
|
|
1128
1132
|
}
|
|
1129
|
-
// The
|
|
1130
|
-
//
|
|
1131
|
-
// address is in none.
|
|
1133
|
+
// The reverse of addressFromProto, for a delivery address sent directly instead of by id - see
|
|
1134
|
+
// buildOrderMessage. id is copied from Address, though it means nothing outside the list the address
|
|
1135
|
+
// was read from, and this address is in none.
|
|
1132
1136
|
function addressToProto(address) {
|
|
1133
1137
|
return proto_Address.create({
|
|
1134
1138
|
id: address.id,
|
|
@@ -1142,7 +1146,7 @@ function addressToProto(address) {
|
|
|
1142
1146
|
countryCode: address.countryCode,
|
|
1143
1147
|
});
|
|
1144
1148
|
}
|
|
1145
|
-
// The
|
|
1149
|
+
// The reverse of addressListFromProto, for the addresses of a user sent to the server.
|
|
1146
1150
|
function addressListToProto(addressList) {
|
|
1147
1151
|
return proto_AddressList.create({
|
|
1148
1152
|
defaultAddressId: addressList.defaultAddressId,
|
|
@@ -1154,8 +1158,8 @@ function addressListFromProto(addressList) {
|
|
|
1154
1158
|
return AddressList.EMPTY;
|
|
1155
1159
|
return new AddressList(addressList.defaultAddressId, Object.freeze(addressList.addresses.map(addressFromProto)));
|
|
1156
1160
|
}
|
|
1157
|
-
// The wire carries a tax's raw code, meaningful only within a country, so every caller
|
|
1158
|
-
// the business the tax is on.
|
|
1161
|
+
// The wire carries a tax's raw code, meaningful only within a country, so every caller passes the
|
|
1162
|
+
// country of the business the tax is on.
|
|
1159
1163
|
function taxFromProto(tax, countryCode) {
|
|
1160
1164
|
return Tax.fromScaledValues(countryCode, tax.taxCode, tax.rate);
|
|
1161
1165
|
}
|
|
@@ -1263,8 +1267,8 @@ function orderFromProto(message, salesOrder, countryCode) {
|
|
|
1263
1267
|
return Order.fromScaledValues(toEntityHeader(entity), message.senderId, message.status, stringFromProto(message.senderName), stringFromProto(message.senderPhone), salesOrder.status, salesOrder.fulfillmentMode, shoppingListFromProto(salesOrder.shoppingList, countryCode), salesOrder.shoppingListTotal, salesOrder.shoppingListCount, salesOrder.deliveryAddressId, salesOrder.deliveryAddress === undefined ? null : addressFromProto(salesOrder.deliveryAddress), salesOrder.creationDate, stringFromProto(salesOrder.note), salesOrder.paymentReceived);
|
|
1264
1268
|
}
|
|
1265
1269
|
// The one part of a cart message a customer is shown. Null when the cart holds nothing, so a cart
|
|
1266
|
-
// emptied by placing an order and one never created give
|
|
1267
|
-
// the server
|
|
1270
|
+
// emptied by placing an order and one never created both give null. The rest of the message is set by
|
|
1271
|
+
// the server or read elsewhere: who sent it, how far the business has read it, and the fulfillment
|
|
1268
1272
|
// mode, which buildBusiness reads as defaultFulfillmentMode.
|
|
1269
1273
|
function cartShoppingListFromProto(partyPreferences, countryCode) {
|
|
1270
1274
|
const shoppingList = partyPreferences.shoppingList;
|
|
@@ -1283,6 +1287,6 @@ function takePhotos(attachmentsByOwner, owningEntityId, attachmentBaseUrl) {
|
|
|
1283
1287
|
// #endregion
|
|
1284
1288
|
// The width of the updatedAt an eTag carries below its update number. An int64 is never wider.
|
|
1285
1289
|
const UPDATED_AT_BITS = 64n;
|
|
1286
|
-
//
|
|
1290
|
+
// The bytes of a blob that exists only for its eTag
|
|
1287
1291
|
const NO_BYTES = new Uint8Array(0);
|
|
1288
1292
|
const NO_PHOTOS = Object.freeze([]);
|