@peabody-soft/mbs-model-ts 1.7.1 → 1.9.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 (40) hide show
  1. package/dist/Business.js +2 -1
  2. package/dist/Catalog.d.ts +19 -7
  3. package/dist/Catalog.js +119 -39
  4. package/dist/Constants.d.ts +1 -0
  5. package/dist/Constants.js +6 -3
  6. package/dist/Entity.d.ts +1 -0
  7. package/dist/Entity.js +4 -0
  8. package/dist/Enums.d.ts +1 -0
  9. package/dist/Enums.js +1 -0
  10. package/dist/GeneralSettings.d.ts +5 -4
  11. package/dist/GeneralSettings.js +9 -8
  12. package/dist/Item.js +2 -1
  13. package/dist/Package.d.ts +6 -2
  14. package/dist/Package.js +11 -5
  15. package/dist/Price.d.ts +5 -2
  16. package/dist/Price.js +18 -3
  17. package/dist/PriceList.d.ts +13 -0
  18. package/dist/PriceList.js +50 -2
  19. package/dist/Product.d.ts +6 -2
  20. package/dist/Product.js +8 -5
  21. package/dist/ProtoMap.js +60 -68
  22. package/dist/ShoppingList.js +4 -3
  23. package/dist/generated/BusinessSyncOutResponse.d.ts +4 -1
  24. package/dist/generated/Constants.d.ts +0 -4
  25. package/dist/generated/Constants.js +0 -4
  26. package/dist/generated/GeneralSettings.d.ts +5 -4
  27. package/dist/generated/GeneralSettings.js +8 -6
  28. package/dist/generated/Item.d.ts +4 -3
  29. package/dist/generated/Message.d.ts +5 -0
  30. package/dist/generated/OnlineStoreTheme.d.ts +33 -0
  31. package/dist/generated/OnlineStoreTheme.js +38 -0
  32. package/dist/generated/Price.d.ts +9 -0
  33. package/dist/generated/Price.js +9 -1
  34. package/dist/generated/PriceList.d.ts +13 -2
  35. package/dist/generated/ProfileSyncOutRequest.d.ts +16 -15
  36. package/dist/generated/ProfileSyncOutResponse.d.ts +4 -6
  37. package/dist/generated/SalesOrderPayload.d.ts +6 -3
  38. package/dist/index.d.ts +4 -0
  39. package/dist/index.js +2 -2
  40. package/package.json +2 -2
package/dist/Business.js CHANGED
@@ -57,7 +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 the cart is ordered. The cart message itself stays, with no list. Only the list
60
+ // null again once the cart is ordered. The cart message itself stays, with no list, or with a list
61
+ // that has no items (the merchant app stores one), which is null here too. Only the list
61
62
  // is exposed here. The fulfillment mode is chosen when an order is placed and is stored on the
62
63
  // Order (see defaultFulfillmentMode).
63
64
  get cart() {
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
- private readonly _priceList;
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 _categories;
24
- private readonly _products;
25
- private readonly _categoryMap;
26
- private readonly _productMap;
27
- private readonly _packageMap;
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
  }
package/dist/Catalog.js CHANGED
@@ -1,51 +1,70 @@
1
1
  import { Constants as proto_Constants } from "./generated/Constants";
2
- import { EntityHeader } from "./Entity";
2
+ import { DEFAULT_SALE_PRICE_LIST_ID } from "./Constants";
3
+ import { EntityHeader, UpdatableEntity } from "./Entity";
3
4
  import ProductCategory from "./ProductCategory";
4
5
  // The catalog of a business: its categories and the products in them. Not an entity and not on the
5
- // wire. The sync layer rebuilds it from the flat item entities of a sync response.
6
+ // wire. The sync layer builds it from the flat item entities of a sync response, as the merchant
7
+ // app's Catalog is built.
8
+ //
9
+ // The constructor links what it is given: a package to its product, a price to its price list and a
10
+ // price list to its parent. It does not depend on any price list being present. Packages are priced
11
+ // from currentSalePriceListId, so setting that id decides which packages and products are buyable.
6
12
  class Catalog {
7
- constructor(categories, products, _priceList) {
8
- this._priceList = _priceList;
13
+ constructor(categories, products, packages, priceLists, prices) {
14
+ // Every package that has a product, by that product's id, whether it can be bought or not
15
+ this._candidatePackages = new Map();
16
+ this._priceListMap = new Map();
17
+ this._currentSalePriceListId = DEFAULT_SALE_PRICE_LIST_ID;
18
+ // What a customer can reach, rebuilt by applySalePriceList
19
+ this._categories = Object.freeze([]);
20
+ this._products = Object.freeze([]);
9
21
  this._categoryMap = new Map();
10
22
  this._productMap = new Map();
11
23
  this._packageMap = new Map();
24
+ this._allCategories = categories;
25
+ this._allProducts = products;
12
26
  // A dummy category for products whose parentItemId is Constants.ENTITY_ID_NULL. Its id is also
13
27
  // ENTITY_ID_NULL, so getCategory(product.productCategoryId) resolves it like a real category.
14
- // It is left out below like any other category when it has no products.
15
- const uncategorized = new ProductCategory(new EntityHeader(BigInt(proto_Constants.ENTITY_ID_NULL), 0n, 0n, 0n, 0n), "", null, BigInt(proto_Constants.ENTITY_ID_NULL), false);
16
- // Only a buyable product is part of a catalog: enabled, not sold only offline, with at least one
17
- // buyable package. Any other is left out of products, getProduct and its categories, and its
18
- // packages are unreachable by id. Enforced here so no builder can expose a product that cannot
19
- // be bought.
20
- const buyableProducts = [];
21
- const productsByCategory = new Map();
22
- for (const product of products) {
23
- if (!product.isBuyable)
28
+ // It is left out like any other category when it has no buyable products.
29
+ this._uncategorized = new ProductCategory(new EntityHeader(BigInt(proto_Constants.ENTITY_ID_NULL), 0n, 0n, 0n, 0n), "", null, BigInt(proto_Constants.ENTITY_ID_NULL), false);
30
+ // A package with no product is dropped
31
+ const productsById = new Map();
32
+ for (const product of products)
33
+ productsById.set(product.id, product);
34
+ for (const pkg of packages) {
35
+ const product = productsById.get(pkg.productId);
36
+ if (product === undefined)
24
37
  continue;
25
- buyableProducts.push(product);
26
- this._productMap.set(product.id, product);
27
- const productsOfCategory = productsByCategory.get(product.productCategoryId);
28
- if (productsOfCategory === undefined)
29
- productsByCategory.set(product.productCategoryId, [product]);
38
+ UpdatableEntity.setParent(pkg, product);
39
+ const productPackages = this._candidatePackages.get(product.id);
40
+ if (productPackages === undefined)
41
+ this._candidatePackages.set(product.id, [pkg]);
30
42
  else
31
- productsOfCategory.push(product);
32
- for (const pkg of product.packages)
33
- this._packageMap.set(pkg.id, pkg);
43
+ productPackages.push(pkg);
34
44
  }
35
- this._products = Object.freeze(buyableProducts);
36
- // A category with no buyable products is left out instead of being kept empty. This includes
37
- // the dummy category.
38
- const nonEmptyCategories = [];
39
- for (const category of [...categories, uncategorized]) {
40
- const categoryProducts = productsByCategory.get(category.id);
41
- if (categoryProducts === undefined)
45
+ // The first of two price lists with one id is kept. A price with no price list is dropped, and a
46
+ // list holds one price per package, the first to arrive.
47
+ for (const priceList of priceLists)
48
+ if (!this._priceListMap.has(priceList.id))
49
+ this._priceListMap.set(priceList.id, priceList);
50
+ const pricesByList = new Map();
51
+ for (const price of prices) {
52
+ if (!this._priceListMap.has(price.priceListId))
42
53
  continue;
43
- category.products = Object.freeze(categoryProducts);
44
- nonEmptyCategories.push(category);
54
+ const listPrices = pricesByList.get(price.priceListId);
55
+ if (listPrices === undefined)
56
+ pricesByList.set(price.priceListId, new Map([[price.packageId, price]]));
57
+ else if (!listPrices.has(price.packageId))
58
+ listPrices.set(price.packageId, price);
45
59
  }
46
- this._categories = Object.freeze(nonEmptyCategories);
47
- for (const category of this._categories)
48
- this._categoryMap.set(category.id, category);
60
+ // A parent that was not read, or a list that names itself, leaves the list without a parent
61
+ for (const priceList of this._priceListMap.values()) {
62
+ const updatable = priceList;
63
+ updatable.prices = pricesByList.get(priceList.id) ?? new Map();
64
+ const parent = this._priceListMap.get(priceList.parentPriceListId) ?? null;
65
+ updatable.parentPriceList = parent === priceList ? null : parent;
66
+ }
67
+ this.applySalePriceList();
49
68
  }
50
69
  // In no particular order - a UI orders these for display
51
70
  get categories() {
@@ -55,11 +74,22 @@ class Catalog {
55
74
  get products() {
56
75
  return this._products;
57
76
  }
58
- // The one price list a storefront sells from (MeraBills Online). Null until it arrives. Every
59
- // price in this catalog belongs to it, and prices in other lists are left out. A shopping list
77
+ // The id of the price list packages are priced from, DEFAULT_SALE_PRICE_LIST_ID until the sync
78
+ // layer sets it. Setting it prices every package from that list (see PriceList.getPrice), and keeps
79
+ // the packages, products and categories that can then be bought. An id that names no list prices
80
+ // nothing, so the catalog holds nothing.
81
+ get currentSalePriceListId() {
82
+ return this._currentSalePriceListId;
83
+ }
84
+ set currentSalePriceListId(value) {
85
+ this._currentSalePriceListId = value;
86
+ this.applySalePriceList();
87
+ }
88
+ // The price list packages are priced from (MeraBills Online for a storefront). Null when the
89
+ // catalog has no list with the current id, as before the catalog has arrived. A shopping list
60
90
  // built from this catalog carries its id, or MERABILLS_ONLINE_PRICE_LIST_ID while it is null.
61
91
  get priceList() {
62
- return this._priceList;
92
+ return this._priceListMap.get(this._currentSalePriceListId) ?? null;
63
93
  }
64
94
  get productCount() {
65
95
  return this._products.length;
@@ -75,7 +105,8 @@ class Catalog {
75
105
  return this._productMap.get(productId) ?? null;
76
106
  }
77
107
  // Null for the four reserved package ids a shopping list uses for its charge and summary lines
78
- // (see Ids.isReservedPackageId), and for a package whose product was deleted or is not buyable
108
+ // (see Ids.isReservedPackageId), and for a package that is not buyable or whose product is not
109
+ // buyable
79
110
  getPackage(packageId) {
80
111
  return this._packageMap.get(packageId) ?? null;
81
112
  }
@@ -111,7 +142,56 @@ class Catalog {
111
142
  return -1;
112
143
  return left.offerPrice - right.offerPrice;
113
144
  }
145
+ // Prices the packages from the current sale price list, then rebuilds what a customer can reach.
146
+ // Only a buyable product is part of a catalog: enabled, not sold only offline, with at least one
147
+ // buyable package. Any other is left out of products, getProduct and its categories, and its
148
+ // packages are unreachable by id. A category with no buyable products is left out too, the dummy
149
+ // one included.
150
+ applySalePriceList() {
151
+ const salePriceList = this.priceList;
152
+ const buyableProducts = [];
153
+ const productMap = new Map();
154
+ const packageMap = new Map();
155
+ const productsByCategory = new Map();
156
+ for (const product of this._allProducts) {
157
+ const candidates = this._candidatePackages.get(product.id) ?? [];
158
+ for (const pkg of candidates) {
159
+ const price = salePriceList === null ? null : salePriceList.getPrice(pkg.id);
160
+ pkg.price = price;
161
+ if (price !== null)
162
+ UpdatableEntity.setParent(price, pkg);
163
+ }
164
+ // The product keeps only the buyable ones, and is judged by them
165
+ product.packages = candidates;
166
+ if (!product.isBuyable)
167
+ continue;
168
+ buyableProducts.push(product);
169
+ productMap.set(product.id, product);
170
+ const productsOfCategory = productsByCategory.get(product.productCategoryId);
171
+ if (productsOfCategory === undefined)
172
+ productsByCategory.set(product.productCategoryId, [product]);
173
+ else
174
+ productsOfCategory.push(product);
175
+ for (const pkg of product.packages)
176
+ packageMap.set(pkg.id, pkg);
177
+ }
178
+ const nonEmptyCategories = [];
179
+ const categoryMap = new Map();
180
+ for (const category of [...this._allCategories, this._uncategorized]) {
181
+ const categoryProducts = productsByCategory.get(category.id);
182
+ category.products = Object.freeze(categoryProducts ?? []);
183
+ if (categoryProducts === undefined)
184
+ continue;
185
+ nonEmptyCategories.push(category);
186
+ categoryMap.set(category.id, category);
187
+ }
188
+ this._products = Object.freeze(buyableProducts);
189
+ this._categories = Object.freeze(nonEmptyCategories);
190
+ this._productMap = productMap;
191
+ this._packageMap = packageMap;
192
+ this._categoryMap = categoryMap;
193
+ }
114
194
  }
115
195
  // A business whose catalog has not arrived yet
116
- Catalog.EMPTY = new Catalog(Object.freeze([]), Object.freeze([]), null);
196
+ Catalog.EMPTY = new Catalog([], [], [], [], []);
117
197
  export default Catalog;
@@ -1,5 +1,6 @@
1
1
  export { Constants } from "./generated/Constants";
2
2
  export declare function buildPriceListId(priceList: number): bigint;
3
+ export declare const DEFAULT_SALE_PRICE_LIST_ID: bigint;
3
4
  export declare const MERABILLS_ONLINE_PRICE_LIST_ID: bigint;
4
5
  export declare const MAX_SCALED_AMOUNT: bigint;
5
6
  export declare function amountFromScaledValue(scaledAmount: bigint): number;
package/dist/Constants.js CHANGED
@@ -11,9 +11,12 @@ export { Constants } from "./generated/Constants";
11
11
  export function buildPriceListId(priceList) {
12
12
  return Ids.buildId(proto_Constants.PRICE_LIST_ENTITY_TYPE_ID, BigInt(priceList));
13
13
  }
14
- // The only price list this library reads. A business prices a package in several lists (over the
15
- // counter, on a marketplace), and only this one is the storefront's. Prices in other lists are
16
- // skipped, see ProtoMap.buildBusiness.
14
+ // The list a catalog sells from until another is set, as in the merchant app's Catalog
15
+ export const DEFAULT_SALE_PRICE_LIST_ID = buildPriceListId(proto_Constants.DEFAULT_SALE_PRICE_LIST);
16
+ // The list a storefront sells from. A business prices a package in several lists (over the counter,
17
+ // on a marketplace), and a response can carry any of its public ones. ProtoMap.buildBusiness sets
18
+ // the catalog to sell from this one, which can take the amounts of a package from its parent (see
19
+ // PriceList.getPrice).
17
20
  export const MERABILLS_ONLINE_PRICE_LIST_ID = buildPriceListId(proto_Constants.MERABILLS_ONLINE_PRICE_LIST);
18
21
  // The largest amount the protocol can carry (Java Transaction.MAX_AMOUNT). Kept here because it does
19
22
  // not fit a protobuf enum value. It is 2^44 - 1, well below 2^53, so an amount is safe as a number.
package/dist/Entity.d.ts CHANGED
@@ -10,6 +10,7 @@ export default abstract class Entity {
10
10
  get isDeleted(): boolean;
11
11
  get parent(): Entity | null;
12
12
  protected set parent(value: Entity | null);
13
+ protected get header(): EntityHeader;
13
14
  private static readonly UPDATE_TYPE_MASK;
14
15
  private readonly _header;
15
16
  private _parent;
package/dist/Entity.js CHANGED
@@ -49,6 +49,10 @@ class Entity {
49
49
  set parent(value) {
50
50
  this._parent = value;
51
51
  }
52
+ // For a subclass that builds a variant of itself, which has the same id and update number
53
+ get header() {
54
+ return this._header;
55
+ }
52
56
  }
53
57
  Entity.UPDATE_TYPE_MASK = BigInt(proto_Constants.UPDATE_TYPE_MASK);
54
58
  export default Entity;
package/dist/Enums.d.ts CHANGED
@@ -5,6 +5,7 @@ export { BusinessIdentifierType } from "./generated/BusinessIdentifierType";
5
5
  export { DimensionUnit } from "./generated/DimensionUnit";
6
6
  export { FulfillmentMode } from "./generated/FulfillmentMode";
7
7
  export { MessageType } from "./generated/MessageType";
8
+ export { OnlineStoreTheme } from "./generated/OnlineStoreTheme";
8
9
  export { OrderStatus } from "./generated/OrderStatus";
9
10
  export { PackageIdentifierType } from "./generated/PackageIdentifierType";
10
11
  export { PaymentMethod } from "./generated/PaymentMethod";
package/dist/Enums.js CHANGED
@@ -8,6 +8,7 @@ export { BusinessIdentifierType } from "./generated/BusinessIdentifierType";
8
8
  export { DimensionUnit } from "./generated/DimensionUnit";
9
9
  export { FulfillmentMode } from "./generated/FulfillmentMode";
10
10
  export { MessageType } from "./generated/MessageType";
11
+ export { OnlineStoreTheme } from "./generated/OnlineStoreTheme";
11
12
  export { OrderStatus } from "./generated/OrderStatus";
12
13
  export { PackageIdentifierType } from "./generated/PackageIdentifierType";
13
14
  export { PaymentMethod } from "./generated/PaymentMethod";
@@ -1,4 +1,5 @@
1
1
  import { FulfillmentMode as proto_FulfillmentMode } from "./generated/FulfillmentMode";
2
+ import { OnlineStoreTheme as proto_OnlineStoreTheme } from "./generated/OnlineStoreTheme";
2
3
  import { AddressList } from "./Address";
3
4
  import BusinessIdentifier from "./BusinessIdentifier";
4
5
  import LatLong from "./LatLong";
@@ -22,10 +23,10 @@ export default class GeneralSettings {
22
23
  private readonly _primaryStoreLocation;
23
24
  private readonly _googleBusinessCategoryId;
24
25
  private readonly _googleBusinessCategoryName;
25
- private readonly _onlineStoreThemeId;
26
+ private readonly _onlineStoreTheme;
26
27
  private readonly _onlineShoppingEnabled;
27
- static fromScaledValues(paymentMethodDetails: ReadonlyArray<PaymentMethodDetail>, customerServicePhone: string | null, isWhatsAppSupported: boolean, isSmsSupported: boolean, businessAddresses: AddressList, businessIdentifiers: ReadonlyArray<BusinessIdentifier>, fulfillmentModes: ReadonlyArray<proto_FulfillmentMode>, scaledDeliveryCharge: bigint, deliveryChargeTaxes: ReadonlyArray<Tax>, scaledFreeDeliveryTotal: bigint, deliveryRadiusKilometres: number, storeTagline: string | null, workingDays: WorkingDays, primaryStoreLocation: LatLong | null, googleBusinessCategoryId: string | null, googleBusinessCategoryName: string | null, onlineStoreThemeId: string | null, onlineShoppingEnabled: boolean): GeneralSettings;
28
- constructor(_paymentMethodDetails: ReadonlyArray<PaymentMethodDetail>, _customerServicePhone: string | null, _isWhatsAppSupported: boolean, _isSmsSupported: boolean, _businessAddresses: AddressList, _businessIdentifiers: ReadonlyArray<BusinessIdentifier>, _fulfillmentModes: ReadonlyArray<proto_FulfillmentMode>, _deliveryCharge: number, _deliveryChargeTaxes: ReadonlyArray<Tax>, _freeDeliveryTotal: number, _deliveryRadiusKilometres: number, _storeTagline: string | null, _workingDays: WorkingDays, _primaryStoreLocation: LatLong | null, _googleBusinessCategoryId: string | null, _googleBusinessCategoryName: string | null, _onlineStoreThemeId: string | null, _onlineShoppingEnabled: boolean);
28
+ static fromScaledValues(paymentMethodDetails: ReadonlyArray<PaymentMethodDetail>, customerServicePhone: string | null, isWhatsAppSupported: boolean, isSmsSupported: boolean, businessAddresses: AddressList, businessIdentifiers: ReadonlyArray<BusinessIdentifier>, fulfillmentModes: ReadonlyArray<proto_FulfillmentMode>, scaledDeliveryCharge: bigint, deliveryChargeTaxes: ReadonlyArray<Tax>, scaledFreeDeliveryTotal: bigint, deliveryRadiusKilometres: number, storeTagline: string | null, workingDays: WorkingDays, primaryStoreLocation: LatLong | null, googleBusinessCategoryId: string | null, googleBusinessCategoryName: string | null, onlineStoreTheme: proto_OnlineStoreTheme | null, onlineShoppingEnabled: boolean): GeneralSettings;
29
+ constructor(_paymentMethodDetails: ReadonlyArray<PaymentMethodDetail>, _customerServicePhone: string | null, _isWhatsAppSupported: boolean, _isSmsSupported: boolean, _businessAddresses: AddressList, _businessIdentifiers: ReadonlyArray<BusinessIdentifier>, _fulfillmentModes: ReadonlyArray<proto_FulfillmentMode>, _deliveryCharge: number, _deliveryChargeTaxes: ReadonlyArray<Tax>, _freeDeliveryTotal: number, _deliveryRadiusKilometres: number, _storeTagline: string | null, _workingDays: WorkingDays, _primaryStoreLocation: LatLong | null, _googleBusinessCategoryId: string | null, _googleBusinessCategoryName: string | null, _onlineStoreTheme: proto_OnlineStoreTheme | null, _onlineShoppingEnabled: boolean);
29
30
  get paymentMethodDetails(): ReadonlyArray<PaymentMethodDetail>;
30
31
  get customerServicePhone(): string | null;
31
32
  get isWhatsAppSupported(): boolean;
@@ -42,7 +43,7 @@ export default class GeneralSettings {
42
43
  get primaryStoreLocation(): LatLong | null;
43
44
  get googleBusinessCategoryId(): string | null;
44
45
  get googleBusinessCategoryName(): string | null;
45
- get onlineStoreThemeId(): string | null;
46
+ get onlineStoreTheme(): proto_OnlineStoreTheme | null;
46
47
  get onlineShoppingEnabled(): boolean;
47
48
  static readonly EMPTY: GeneralSettings;
48
49
  }
@@ -7,10 +7,10 @@ import WorkingDays from "./WorkingDays";
7
7
  // merchant-only fields are absent.
8
8
  class GeneralSettings {
9
9
  // For ProtoMap, which sees amounts still multiplied by Constants.AMOUNT_FACTOR.
10
- static fromScaledValues(paymentMethodDetails, customerServicePhone, isWhatsAppSupported, isSmsSupported, businessAddresses, businessIdentifiers, fulfillmentModes, scaledDeliveryCharge, deliveryChargeTaxes, scaledFreeDeliveryTotal, deliveryRadiusKilometres, storeTagline, workingDays, primaryStoreLocation, googleBusinessCategoryId, googleBusinessCategoryName, onlineStoreThemeId, onlineShoppingEnabled) {
11
- return new GeneralSettings(paymentMethodDetails, customerServicePhone, isWhatsAppSupported, isSmsSupported, businessAddresses, businessIdentifiers, fulfillmentModes, amountFromScaledValue(scaledDeliveryCharge), deliveryChargeTaxes, amountFromScaledValue(scaledFreeDeliveryTotal), deliveryRadiusKilometres, storeTagline, workingDays, primaryStoreLocation, googleBusinessCategoryId, googleBusinessCategoryName, onlineStoreThemeId, onlineShoppingEnabled);
10
+ static fromScaledValues(paymentMethodDetails, customerServicePhone, isWhatsAppSupported, isSmsSupported, businessAddresses, businessIdentifiers, fulfillmentModes, scaledDeliveryCharge, deliveryChargeTaxes, scaledFreeDeliveryTotal, deliveryRadiusKilometres, storeTagline, workingDays, primaryStoreLocation, googleBusinessCategoryId, googleBusinessCategoryName, onlineStoreTheme, onlineShoppingEnabled) {
11
+ return new GeneralSettings(paymentMethodDetails, customerServicePhone, isWhatsAppSupported, isSmsSupported, businessAddresses, businessIdentifiers, fulfillmentModes, amountFromScaledValue(scaledDeliveryCharge), deliveryChargeTaxes, amountFromScaledValue(scaledFreeDeliveryTotal), deliveryRadiusKilometres, storeTagline, workingDays, primaryStoreLocation, googleBusinessCategoryId, googleBusinessCategoryName, onlineStoreTheme, onlineShoppingEnabled);
12
12
  }
13
- constructor(_paymentMethodDetails, _customerServicePhone, _isWhatsAppSupported, _isSmsSupported, _businessAddresses, _businessIdentifiers, _fulfillmentModes, _deliveryCharge, _deliveryChargeTaxes, _freeDeliveryTotal, _deliveryRadiusKilometres, _storeTagline, _workingDays, _primaryStoreLocation, _googleBusinessCategoryId, _googleBusinessCategoryName, _onlineStoreThemeId, _onlineShoppingEnabled) {
13
+ constructor(_paymentMethodDetails, _customerServicePhone, _isWhatsAppSupported, _isSmsSupported, _businessAddresses, _businessIdentifiers, _fulfillmentModes, _deliveryCharge, _deliveryChargeTaxes, _freeDeliveryTotal, _deliveryRadiusKilometres, _storeTagline, _workingDays, _primaryStoreLocation, _googleBusinessCategoryId, _googleBusinessCategoryName, _onlineStoreTheme, _onlineShoppingEnabled) {
14
14
  this._paymentMethodDetails = _paymentMethodDetails;
15
15
  this._customerServicePhone = _customerServicePhone;
16
16
  this._isWhatsAppSupported = _isWhatsAppSupported;
@@ -27,7 +27,7 @@ class GeneralSettings {
27
27
  this._primaryStoreLocation = _primaryStoreLocation;
28
28
  this._googleBusinessCategoryId = _googleBusinessCategoryId;
29
29
  this._googleBusinessCategoryName = _googleBusinessCategoryName;
30
- this._onlineStoreThemeId = _onlineStoreThemeId;
30
+ this._onlineStoreTheme = _onlineStoreTheme;
31
31
  this._onlineShoppingEnabled = _onlineShoppingEnabled;
32
32
  }
33
33
  get paymentMethodDetails() {
@@ -93,10 +93,11 @@ class GeneralSettings {
93
93
  get googleBusinessCategoryName() {
94
94
  return this._googleBusinessCategoryName;
95
95
  }
96
- // Which of the storefront's own themes this business picked. The storefront defines what an id
97
- // means. This library does not interpret it. Null when none was picked.
98
- get onlineStoreThemeId() {
99
- return this._onlineStoreThemeId;
96
+ // Which of the storefront's themes this business picked. Null when none was picked. A theme that a
97
+ // newer app added, which this version of the library has no entry for, is returned as it came: a
98
+ // number that is not a member of the enum. A caller that maps themes must have a fallback.
99
+ get onlineStoreTheme() {
100
+ return this._onlineStoreTheme;
100
101
  }
101
102
  // Whether the business has turned online shopping on. The server sends the business either way.
102
103
  // Model throws a NotFoundException, after reading everything it was sent, when the settings it
package/dist/Item.js CHANGED
@@ -17,7 +17,8 @@ export default class Item extends Entity {
17
17
  get description() {
18
18
  return this._description;
19
19
  }
20
- // Constants.ENTITY_ID_NULL when this item has no parent (e.g. an uncategorized product, or a price list).
20
+ // Constants.ENTITY_ID_NULL when this item has no parent (e.g. an uncategorized product, or a price
21
+ // list with no parent list).
21
22
  get parentItemId() {
22
23
  return this._parentItemId;
23
24
  }
package/dist/Package.d.ts CHANGED
@@ -11,8 +11,7 @@ export default class Package extends Item {
11
11
  private readonly _dimensions;
12
12
  private readonly _weight;
13
13
  private readonly _photos;
14
- private readonly _price;
15
- constructor(header: EntityHeader, name: string, description: string | null, productId: bigint, isDisabled: boolean, _isSoldLoose: boolean, _itemCode: bigint, _identifiers: ReadonlyArray<PackageIdentifier>, _dimensions: Dimensions | null, _weight: Weight | null, _photos: ReadonlyArray<Photo>, _price: Price | null);
14
+ constructor(header: EntityHeader, name: string, description: string | null, productId: bigint, isDisabled: boolean, _isSoldLoose: boolean, _itemCode: bigint, _identifiers: ReadonlyArray<PackageIdentifier>, _dimensions: Dimensions | null, _weight: Weight | null, _photos: ReadonlyArray<Photo>);
16
15
  get productId(): bigint;
17
16
  get isSoldLoose(): boolean;
18
17
  get itemCode(): bigint;
@@ -21,5 +20,10 @@ export default class Package extends Item {
21
20
  get weight(): Weight | null;
22
21
  get photos(): ReadonlyArray<Photo>;
23
22
  get price(): Price | null;
23
+ private set price(value);
24
24
  get isBuyable(): boolean;
25
+ private _price;
26
+ }
27
+ export interface UpdatablePackage {
28
+ set price(value: Price | null);
25
29
  }
package/dist/Package.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import Item from "./Item";
2
2
  // A package, or variant, of a product (a 500 gram bottle of mango pickle, say) and the thing a
3
- // customer buys. Built complete, with photos and price resolved, so everything here is readonly.
3
+ // customer buys. Built with its photos. Catalog sets the price, once the price list to sell from is
4
+ // known.
4
5
  export default class Package extends Item {
5
- constructor(header, name, description, productId, isDisabled, _isSoldLoose, _itemCode, _identifiers, _dimensions, _weight, _photos, _price) {
6
+ constructor(header, name, description, productId, isDisabled, _isSoldLoose, _itemCode, _identifiers, _dimensions, _weight, _photos) {
6
7
  super(header, name, description, productId, isDisabled);
7
8
  this._isSoldLoose = _isSoldLoose;
8
9
  this._itemCode = _itemCode;
@@ -10,7 +11,7 @@ export default class Package extends Item {
10
11
  this._dimensions = _dimensions;
11
12
  this._weight = _weight;
12
13
  this._photos = _photos;
13
- this._price = _price;
14
+ this._price = null;
14
15
  }
15
16
  // The product this package is a variant of, which is the parent id every item has
16
17
  get productId() {
@@ -37,11 +38,16 @@ export default class Package extends Item {
37
38
  get photos() {
38
39
  return this._photos;
39
40
  }
40
- // What this package costs, with the taxes of its product included (Product.taxes). Null when the
41
- // business has not priced it, so it cannot be bought.
41
+ // What this package costs, with the taxes of its product included (Product.taxes). It is the
42
+ // price in the catalog's sale price list, which can follow that list's parent (see
43
+ // PriceList.getPrice). It is disabled when the package is not in the list. Null when the list gives
44
+ // the package no price, so the package cannot be bought.
42
45
  get price() {
43
46
  return this._price;
44
47
  }
48
+ set price(value) {
49
+ this._price = value;
50
+ }
45
51
  // True when this package is enabled and has an enabled price. Checked here because a
46
52
  // product-level filter does not guarantee that every package, or every price, of an otherwise
47
53
  // visible product is enabled.
package/dist/Price.d.ts CHANGED
@@ -4,10 +4,13 @@ export default class Price extends Item {
4
4
  private readonly _packageId;
5
5
  private readonly _regularPrice;
6
6
  private readonly _offerPrice;
7
- static fromScaledValues(header: EntityHeader, name: string, description: string | null, priceListId: bigint, isDisabled: boolean, packageId: bigint, scaledRegularPrice: bigint, scaledOfferPrice: bigint): Price;
8
- constructor(header: EntityHeader, name: string, description: string | null, priceListId: bigint, isDisabled: boolean, _packageId: bigint, _regularPrice: number, _offerPrice: number);
7
+ private readonly _isPlaceholder;
8
+ static fromScaledValues(header: EntityHeader, name: string, description: string | null, priceListId: bigint, isDisabled: boolean, packageId: bigint, scaledRegularPrice: bigint, scaledOfferPrice: bigint, isPlaceholder?: boolean): Price;
9
+ constructor(header: EntityHeader, name: string, description: string | null, priceListId: bigint, isDisabled: boolean, _packageId: bigint, _regularPrice: number, _offerPrice: number, _isPlaceholder?: boolean);
9
10
  get packageId(): bigint;
10
11
  get priceListId(): bigint;
12
+ get isPlaceholder(): boolean;
13
+ withIsDisabled(isDisabled: boolean): Price;
11
14
  get regularPrice(): number;
12
15
  get offerPrice(): number;
13
16
  get finalPrice(): number;
package/dist/Price.js CHANGED
@@ -9,14 +9,15 @@ import Item from "./Item";
9
9
  // stores a price without taxes, so a cart takes them out (CartEditor).
10
10
  export default class Price extends Item {
11
11
  // For ProtoMap, which sees prices still multiplied by Constants.AMOUNT_FACTOR.
12
- static fromScaledValues(header, name, description, priceListId, isDisabled, packageId, scaledRegularPrice, scaledOfferPrice) {
13
- return new Price(header, name, description, priceListId, isDisabled, packageId, amountFromScaledValue(scaledRegularPrice), amountFromScaledValue(scaledOfferPrice));
12
+ static fromScaledValues(header, name, description, priceListId, isDisabled, packageId, scaledRegularPrice, scaledOfferPrice, isPlaceholder = false) {
13
+ return new Price(header, name, description, priceListId, isDisabled, packageId, amountFromScaledValue(scaledRegularPrice), amountFromScaledValue(scaledOfferPrice), isPlaceholder);
14
14
  }
15
- constructor(header, name, description, priceListId, isDisabled, _packageId, _regularPrice, _offerPrice) {
15
+ constructor(header, name, description, priceListId, isDisabled, _packageId, _regularPrice, _offerPrice, _isPlaceholder = false) {
16
16
  super(header, name, description, priceListId, isDisabled);
17
17
  this._packageId = _packageId;
18
18
  this._regularPrice = _regularPrice;
19
19
  this._offerPrice = _offerPrice;
20
+ this._isPlaceholder = _isPlaceholder;
20
21
  }
21
22
  // The package this price applies to. Nothing nests on the wire, so this is how the catalog finds
22
23
  // the price of a package.
@@ -27,6 +28,20 @@ export default class Price extends Item {
27
28
  get priceListId() {
28
29
  return this.parentItemId;
29
30
  }
31
+ // True when this price has no amounts of its own: the package has the amounts of the price the
32
+ // list's parent has for it. regularPrice and offerPrice are a copy of those, which can be out of
33
+ // date, so they are never used. isDisabled says whether the package is in this list. See
34
+ // PriceList.getPrice, which resolves it. A UI never sees one, because Package.price is never a
35
+ // placeholder.
36
+ get isPlaceholder() {
37
+ return this._isPlaceholder;
38
+ }
39
+ // This price with another enabled state: the same entity, amounts and list. For PriceList.getPrice,
40
+ // which returns a price that is not the one the list holds when the package's state in the list
41
+ // differs from the state of the price that gives its amounts.
42
+ withIsDisabled(isDisabled) {
43
+ return new Price(this.header, this.name, this.description, this.priceListId, isDisabled, this._packageId, this._regularPrice, this._offerPrice, this._isPlaceholder);
44
+ }
30
45
  get regularPrice() {
31
46
  return this._regularPrice;
32
47
  }
@@ -1,3 +1,16 @@
1
1
  import Item from "./Item";
2
+ import Price from "./Price";
2
3
  export default class PriceList extends Item {
4
+ get parentPriceListId(): bigint;
5
+ get parentPriceList(): PriceList | null;
6
+ private set parentPriceList(value);
7
+ get prices(): ReadonlyMap<bigint, Price>;
8
+ private set prices(value);
9
+ getPrice(packageId: bigint): Price | null;
10
+ private _parentPriceList;
11
+ private _prices;
12
+ }
13
+ export interface UpdatablePriceList {
14
+ set parentPriceList(value: PriceList | null);
15
+ set prices(value: ReadonlyMap<bigint, Price>);
3
16
  }
package/dist/PriceList.js CHANGED
@@ -1,5 +1,53 @@
1
1
  import Item from "./Item";
2
- // A named list of prices. A storefront sells from one, so no package has two prices to choose
3
- // between. Catalog.priceList holds it, and CartEditor names its id on the carts it builds.
2
+ // A named list of prices, at most one per package. A list can have a parent, named by its
3
+ // parentItemId, and gets the amounts of some packages from it. See getPrice for the rules. Catalog
4
+ // sets the prices and the parent, once every price list and price has been read.
4
5
  export default class PriceList extends Item {
6
+ constructor() {
7
+ super(...arguments);
8
+ this._parentPriceList = null;
9
+ this._prices = new Map();
10
+ }
11
+ // The id of the list whose prices give the amounts of the packages this list takes them from it
12
+ // for, or Constants.ENTITY_ID_NULL when this list has no parent
13
+ get parentPriceListId() {
14
+ return this.parentItemId;
15
+ }
16
+ // Null when this list has no parent, or when the parent was not read. Not Entity.parent, which is
17
+ // the business.
18
+ get parentPriceList() {
19
+ return this._parentPriceList;
20
+ }
21
+ set parentPriceList(value) {
22
+ this._parentPriceList = value;
23
+ }
24
+ // The prices of this list itself, by package id. getPrice also looks in the parent.
25
+ get prices() {
26
+ return this._prices;
27
+ }
28
+ set prices(value) {
29
+ this._prices = value;
30
+ }
31
+ // The price of a package in this list, or null when the list gives it none. Only the parent is
32
+ // consulted, not its parent.
33
+ // - A list follows its parent. A package the list has no price for has the parent's price, so its
34
+ // amounts and whether it is enabled.
35
+ // - A price of this list replaces the parent's price, both amounts and enabled state.
36
+ // - A placeholder replaces only the enabled state. The amounts are the parent's. The placeholder
37
+ // holds a copy of them that can be out of date, so it is never used.
38
+ // - The package is in the list when the price returned is enabled.
39
+ // - A placeholder gives no price when the parent has none.
40
+ // The price returned is the one that gives the amounts when it already has the right state, and a
41
+ // copy of it with the right state (withIsDisabled) otherwise.
42
+ getPrice(packageId) {
43
+ const ownPrice = this._prices.get(packageId);
44
+ if (ownPrice !== undefined && !ownPrice.isPlaceholder)
45
+ return ownPrice;
46
+ const parentPrice = this._parentPriceList?._prices.get(packageId);
47
+ if (parentPrice === undefined)
48
+ return null;
49
+ if (ownPrice === undefined || ownPrice.isDisabled === parentPrice.isDisabled)
50
+ return parentPrice;
51
+ return parentPrice.withIsDisabled(ownPrice.isDisabled);
52
+ }
5
53
  }
package/dist/Product.d.ts CHANGED
@@ -16,7 +16,7 @@ export default class Product extends Item {
16
16
  private readonly _features;
17
17
  private readonly _isForOfflineSaleOnly;
18
18
  private readonly _photos;
19
- constructor(header: EntityHeader, name: string, description: string | null, productCategoryId: bigint, isDisabled: boolean, _productType: proto_ProductType, _brand: string | null, _isManufacturedByBusiness: boolean, _manufacturer: string | null, _manufacturerAddress: Address | null, _taxes: ReadonlyArray<Tax>, _features: ReadonlyArray<string>, _isForOfflineSaleOnly: boolean, _photos: ReadonlyArray<Photo>, packages: ReadonlyArray<Package>);
19
+ constructor(header: EntityHeader, name: string, description: string | null, productCategoryId: bigint, isDisabled: boolean, _productType: proto_ProductType, _brand: string | null, _isManufacturedByBusiness: boolean, _manufacturer: string | null, _manufacturerAddress: Address | null, _taxes: ReadonlyArray<Tax>, _features: ReadonlyArray<string>, _isForOfflineSaleOnly: boolean, _photos: ReadonlyArray<Photo>);
20
20
  get productCategoryId(): bigint;
21
21
  get productType(): proto_ProductType;
22
22
  get brand(): string | null;
@@ -28,8 +28,12 @@ export default class Product extends Item {
28
28
  get isForOfflineSaleOnly(): boolean;
29
29
  get photos(): ReadonlyArray<Photo>;
30
30
  get packages(): ReadonlyArray<Package>;
31
+ private set packages(value);
31
32
  get lowestPrice(): Price | null;
32
33
  get isOnOffer(): boolean;
33
34
  get isBuyable(): boolean;
34
- private readonly _packages;
35
+ private _packages;
36
+ }
37
+ export interface UpdatableProduct {
38
+ set packages(value: ReadonlyArray<Package>);
35
39
  }