ng-miam 10.7.0 → 10.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/bundles/ng-miam.umd.js +375 -103
  2. package/bundles/ng-miam.umd.js.map +1 -1
  3. package/bundles/ng-miam.umd.min.js +1 -1
  4. package/bundles/ng-miam.umd.min.js.map +1 -1
  5. package/esm2015/lib/_components/product-card/product-card.component.js +11 -3
  6. package/esm2015/lib/_constants/error-toast-codes.js +8 -0
  7. package/esm2015/lib/_models/basket-actions/add-ingredient-from-replace-action.js +4 -2
  8. package/esm2015/lib/_services/analytics.service.js +5 -2
  9. package/esm2015/lib/_services/context-registry.service.js +2 -1
  10. package/esm2015/lib/_services/context.service.js +9 -2
  11. package/esm2015/lib/_services/ignored-ingredients.service.js +8 -5
  12. package/esm2015/lib/_services/new-recipe-details.service.js +32 -8
  13. package/esm2015/lib/_services/recipe-details-products.service.js +25 -5
  14. package/esm2015/lib/_services/recipe-details.service.js +3 -4
  15. package/esm2015/lib/_services/recipes.service.js +5 -1
  16. package/esm2015/lib/_services/replace-item.service.js +115 -60
  17. package/esm2015/lib/_services/store-out-of-stock.service.js +24 -5
  18. package/esm2015/lib/_types/builded/mealz-interface.js +1 -1
  19. package/esm2015/lib/_types/builded/mealz-internal-interface.js +1 -1
  20. package/esm2015/lib/_utils/attach-product-show-tracking.js +56 -0
  21. package/esm2015/lib/_utils/product-show-tracker.js +62 -0
  22. package/esm2015/lib/_web-components/basket-preview/basket-preview-product/basket-preview-product.component.js +3 -1
  23. package/esm2015/lib/environments/version.js +2 -2
  24. package/fesm2015/ng-miam.js +346 -90
  25. package/fesm2015/ng-miam.js.map +1 -1
  26. package/lib/_components/product-card/product-card.component.d.ts +1 -0
  27. package/lib/_components/product-card/product-card.component.d.ts.map +1 -1
  28. package/lib/_constants/error-toast-codes.d.ts +5 -0
  29. package/lib/_constants/error-toast-codes.d.ts.map +1 -0
  30. package/lib/_models/basket-actions/add-ingredient-from-replace-action.d.ts.map +1 -1
  31. package/lib/_services/analytics.service.d.ts.map +1 -1
  32. package/lib/_services/context-registry.service.d.ts +5 -0
  33. package/lib/_services/context-registry.service.d.ts.map +1 -1
  34. package/lib/_services/context.service.d.ts.map +1 -1
  35. package/lib/_services/ignored-ingredients.service.d.ts +1 -0
  36. package/lib/_services/ignored-ingredients.service.d.ts.map +1 -1
  37. package/lib/_services/new-recipe-details.service.d.ts +3 -1
  38. package/lib/_services/new-recipe-details.service.d.ts.map +1 -1
  39. package/lib/_services/recipe-details-products.service.d.ts +5 -1
  40. package/lib/_services/recipe-details-products.service.d.ts.map +1 -1
  41. package/lib/_services/recipe-details.service.d.ts.map +1 -1
  42. package/lib/_services/recipes.service.d.ts.map +1 -1
  43. package/lib/_services/replace-item.service.d.ts +26 -15
  44. package/lib/_services/replace-item.service.d.ts.map +1 -1
  45. package/lib/_services/store-out-of-stock.service.d.ts +7 -0
  46. package/lib/_services/store-out-of-stock.service.d.ts.map +1 -1
  47. package/lib/_types/builded/mealz-interface.d.ts +7 -0
  48. package/lib/_types/builded/mealz-interface.d.ts.map +1 -1
  49. package/lib/_types/builded/mealz-internal-interface.d.ts +9 -0
  50. package/lib/_types/builded/mealz-internal-interface.d.ts.map +1 -1
  51. package/lib/_utils/attach-product-show-tracking.d.ts +14 -0
  52. package/lib/_utils/attach-product-show-tracking.d.ts.map +1 -0
  53. package/lib/_utils/product-show-tracker.d.ts +25 -0
  54. package/lib/_utils/product-show-tracker.d.ts.map +1 -0
  55. package/lib/_web-components/basket-preview/basket-preview-product/basket-preview-product.component.d.ts.map +1 -1
  56. package/lib/environments/version.d.ts +1 -1
  57. package/package.json +1 -1
@@ -1647,7 +1647,7 @@
1647
1647
  analyticsEnabled: true // Only used in DEV mode
1648
1648
  };
1649
1649
 
1650
- var WINDOW_SINGLETON_KEY = '__mealzRecipeCardShowTracker__';
1650
+ var WINDOW_SINGLETON_KEY$1 = '__mealzRecipeCardShowTracker__';
1651
1651
  /**
1652
1652
  * Tracks recipe.show events and prevents duplicates (first show, or again after scroll).
1653
1653
  * Shared across SDK and Lit recipe cards via {@link WINDOW_SINGLETON_KEY} on window.
@@ -1672,7 +1672,7 @@
1672
1672
  var w = typeof window !== 'undefined'
1673
1673
  ? window
1674
1674
  : undefined;
1675
- var fromWindow = w === null || w === void 0 ? void 0 : w[WINDOW_SINGLETON_KEY];
1675
+ var fromWindow = w === null || w === void 0 ? void 0 : w[WINDOW_SINGLETON_KEY$1];
1676
1676
  if (fromWindow) {
1677
1677
  RecipeCardShowTracker.instance = fromWindow;
1678
1678
  return fromWindow;
@@ -1680,7 +1680,7 @@
1680
1680
  if (!RecipeCardShowTracker.instance) {
1681
1681
  RecipeCardShowTracker.instance = new RecipeCardShowTracker();
1682
1682
  if (w) {
1683
- w[WINDOW_SINGLETON_KEY] = RecipeCardShowTracker.instance;
1683
+ w[WINDOW_SINGLETON_KEY$1] = RecipeCardShowTracker.instance;
1684
1684
  }
1685
1685
  }
1686
1686
  return RecipeCardShowTracker.instance;
@@ -1740,7 +1740,7 @@
1740
1740
  this.hasScrolled = false;
1741
1741
  RecipeCardShowTracker.instance = null;
1742
1742
  if (typeof window !== 'undefined') {
1743
- delete window[WINDOW_SINGLETON_KEY];
1743
+ delete window[WINDOW_SINGLETON_KEY$1];
1744
1744
  }
1745
1745
  };
1746
1746
  RecipeCardShowTracker.prototype.setupScrollListener = function () {
@@ -1843,6 +1843,135 @@
1843
1843
  };
1844
1844
  }
1845
1845
 
1846
+ var WINDOW_SINGLETON_KEY = '__mealzProductShowTracker__';
1847
+ /**
1848
+ * Dedupes product.show per recipe visit (recipeId:itemId).
1849
+ * Cleared at the start of each openRecipeDetails visit and by ContextService.loadAndSetupRecipe
1850
+ * at the start of each recipe-to-basket visit.
1851
+ */
1852
+ var ProductShowTracker = /** @class */ (function () {
1853
+ function ProductShowTracker() {
1854
+ var _this = this;
1855
+ this.trackedKeys = new Set();
1856
+ this.handlePageUnload = function () {
1857
+ _this.destroy();
1858
+ };
1859
+ if (typeof window !== 'undefined') {
1860
+ window.addEventListener('beforeunload', this.handlePageUnload);
1861
+ }
1862
+ }
1863
+ ProductShowTracker.getInstance = function () {
1864
+ var windowRef = typeof window !== 'undefined' ? window : undefined;
1865
+ var fromWindow = windowRef === null || windowRef === void 0 ? void 0 : windowRef[WINDOW_SINGLETON_KEY];
1866
+ if (fromWindow) {
1867
+ ProductShowTracker.instance = fromWindow;
1868
+ return fromWindow;
1869
+ }
1870
+ if (!ProductShowTracker.instance) {
1871
+ ProductShowTracker.instance = new ProductShowTracker();
1872
+ if (windowRef) {
1873
+ windowRef[WINDOW_SINGLETON_KEY] = ProductShowTracker.instance;
1874
+ }
1875
+ }
1876
+ return ProductShowTracker.instance;
1877
+ };
1878
+ ProductShowTracker.prototype.trackProductShow = function (analyticsPath, props) {
1879
+ var _a, _b;
1880
+ var trackingKey = props.recipe_id + ":" + props.item_id;
1881
+ if (this.trackedKeys.has(trackingKey)) {
1882
+ return;
1883
+ }
1884
+ this.trackedKeys.add(trackingKey);
1885
+ (_b = (_a = window.mealzInternal) === null || _a === void 0 ? void 0 : _a.analytics) === null || _b === void 0 ? void 0 : _b.sendEvent('product.show', analyticsPath, props);
1886
+ };
1887
+ ProductShowTracker.prototype.clearForRecipe = function (recipeId) {
1888
+ var e_1, _c;
1889
+ var prefix = recipeId + ":";
1890
+ try {
1891
+ for (var _d = __values(this.trackedKeys), _e = _d.next(); !_e.done; _e = _d.next()) {
1892
+ var key = _e.value;
1893
+ if (key.startsWith(prefix)) {
1894
+ this.trackedKeys.delete(key);
1895
+ }
1896
+ }
1897
+ }
1898
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
1899
+ finally {
1900
+ try {
1901
+ if (_e && !_e.done && (_c = _d.return)) _c.call(_d);
1902
+ }
1903
+ finally { if (e_1) throw e_1.error; }
1904
+ }
1905
+ };
1906
+ ProductShowTracker.prototype.clearAll = function () {
1907
+ this.trackedKeys.clear();
1908
+ };
1909
+ ProductShowTracker.prototype.destroy = function () {
1910
+ if (typeof window !== 'undefined') {
1911
+ window.removeEventListener('beforeunload', this.handlePageUnload);
1912
+ delete window[WINDOW_SINGLETON_KEY];
1913
+ }
1914
+ this.trackedKeys.clear();
1915
+ ProductShowTracker.instance = null;
1916
+ };
1917
+ return ProductShowTracker;
1918
+ }());
1919
+ ProductShowTracker.instance = null;
1920
+
1921
+ var PRODUCT_SHOW_ANALYTICS_PATH_SUFFIX = '/shopping';
1922
+ var PRODUCT_SHOW_VISIBILITY_THRESHOLD = 0.8;
1923
+ var PRODUCT_SHOW_DEBOUNCE_MS = 1000;
1924
+ var SKIP_PRODUCT_SHOW_STATUSES = ['unavailable', 'out_of_stock', 'ignored', 'deleted', 'often_deleted'];
1925
+ function buildProductShowProps(product, recipeId) {
1926
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
1927
+ var basketEntry = product === null || product === void 0 ? void 0 : product.basketEntry;
1928
+ var selectedItem = basketEntry === null || basketEntry === void 0 ? void 0 : basketEntry.selectedItem;
1929
+ var item = (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.id) != null ? selectedItem : product === null || product === void 0 ? void 0 : product.item;
1930
+ if ((item === null || item === void 0 ? void 0 : item.id) == null) {
1931
+ return null;
1932
+ }
1933
+ var itemAttributes = item.attributes || {};
1934
+ var entryName = (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.id) != null
1935
+ ? ((_c = (_a = basketEntry.name) !== null && _a !== void 0 ? _a : (_b = basketEntry.attributes) === null || _b === void 0 ? void 0 : _b.name) !== null && _c !== void 0 ? _c : '')
1936
+ : ((_h = (_f = (_e = (_d = product.ingredient) === null || _d === void 0 ? void 0 : _d.attributes) === null || _e === void 0 ? void 0 : _e.name) !== null && _f !== void 0 ? _f : (_g = product.ingredient) === null || _g === void 0 ? void 0 : _g.name) !== null && _h !== void 0 ? _h : '');
1937
+ var productBasePrice = ((_j = product === null || product === void 0 ? void 0 : product.price) === null || _j === void 0 ? void 0 : _j.unit) != null ? String(product.price.unit) : '0.0';
1938
+ return {
1939
+ entry_name: entryName,
1940
+ item_id: String(item.id),
1941
+ ext_item_id: String((_k = itemAttributes['ext-id']) !== null && _k !== void 0 ? _k : ''),
1942
+ item_ean: String((_m = (_l = item.ean) !== null && _l !== void 0 ? _l : itemAttributes.ean) !== null && _m !== void 0 ? _m : ''),
1943
+ recipe_id: recipeId,
1944
+ product_base_price: productBasePrice
1945
+ };
1946
+ }
1947
+ function isProductShowEligible(status) {
1948
+ return !!status && !SKIP_PRODUCT_SHOW_STATUSES.includes(status);
1949
+ }
1950
+ function getProductShowStatus(product) {
1951
+ var _a, _b;
1952
+ return (_b = (_a = product.basketEntry) === null || _a === void 0 ? void 0 : _a.status) !== null && _b !== void 0 ? _b : product.status;
1953
+ }
1954
+ function attachProductShowTracking(options) {
1955
+ var element = options.element, product = options.product, recipeId = options.recipeId, _o = options.analyticsPath, analyticsPath = _o === void 0 ? "/recipes/" + recipeId + PRODUCT_SHOW_ANALYTICS_PATH_SUFFIX : _o;
1956
+ ProductShowTracker.getInstance();
1957
+ var listener = new ViewportListener(element, function () {
1958
+ if (!isProductShowEligible(getProductShowStatus(product))) {
1959
+ return;
1960
+ }
1961
+ var props = buildProductShowProps(product, recipeId);
1962
+ if (!props) {
1963
+ return;
1964
+ }
1965
+ ProductShowTracker.getInstance().trackProductShow(analyticsPath, props);
1966
+ }, {
1967
+ threshold: PRODUCT_SHOW_VISIBILITY_THRESHOLD,
1968
+ debounce: PRODUCT_SHOW_DEBOUNCE_MS
1969
+ });
1970
+ return {
1971
+ disconnect: function () { return listener.disconnect(); }
1972
+ };
1973
+ }
1974
+
1846
1975
  var Ingredient = /** @class */ (function (_super) {
1847
1976
  __extends(Ingredient, _super);
1848
1977
  function Ingredient() {
@@ -3646,7 +3775,7 @@
3646
3775
  EventJourney["EMPTY"] = "";
3647
3776
  })(EventJourney || (EventJourney = {}));
3648
3777
 
3649
- var VERSION = "10.7.0"; // TODO: replace by ##VERSION## and update it in the CI/CD
3778
+ var VERSION = "10.8.0"; // TODO: replace by ##VERSION## and update it in the CI/CD
3650
3779
 
3651
3780
  var ContextRegistryService = /** @class */ (function () {
3652
3781
  function ContextRegistryService() {
@@ -3655,6 +3784,7 @@
3655
3784
  this.guestsChanged = new i0.EventEmitter();
3656
3785
  this.recipePriceChanged = new i0.EventEmitter();
3657
3786
  this.addAllProductsWasClicked = new i0.EventEmitter();
3787
+ this.drawerErrorToast = new i0.EventEmitter();
3658
3788
  // CALLBACKS
3659
3789
  this.hookCallback = function (isLogged, isPosValid) {
3660
3790
  mealzWarn('[Mealz] hookCallback (used to force user login) not set');
@@ -3794,7 +3924,7 @@
3794
3924
  var detectedJourney = journey !== null && journey !== void 0 ? journey : detectJourney();
3795
3925
  var isBulk = actions.length > 1;
3796
3926
  actions.forEach(function (action) {
3797
- var _a, _b, _c, _d, _e, _f, _g, _h, _j;
3927
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
3798
3928
  try {
3799
3929
  var params = action.params;
3800
3930
  // Determine add_source: replacement if previousItem exists, otherwise unit or bulk based on actions count
@@ -3827,6 +3957,7 @@
3827
3957
  ext_item_id: (_j = item.attributes) === null || _j === void 0 ? void 0 : _j['ext-id'],
3828
3958
  item_ean: item.ean,
3829
3959
  product_quantity: quantity.toString(),
3960
+ product_base_price: ((_k = basketEntry === null || basketEntry === void 0 ? void 0 : basketEntry.price) !== null && _k !== void 0 ? _k : 0).toString(),
3830
3961
  recipe_id: recipeId,
3831
3962
  add_source: addSource
3832
3963
  }, detectedJourney);
@@ -3840,12 +3971,14 @@
3840
3971
  var _this = this;
3841
3972
  var detectedJourney = journey !== null && journey !== void 0 ? journey : detectJourney();
3842
3973
  actions.forEach(function (action) {
3974
+ var _a;
3843
3975
  _this.sendEvent('entry.deleted', action.params.eventTrace.originPath, {
3844
3976
  entry_name: action.params.basketEntry.name,
3845
3977
  item_id: action.params.basketEntry.selectedItem.id,
3846
3978
  ext_item_id: action.params.basketEntry.selectedItem.attributes['ext-id'],
3847
3979
  item_ean: action.params.basketEntry.selectedItem.ean,
3848
3980
  product_quantity: action.params.basketEntry.quantity.toString(),
3981
+ product_base_price: ((_a = action.params.basketEntry.price) !== null && _a !== void 0 ? _a : 0).toString(),
3849
3982
  recipe_id: action.params.recipeId
3850
3983
  }, detectedJourney);
3851
3984
  });
@@ -4078,10 +4211,11 @@
4078
4211
 
4079
4212
  var IgnoredIngredientsService = /** @class */ (function () {
4080
4213
  function IgnoredIngredientsService() {
4214
+ this.storageKey = '_miam/ignoredBasketEntries';
4081
4215
  }
4082
4216
  Object.defineProperty(IgnoredIngredientsService.prototype, "ignoredIngredients", {
4083
4217
  get: function () {
4084
- return JSON.parse(sessionStorage.getItem('_miam/ignoredBasketEntries') || '[]');
4218
+ return JSON.parse(localStorage.getItem(this.storageKey) || '[]');
4085
4219
  },
4086
4220
  enumerable: false,
4087
4221
  configurable: true
@@ -4092,16 +4226,16 @@
4092
4226
  newIgnoredIngredients = [newIgnoredIngredients];
4093
4227
  }
4094
4228
  newIgnoredIngredients = newIgnoredIngredients.filter(function (e) { return !_this.isIngredientIgnored(e); });
4095
- sessionStorage.setItem('_miam/ignoredBasketEntries', JSON.stringify(__spread(this.ignoredIngredients, newIgnoredIngredients)));
4229
+ localStorage.setItem(this.storageKey, JSON.stringify(__spread(this.ignoredIngredients, newIgnoredIngredients)));
4096
4230
  };
4097
4231
  IgnoredIngredientsService.prototype.isIngredientIgnored = function (ingredientId) {
4098
4232
  return this.ignoredIngredients.includes(ingredientId);
4099
4233
  };
4100
4234
  IgnoredIngredientsService.prototype.removeIngredientFromIgnored = function (ingredientId) {
4101
- sessionStorage.setItem('_miam/ignoredBasketEntries', JSON.stringify(this.ignoredIngredients.filter(function (e) { return e !== ingredientId; })));
4235
+ localStorage.setItem(this.storageKey, JSON.stringify(this.ignoredIngredients.filter(function (e) { return e !== ingredientId; })));
4102
4236
  };
4103
4237
  IgnoredIngredientsService.prototype.resetIgnoredIngredients = function () {
4104
- sessionStorage.removeItem('_miam/ignoredBasketEntries');
4238
+ localStorage.removeItem(this.storageKey);
4105
4239
  };
4106
4240
  return IgnoredIngredientsService;
4107
4241
  }());
@@ -5901,6 +6035,9 @@
5901
6035
  RecipesService.prototype.openRecipeDetails = function (recipeId, guests, initialTabIndex, path, categoryId) {
5902
6036
  if (path === void 0) { path = ''; }
5903
6037
  if (categoryId === void 0) { categoryId = null; }
6038
+ if (recipeId) {
6039
+ ProductShowTracker.getInstance().clearForRecipe(String(recipeId));
6040
+ }
5904
6041
  this.display(recipeId, { previewAllowed: true, previewMode: false, guests: guests });
5905
6042
  this.recipeDetailsTabIndex$.next(initialTabIndex);
5906
6043
  this.analyticsService.sendEvent('recipe.display', path, { recipe_id: recipeId, category_id: categoryId });
@@ -9268,6 +9405,7 @@
9268
9405
  var StoreOutOfStockService = /** @class */ (function () {
9269
9406
  function StoreOutOfStockService() {
9270
9407
  this.outOfStockEntries = [];
9408
+ this.outOfStockPersisted$ = new rxjs.Subject();
9271
9409
  this.fetchCurrentOutOfStocks();
9272
9410
  }
9273
9411
  StoreOutOfStockService.prototype.fetchCurrentOutOfStocks = function () {
@@ -9286,13 +9424,17 @@
9286
9424
  var _a;
9287
9425
  if (entry.status === 'active') {
9288
9426
  this.removeEntryFromStorage(entry);
9289
- return;
9427
+ return false;
9290
9428
  }
9291
9429
  if ((_a = entry === null || entry === void 0 ? void 0 : entry.selectedItem) === null || _a === void 0 ? void 0 : _a.id) {
9292
- return this.outOfStockEntries.includes(entry.selectedItem.id.toString());
9430
+ return this.isItemOutOfStock(entry.selectedItem.id);
9293
9431
  }
9294
9432
  return false;
9295
9433
  };
9434
+ StoreOutOfStockService.prototype.isItemOutOfStock = function (itemId) {
9435
+ this.fetchCurrentOutOfStocks();
9436
+ return this.outOfStockEntries.includes(itemId);
9437
+ };
9296
9438
  StoreOutOfStockService.prototype.removeEntryFromStorage = function (entry) {
9297
9439
  var _a;
9298
9440
  if ((_a = entry === null || entry === void 0 ? void 0 : entry.selectedItem) === null || _a === void 0 ? void 0 : _a.id) {
@@ -9300,12 +9442,25 @@
9300
9442
  sessionStorage.setItem('_miam/outOfStockEntry', JSON.stringify(this.outOfStockEntries));
9301
9443
  }
9302
9444
  };
9445
+ /**
9446
+ * Marks an entry as out of stock in session storage.
9447
+ */
9448
+ StoreOutOfStockService.prototype.persistOutOfStock = function (entry) {
9449
+ var _a;
9450
+ if (!((_a = entry.selectedItem) === null || _a === void 0 ? void 0 : _a.id)) {
9451
+ return;
9452
+ }
9453
+ entry.status = 'out_of_stock';
9454
+ this.addEntryToStorage(entry);
9455
+ this.outOfStockPersisted$.next(entry);
9456
+ };
9303
9457
  StoreOutOfStockService.prototype.addEntryToStorage = function (entry) {
9304
9458
  var _a;
9305
9459
  if ((_a = entry.selectedItem) === null || _a === void 0 ? void 0 : _a.id) {
9306
9460
  this.fetchCurrentOutOfStocks();
9307
- if (!this.outOfStockEntries.includes(entry.selectedItem.id)) {
9308
- this.outOfStockEntries.push(entry.selectedItem.id);
9461
+ var itemId = entry.selectedItem.id.toString();
9462
+ if (!this.outOfStockEntries.includes(itemId)) {
9463
+ this.outOfStockEntries.push(itemId);
9309
9464
  }
9310
9465
  sessionStorage.setItem('_miam/outOfStockEntry', JSON.stringify(this.outOfStockEntries));
9311
9466
  }
@@ -9875,9 +10030,8 @@
9875
10030
  RecipeDetailsService.prototype.setProductOutOfStockInSession = function (products) {
9876
10031
  var _this = this;
9877
10032
  products.forEach(function (_c) {
9878
- var entry = _c.entry;
9879
- entry.status = 'out_of_stock';
9880
- _this.storeOutOfStockService.addEntryToStorage(entry);
10033
+ var ingredientId = _c.ingredientId, entry = _c.entry;
10034
+ _this.storeOutOfStockService.persistOutOfStock(entry);
9881
10035
  });
9882
10036
  };
9883
10037
  return RecipeDetailsService;
@@ -9939,7 +10093,9 @@
9939
10093
  }
9940
10094
  this.basketEntry.addRelationship(this.updateMealzParams.item, 'selected-item');
9941
10095
  this.basketEntriesService.selectItem(this.basketEntry.id, this.updateMealzParams.item.id, this.quantity)
9942
- .pipe(operators.switchMap(function () { return _this.basketsService.refreshCurrentBasket(); }), operators.take(1)).subscribe();
10096
+ .pipe(operators.switchMap(function () { return _this.basketsService.refreshCurrentBasket(); }), operators.take(1)).subscribe({
10097
+ error: function () { return _this.onError.next(); }
10098
+ });
9943
10099
  };
9944
10100
  AddIngredientFromReplaceAction.prototype.createActionForRetailer = function () {
9945
10101
  var _this = this;
@@ -9987,8 +10143,16 @@
9987
10143
  return AddIngredientAction;
9988
10144
  }(BasketAction));
9989
10145
 
10146
+ var UNAVAILABLE_PRODUCT_ADD_ERROR_CODE = 'unavailableProductAdd';
10147
+ var UNAVAILABLE_PRODUCTS_ADD_ERROR_CODE = 'unavailableProductsAdd';
10148
+ function unavailableProductAddErrorCode(failureCount) {
10149
+ return failureCount <= 1
10150
+ ? UNAVAILABLE_PRODUCT_ADD_ERROR_CODE
10151
+ : UNAVAILABLE_PRODUCTS_ADD_ERROR_CODE;
10152
+ }
10153
+
9990
10154
  var ReplaceItemService = /** @class */ (function () {
9991
- function ReplaceItemService(basketsService, basketEntriesService, itemsService, recipeDetailsService, basketSynchroService, ignoredBasketsService, recipesService, analyticsService) {
10155
+ function ReplaceItemService(basketsService, basketEntriesService, itemsService, recipeDetailsService, basketSynchroService, ignoredBasketsService, recipesService, analyticsService, storeOutOfStockService, contextRegistryService) {
9992
10156
  var _this = this;
9993
10157
  this.basketsService = basketsService;
9994
10158
  this.basketEntriesService = basketEntriesService;
@@ -9998,6 +10162,8 @@
9998
10162
  this.ignoredBasketsService = ignoredBasketsService;
9999
10163
  this.recipesService = recipesService;
10000
10164
  this.analyticsService = analyticsService;
10165
+ this.storeOutOfStockService = storeOutOfStockService;
10166
+ this.contextRegistryService = contextRegistryService;
10001
10167
  this.basketEntry = new BasketEntry();
10002
10168
  this.itemsWithPricesList$ = new rxjs.BehaviorSubject([]);
10003
10169
  this.fetchingItemLoading$ = new rxjs.BehaviorSubject(false);
@@ -10031,14 +10197,20 @@
10031
10197
  _this.recipe = recipeDisplay === null || recipeDisplay === void 0 ? void 0 : recipeDisplay.recipe;
10032
10198
  });
10033
10199
  this.replaceProductFromPreviewOpen$.pipe(operators.filter(function (isOpen) { return !!isOpen; }), operators.switchMap(function () { return _this.fetchItems(); })).subscribe();
10200
+ this.storeOutOfStockService.outOfStockPersisted$.subscribe(function () {
10201
+ _this.refreshItemsOutOfStockFlags();
10202
+ });
10034
10203
  }
10035
- ReplaceItemService.prototype.handleItemSelection = function (item, searchString, onComplete, eventTrace, timeOutAction) {
10204
+ ReplaceItemService.prototype.handleItemSelection = function (item, searchString, onComplete, eventTrace) {
10036
10205
  var _this = this;
10037
10206
  var fromRecipeDetails = !!this.recipe;
10038
- this.timeOutAction = timeOutAction;
10207
+ this.onComplete = onComplete;
10039
10208
  this.onSelectItem(item, searchString, eventTrace)
10040
10209
  .pipe(operators.take(1))
10041
10210
  .subscribe(function (action) {
10211
+ if (!action) {
10212
+ return;
10213
+ }
10042
10214
  action.onSuccess.pipe(operators.take(1)).subscribe(function () {
10043
10215
  _this.recipeDetailsService.hasReplaced = true;
10044
10216
  if (eventTrace && fromRecipeDetails && !_this.addProductMode) {
@@ -10046,15 +10218,17 @@
10046
10218
  }
10047
10219
  if (_this.addProductMode || !fromRecipeDetails) {
10048
10220
  _this.basketsService.itemWillBeAdded(item.id).subscribe(function (addItem) {
10049
- if (!addItem && _this.replaceItemLoading$.value) {
10221
+ if (!addItem) {
10222
+ _this.replaceItemLoading$.next(undefined);
10050
10223
  onComplete(item.id);
10051
10224
  }
10052
10225
  });
10053
10226
  }
10054
10227
  else {
10055
10228
  _this.basketsService.ingredientWillBeAdded(_this.ingredient.id).subscribe(function (addIngredient) {
10056
- if (!addIngredient && _this.replaceItemLoading$.value) {
10057
- onComplete(_this.basketEntry.id);
10229
+ if (!addIngredient) {
10230
+ _this.replaceItemLoading$.next(undefined);
10231
+ onComplete(item.id);
10058
10232
  }
10059
10233
  });
10060
10234
  }
@@ -10096,7 +10270,8 @@
10096
10270
  var priceObservables = items.map(function (item) { return _this.itemsService.getPriceForItem(item.id).pipe(operators.map(function (price) { return ({ item: item, price: price }); })); });
10097
10271
  return (priceObservables.length === 0 ? rxjs.of([]) : rxjs.forkJoin(priceObservables)).pipe(operators.map(function (itemsWithPrices) {
10098
10272
  _this.fetchingItemLoading$.next(false);
10099
- return _this.itemsWithPricesList$.next(itemsWithPrices);
10273
+ var itemsWithOutOfStockFlags = _this.withOutOfStockFlags(itemsWithPrices);
10274
+ return _this.itemsWithPricesList$.next(itemsWithOutOfStockFlags);
10100
10275
  }));
10101
10276
  }));
10102
10277
  };
@@ -10127,18 +10302,13 @@
10127
10302
  * Adds the action to the synchronization service's action list for processing.
10128
10303
  */
10129
10304
  ReplaceItemService.prototype.handleAddProductMode = function (item, eventTrace) {
10130
- var _this = this;
10131
10305
  this.replaceItemLoading$.next(item);
10132
10306
  var newBasketEntry = new BasketEntry();
10133
10307
  newBasketEntry.quantity = 1;
10134
10308
  newBasketEntry.quantityToAdd = 0;
10135
10309
  this.replaceRelationship(newBasketEntry, item);
10136
10310
  var addIngredientWithoutRecipe = new AddIngredientAction(newBasketEntry, { eventTrace: eventTrace, item: item }, this.basketsService, this.basketEntriesService);
10137
- rxjs.merge(addIngredientWithoutRecipe.onTimeout, addIngredientWithoutRecipe.onError)
10138
- .pipe(operators.take(1))
10139
- .subscribe(function () {
10140
- _this.rollbackReplace();
10141
- });
10311
+ this.listenToAddActionFailure(addIngredientWithoutRecipe, item, newBasketEntry);
10142
10312
  return rxjs.of(addIngredientWithoutRecipe);
10143
10313
  };
10144
10314
  /**
@@ -10163,43 +10333,55 @@
10163
10333
  var priceObservables = items.map(function (item) { return _this.itemsService.getPriceForItem(item.id).pipe(operators.map(function (price) { return ({ item: item, price: price }); })); });
10164
10334
  return (priceObservables.length === 0 ? rxjs.of([]) : rxjs.forkJoin(priceObservables));
10165
10335
  }), operators.map(function (itemsWithPrices) {
10166
- _this.itemsWithPricesList$.next(_this.moveSelectedItemToFront(itemsWithPrices));
10336
+ var sortedItems = _this.withOutOfStockFlags(_this.moveSelectedItemToFront(itemsWithPrices));
10337
+ _this.itemsWithPricesList$.next(sortedItems);
10167
10338
  _this.fetchingItemLoading$.next(false);
10168
- return itemsWithPrices;
10339
+ return sortedItems;
10169
10340
  }));
10170
10341
  };
10171
10342
  /**
10172
- * Replace the current basket entry with the given item
10343
+ * Replace the current basket entry with the given item.
10344
+ * Removes the previous item from the retailer cart first; the add action is only enqueued after remove succeeds.
10173
10345
  */
10174
10346
  ReplaceItemService.prototype.selectItem = function (item) {
10175
10347
  var _this = this;
10176
- var _a, _b;
10348
+ var _a;
10177
10349
  var deletedBasketEntry = deepCloneWithClass(this.basketEntry);
10178
10350
  deletedBasketEntry.quantity = 0;
10179
10351
  deletedBasketEntry.selectedItem.extId = this.previousItem.extId;
10180
- var removeIngredient = new RemoveIngredientFromReplaceAction(deletedBasketEntry, {}, this.basketsService);
10181
- rxjs.merge(removeIngredient.onTimeout, removeIngredient.onError).pipe(operators.take(1)).subscribe(function () {
10182
- _this.rollbackReplace();
10183
- });
10184
- this.basketSynchroService.addToActionList([removeIngredient]);
10185
- var guests = +((_a = this.recipe) === null || _a === void 0 ? void 0 : _a.modifiedGuests) || +((_b = this.recipe) === null || _b === void 0 ? void 0 : _b.guests) || 4;
10186
- return (this.ingredient && this.recipe ?
10187
- this.basketsService.basketEntryFromIngredient(this.ingredient.id, guests, true, item.id) :
10188
- rxjs.of(this.basketEntry)).pipe(operators.switchMap(function (newEntry) {
10189
- var newBasketEntry = deepCloneWithClass(_this.basketEntry);
10190
- newBasketEntry.quantityToAdd = newEntry.quantityToAdd;
10191
- _this.replaceRelationship(newBasketEntry, item);
10192
- var addIngredientFromReplace = new AddIngredientFromReplaceAction(newBasketEntry, {
10193
- item: item
10194
- }, _this.basketsService, _this.basketEntriesService);
10195
- rxjs.merge(addIngredientFromReplace.onTimeout, addIngredientFromReplace.onError).pipe(operators.take(1)).subscribe(function () {
10196
- _this.rollbackReplace();
10352
+ var removeIngredient = new RemoveIngredientFromReplaceAction(deletedBasketEntry, { ingredientId: (_a = this.ingredient) === null || _a === void 0 ? void 0 : _a.id }, this.basketsService);
10353
+ return new rxjs.Observable(function (observer) {
10354
+ rxjs.merge(removeIngredient.onError, removeIngredient.onTimeout).pipe(operators.take(1)).subscribe(function () {
10355
+ _this.clearReplaceLoadingState();
10356
+ observer.complete();
10197
10357
  });
10198
- addIngredientFromReplace.onSuccess.pipe(operators.take(1)).subscribe(function () {
10199
- _this.recipeDetailsService.updateIngredientFromBasketLoading = false;
10358
+ removeIngredient.onSuccess.pipe(operators.take(1), operators.switchMap(function () { return _this.createAddIngredientFromReplaceAction(item); })).subscribe({
10359
+ next: function (addIngredientFromReplace) {
10360
+ _this.listenToAddActionFailure(addIngredientFromReplace, item);
10361
+ addIngredientFromReplace.onSuccess.pipe(operators.take(1)).subscribe(function () {
10362
+ _this.recipeDetailsService.updateIngredientFromBasketLoading = false;
10363
+ });
10364
+ observer.next(addIngredientFromReplace);
10365
+ observer.complete();
10366
+ },
10367
+ error: function () {
10368
+ _this.confirmMealzRemoveAfterReplaceAddFailure();
10369
+ _this.clearReplaceLoadingState();
10370
+ observer.complete();
10371
+ }
10200
10372
  });
10201
- return rxjs.of(addIngredientFromReplace);
10202
- }));
10373
+ _this.basketSynchroService.addToActionList([removeIngredient]);
10374
+ });
10375
+ };
10376
+ ReplaceItemService.prototype.withOutOfStockFlags = function (items) {
10377
+ var _this = this;
10378
+ return items.map(function (itemWithPrice) { return (Object.assign(Object.assign({}, itemWithPrice), { isOutOfStock: _this.storeOutOfStockService.isItemOutOfStock(itemWithPrice.item.id) })); });
10379
+ };
10380
+ ReplaceItemService.prototype.refreshItemsOutOfStockFlags = function () {
10381
+ if (this.itemsWithPricesList$.value.length === 0) {
10382
+ return;
10383
+ }
10384
+ this.itemsWithPricesList$.next(this.withOutOfStockFlags(this.itemsWithPricesList$.value));
10203
10385
  };
10204
10386
  ReplaceItemService.prototype.changeReplaceModalState = function (state, basketEntryToReplace) {
10205
10387
  if (basketEntryToReplace) {
@@ -10225,18 +10407,62 @@
10225
10407
  }
10226
10408
  basketEntry.addRelationship(item, 'selected-item');
10227
10409
  };
10410
+ ReplaceItemService.prototype.createAddIngredientFromReplaceAction = function (item) {
10411
+ var _this = this;
10412
+ var _a, _b;
10413
+ var guests = +((_a = this.recipe) === null || _a === void 0 ? void 0 : _a.modifiedGuests) || +((_b = this.recipe) === null || _b === void 0 ? void 0 : _b.guests) || 4;
10414
+ var newEntry$ = this.ingredient && this.recipe ?
10415
+ this.basketsService.basketEntryFromIngredient(this.ingredient.id, guests, true, item.id) :
10416
+ rxjs.of(this.basketEntry);
10417
+ return newEntry$.pipe(operators.map(function (newEntry) {
10418
+ var newBasketEntry = deepCloneWithClass(_this.basketEntry);
10419
+ newBasketEntry.quantityToAdd = newEntry.quantityToAdd;
10420
+ _this.replaceRelationship(newBasketEntry, item);
10421
+ return new AddIngredientFromReplaceAction(newBasketEntry, { item: item }, _this.basketsService, _this.basketEntriesService);
10422
+ }));
10423
+ };
10424
+ ReplaceItemService.prototype.listenToAddActionFailure = function (addAction, attemptedItem, basketEntry) {
10425
+ var _this = this;
10426
+ if (basketEntry === void 0) { basketEntry = this.basketEntry; }
10427
+ var isReplaceAdd = addAction instanceof AddIngredientFromReplaceAction;
10428
+ addAction.onTimeout.pipe(operators.take(1)).subscribe(function () {
10429
+ _this.handleAddTimeout(attemptedItem, basketEntry, isReplaceAdd);
10430
+ });
10431
+ addAction.onError.pipe(operators.take(1)).subscribe(function () {
10432
+ _this.clearReplaceLoadingState();
10433
+ });
10434
+ };
10228
10435
  /**
10229
- * Rolls back the replacement process by resetting loading states and
10230
- * restoring the previous selected item in the basket entry.
10436
+ * After a failed replace-add, the retailer already removed the old product but Mealz was never updated
10437
+ * (remove-from-replace is retailer-only). reloadBasket would re-fetch Mealz and still show the old product
10438
+ * as active. syncMealzBasketWithRetailerCart is too broad and risky while other actions may be pending.
10439
+ * A targeted remove-recipe aligns Mealz with the retailer for this ingredient only.
10231
10440
  */
10232
- ReplaceItemService.prototype.rollbackReplace = function () {
10441
+ ReplaceItemService.prototype.confirmMealzRemoveAfterReplaceAddFailure = function () {
10442
+ var _a;
10443
+ if (!((_a = this.ingredient) === null || _a === void 0 ? void 0 : _a.id)) {
10444
+ return;
10445
+ }
10446
+ var basketEntryToRemove = deepCloneWithClass(this.basketEntry);
10447
+ this.basketsService.removeIngredientFromRecipe(basketEntryToRemove, this.ingredient.id, 0).pipe(operators.take(1)).subscribe();
10448
+ };
10449
+ ReplaceItemService.prototype.handleAddTimeout = function (attemptedItem, basketEntry, isReplaceAdd) {
10450
+ if (basketEntry === void 0) { basketEntry = this.basketEntry; }
10451
+ if (isReplaceAdd === void 0) { isReplaceAdd = false; }
10452
+ if (isReplaceAdd) {
10453
+ this.confirmMealzRemoveAfterReplaceAddFailure();
10454
+ }
10455
+ this.clearReplaceLoadingState();
10456
+ this.replaceRelationship(basketEntry, attemptedItem);
10457
+ this.storeOutOfStockService.persistOutOfStock(basketEntry);
10458
+ this.contextRegistryService.drawerErrorToast.emit({
10459
+ code: UNAVAILABLE_PRODUCT_ADD_ERROR_CODE
10460
+ });
10461
+ };
10462
+ ReplaceItemService.prototype.clearReplaceLoadingState = function () {
10233
10463
  this.recipeDetailsService.ingredientToBasketLoading = false;
10234
10464
  this.recipeDetailsService.updateIngredientFromBasketLoading = false;
10235
10465
  this.replaceItemLoading$.next(undefined);
10236
- this.basketEntry.relationships['selected-item'].data = this.previousItem;
10237
- if (this.timeOutAction) {
10238
- this.timeOutAction();
10239
- }
10240
10466
  };
10241
10467
  /**
10242
10468
  * Creates a ParsedMethod for adding an ingredient to a basket.
@@ -10258,7 +10484,7 @@
10258
10484
  * Updates loading states, creates an AddIngredientAction for the new basket entry,
10259
10485
  * and adds it to the synchronization service's action list.
10260
10486
  */
10261
- ReplaceItemService.prototype.handleAddToBasketResponse = function (newEntry, searchString, eventTrace) {
10487
+ ReplaceItemService.prototype.handleAddToBasketResponse = function (newEntry, searchString, eventTrace, attemptedItem) {
10262
10488
  this.updateLoadingStates();
10263
10489
  var newBasketEntry = deepCloneWithClass(this.basketEntry);
10264
10490
  newBasketEntry.quantityToAdd = newEntry.quantityToAdd;
@@ -10269,19 +10495,11 @@
10269
10495
  previousItem: this.previousItem,
10270
10496
  searchString: searchString
10271
10497
  }, this.basketsService);
10272
- this.handleAddIngredientActionErrors(addIngredient);
10273
- // No need to send entry.added event, because it is done in the action
10498
+ this.handleAddIngredientActionErrors(addIngredient, attemptedItem);
10274
10499
  return rxjs.of(addIngredient);
10275
10500
  };
10276
- /**
10277
- * Listens for errors or timeouts in the given AddIngredientAction and
10278
- * calls rollbackReplace() when one is encountered.
10279
- */
10280
- ReplaceItemService.prototype.handleAddIngredientActionErrors = function (addIngredient) {
10281
- var _this = this;
10282
- rxjs.merge(addIngredient.onError, addIngredient.onTimeout).pipe(operators.take(1)).subscribe(function () {
10283
- _this.rollbackReplace();
10284
- });
10501
+ ReplaceItemService.prototype.handleAddIngredientActionErrors = function (addIngredient, attemptedItem) {
10502
+ this.listenToAddActionFailure(addIngredient, attemptedItem);
10285
10503
  };
10286
10504
  /**
10287
10505
  * Updates the loading states for ingredient and basket operations.
@@ -10326,7 +10544,7 @@
10326
10544
  */
10327
10545
  ReplaceItemService.prototype.addToBasket = function (item, searchString, eventTrace) {
10328
10546
  var _this = this;
10329
- return this.recipeDetailsService.canAddProductToBasket(this.getAddIngredientMethod(eventTrace, searchString), eventTrace, this.recipe).pipe(operators.filter(function (passed) { return passed; }), operators.switchMap(function () { return _this.basketsService.basketEntryFromIngredient(_this.ingredient.id, +_this.recipe.modifiedGuests, true, item.id); }), operators.switchMap(function (newEntry) { return _this.handleAddToBasketResponse(newEntry, searchString, eventTrace); }));
10547
+ return this.recipeDetailsService.canAddProductToBasket(this.getAddIngredientMethod(eventTrace, searchString), eventTrace, this.recipe).pipe(operators.filter(function (passed) { return passed; }), operators.switchMap(function () { return _this.basketsService.basketEntryFromIngredient(_this.ingredient.id, +_this.recipe.modifiedGuests, true, item.id); }), operators.switchMap(function (newEntry) { return _this.handleAddToBasketResponse(newEntry, searchString, eventTrace, item); }));
10330
10548
  };
10331
10549
  /**
10332
10550
  * Replaces the item in the given basket entry with the given item
@@ -10365,7 +10583,7 @@
10365
10583
  };
10366
10584
  return ReplaceItemService;
10367
10585
  }());
10368
- ReplaceItemService.ɵfac = function ReplaceItemService_Factory(t) { return new (t || ReplaceItemService)(i0__namespace.ɵɵinject(BasketsService), i0__namespace.ɵɵinject(BasketEntriesService), i0__namespace.ɵɵinject(ItemsService), i0__namespace.ɵɵinject(RecipeDetailsService), i0__namespace.ɵɵinject(BasketsSynchronizerService), i0__namespace.ɵɵinject(IgnoredIngredientsService), i0__namespace.ɵɵinject(RecipesService), i0__namespace.ɵɵinject(AnalyticsService)); };
10586
+ ReplaceItemService.ɵfac = function ReplaceItemService_Factory(t) { return new (t || ReplaceItemService)(i0__namespace.ɵɵinject(BasketsService), i0__namespace.ɵɵinject(BasketEntriesService), i0__namespace.ɵɵinject(ItemsService), i0__namespace.ɵɵinject(RecipeDetailsService), i0__namespace.ɵɵinject(BasketsSynchronizerService), i0__namespace.ɵɵinject(IgnoredIngredientsService), i0__namespace.ɵɵinject(RecipesService), i0__namespace.ɵɵinject(AnalyticsService), i0__namespace.ɵɵinject(StoreOutOfStockService), i0__namespace.ɵɵinject(ContextRegistryService)); };
10369
10587
  ReplaceItemService.ɵprov = i0__namespace.ɵɵdefineInjectable({ token: ReplaceItemService, factory: ReplaceItemService.ɵfac, providedIn: 'root' });
10370
10588
  (function () {
10371
10589
  (typeof ngDevMode === "undefined" || ngDevMode) && i0__namespace.ɵsetClassMetadata(ReplaceItemService, [{
@@ -10373,17 +10591,19 @@
10373
10591
  args: [{
10374
10592
  providedIn: 'root'
10375
10593
  }]
10376
- }], function () { return [{ type: BasketsService }, { type: BasketEntriesService }, { type: ItemsService }, { type: RecipeDetailsService }, { type: BasketsSynchronizerService }, { type: IgnoredIngredientsService }, { type: RecipesService }, { type: AnalyticsService }]; }, null);
10594
+ }], function () { return [{ type: BasketsService }, { type: BasketEntriesService }, { type: ItemsService }, { type: RecipeDetailsService }, { type: BasketsSynchronizerService }, { type: IgnoredIngredientsService }, { type: RecipesService }, { type: AnalyticsService }, { type: StoreOutOfStockService }, { type: ContextRegistryService }]; }, null);
10377
10595
  })();
10378
10596
 
10379
10597
  var RecipeDetailsProductsService = /** @class */ (function () {
10380
- function RecipeDetailsProductsService(basketsService, priceService, ignoredBasketsService, replaceItemService, contextRegistryService, basketSynchroService) {
10598
+ function RecipeDetailsProductsService(basketsService, priceService, ignoredBasketsService, replaceItemService, contextRegistryService, basketSynchroService, storeOutOfStockService) {
10599
+ var _this = this;
10381
10600
  this.basketsService = basketsService;
10382
10601
  this.priceService = priceService;
10383
10602
  this.ignoredBasketsService = ignoredBasketsService;
10384
10603
  this.replaceItemService = replaceItemService;
10385
10604
  this.contextRegistryService = contextRegistryService;
10386
10605
  this.basketSynchroService = basketSynchroService;
10606
+ this.storeOutOfStockService = storeOutOfStockService;
10387
10607
  this.productsByCategory = {
10388
10608
  toPickProducts: [],
10389
10609
  oftenIgnoredProducts: []
@@ -10394,6 +10614,10 @@
10394
10614
  this.productsLoading$ = new rxjs.BehaviorSubject(false);
10395
10615
  this.ingredientToBasketLoading$ = new rxjs.BehaviorSubject(false);
10396
10616
  this.updateIngredientFromBasketLoading$ = new rxjs.BehaviorSubject(false);
10617
+ this.storeOutOfStockService.outOfStockPersisted$.subscribe(function () {
10618
+ _this.products$.value.forEach(function (product) { return _this.applyOutOfStockFromStorage(product); });
10619
+ _this.emitProductsUpdate();
10620
+ });
10397
10621
  }
10398
10622
  RecipeDetailsProductsService.prototype.initDefaultState = function () {
10399
10623
  this.resetProductsByCategory();
@@ -10445,6 +10669,7 @@
10445
10669
  var basketEntry = product.basketEntry;
10446
10670
  var status = basketEntry.status;
10447
10671
  _this.updateIngredientIgnoredStatus(product);
10672
+ _this.applyOutOfStockFromStorage(product);
10448
10673
  _this.updateProductAvailabilityStatus(product);
10449
10674
  _this.computeProductPriceDetails(product, _this.priceService.getPriceFromBasketEntry(basketEntry));
10450
10675
  // Check if the product is often ignored based on basketEntryStatus
@@ -10489,6 +10714,7 @@
10489
10714
  if (currentProduct) {
10490
10715
  currentProduct.basketEntry.quantity = product.basketEntry.quantity;
10491
10716
  currentProduct.basketEntry.quantityToAdd = product.basketEntry.quantityToAdd;
10717
+ _this.applyOutOfStockFromStorage(currentProduct);
10492
10718
  if (currentProduct.price) {
10493
10719
  _this.computeProductPriceDetails(currentProduct, currentProduct.price);
10494
10720
  }
@@ -10509,6 +10735,17 @@
10509
10735
  }
10510
10736
  });
10511
10737
  };
10738
+ RecipeDetailsProductsService.prototype.emitProductsUpdate = function () {
10739
+ this.products$.next(__spread(this.products$.value));
10740
+ this.productsByCategory$.next(this.productsByCategory);
10741
+ this.ingredientToBasketLoading$.next(false);
10742
+ this.updateIngredientFromBasketLoading$.next(false);
10743
+ };
10744
+ RecipeDetailsProductsService.prototype.applyOutOfStockFromStorage = function (product) {
10745
+ if (this.storeOutOfStockService.isOutOfStockFromStorage(product.basketEntry)) {
10746
+ product.basketEntry.status = 'out_of_stock';
10747
+ }
10748
+ };
10512
10749
  RecipeDetailsProductsService.prototype.updateIngredientIgnoredStatus = function (product) {
10513
10750
  if (this.ignoredBasketsService.isIngredientIgnored(product.ingredient.id)) {
10514
10751
  product.basketEntry.status = 'ignored';
@@ -10517,7 +10754,7 @@
10517
10754
  RecipeDetailsProductsService.prototype.updateProductAvailabilityStatus = function (product) {
10518
10755
  var basketEntry = product.basketEntry;
10519
10756
  var id = basketEntry.id, selectedItem = basketEntry.selectedItem, recipesIds = basketEntry.recipesIds, status = basketEntry.status;
10520
- if (['often_deleted', 'deleted', 'ignored'].includes(status)) {
10757
+ if (['often_deleted', 'deleted', 'ignored', 'out_of_stock'].includes(status)) {
10521
10758
  return;
10522
10759
  }
10523
10760
  if (id === '-1') {
@@ -10570,7 +10807,7 @@
10570
10807
  };
10571
10808
  return RecipeDetailsProductsService;
10572
10809
  }());
10573
- RecipeDetailsProductsService.ɵfac = function RecipeDetailsProductsService_Factory(t) { return new (t || RecipeDetailsProductsService)(i0__namespace.ɵɵinject(BasketsService), i0__namespace.ɵɵinject(PriceService), i0__namespace.ɵɵinject(IgnoredIngredientsService), i0__namespace.ɵɵinject(ReplaceItemService), i0__namespace.ɵɵinject(ContextRegistryService), i0__namespace.ɵɵinject(BasketsSynchronizerService)); };
10810
+ RecipeDetailsProductsService.ɵfac = function RecipeDetailsProductsService_Factory(t) { return new (t || RecipeDetailsProductsService)(i0__namespace.ɵɵinject(BasketsService), i0__namespace.ɵɵinject(PriceService), i0__namespace.ɵɵinject(IgnoredIngredientsService), i0__namespace.ɵɵinject(ReplaceItemService), i0__namespace.ɵɵinject(ContextRegistryService), i0__namespace.ɵɵinject(BasketsSynchronizerService), i0__namespace.ɵɵinject(StoreOutOfStockService)); };
10574
10811
  RecipeDetailsProductsService.ɵprov = i0__namespace.ɵɵdefineInjectable({ token: RecipeDetailsProductsService, factory: RecipeDetailsProductsService.ɵfac, providedIn: 'root' });
10575
10812
  (function () {
10576
10813
  (typeof ngDevMode === "undefined" || ngDevMode) && i0__namespace.ɵsetClassMetadata(RecipeDetailsProductsService, [{
@@ -10578,7 +10815,7 @@
10578
10815
  args: [{
10579
10816
  providedIn: 'root'
10580
10817
  }]
10581
- }], function () { return [{ type: BasketsService }, { type: PriceService }, { type: IgnoredIngredientsService }, { type: ReplaceItemService }, { type: ContextRegistryService }, { type: BasketsSynchronizerService }]; }, null);
10818
+ }], function () { return [{ type: BasketsService }, { type: PriceService }, { type: IgnoredIngredientsService }, { type: ReplaceItemService }, { type: ContextRegistryService }, { type: BasketsSynchronizerService }, { type: StoreOutOfStockService }]; }, null);
10582
10819
  })();
10583
10820
 
10584
10821
  var RecipeDetailsProductsPlannerService = /** @class */ (function () {
@@ -10953,7 +11190,7 @@
10953
11190
  };
10954
11191
  var NewRecipeDetailsService = /** @class */ (function () {
10955
11192
  /** ********* END VARIABLES FROM COMPONENT ********* **/
10956
- function NewRecipeDetailsService(analyticsService, basketsService, recipeService, pendingBasketMethodsService, posService, basketSynchroService, priceService, contextRegistryService, sponsorBlocksService, recipesService, seoService, storeLocatorService, recipeDetailsProductsService, recipeDetailsProductsPlannerService, ignoredBasketsService, locale) {
11193
+ function NewRecipeDetailsService(analyticsService, basketsService, recipeService, pendingBasketMethodsService, posService, basketSynchroService, priceService, contextRegistryService, sponsorBlocksService, recipesService, seoService, storeLocatorService, recipeDetailsProductsService, recipeDetailsProductsPlannerService, ignoredBasketsService, storeOutOfStockService, locale) {
10957
11194
  this.analyticsService = analyticsService;
10958
11195
  this.basketsService = basketsService;
10959
11196
  this.recipeService = recipeService;
@@ -10969,6 +11206,7 @@
10969
11206
  this.recipeDetailsProductsService = recipeDetailsProductsService;
10970
11207
  this.recipeDetailsProductsPlannerService = recipeDetailsProductsPlannerService;
10971
11208
  this.ignoredBasketsService = ignoredBasketsService;
11209
+ this.storeOutOfStockService = storeOutOfStockService;
10972
11210
  this.locale = locale;
10973
11211
  this.totalProducts$ = new rxjs.BehaviorSubject(0);
10974
11212
  this.allIngredientsToBasketLoading$ = new rxjs.BehaviorSubject(false);
@@ -11117,7 +11355,7 @@
11117
11355
  return _this.setupRecipe(recipeDisplay.recipe);
11118
11356
  }
11119
11357
  else {
11120
- _this.initDefaultState(); // This can also reset the state when no recipe is displayed
11358
+ _this.initDefaultState();
11121
11359
  _this.checkIfPosIsInitializedAndValid();
11122
11360
  return rxjs.of(null);
11123
11361
  }
@@ -11125,7 +11363,7 @@
11125
11363
  };
11126
11364
  NewRecipeDetailsService.prototype.listenToBasket = function () {
11127
11365
  var _this = this;
11128
- this.subscriptions.push(this.basketsService._basket$.pipe(operators.filter(function () { return !!_this.recipe; }), operators.switchMap(function () {
11366
+ this.subscriptions.push(this.basketsService._basket$.pipe(operators.filter(function () { var _a; return !!((_a = _this.recipesService.displayedRecipe$.value) === null || _a === void 0 ? void 0 : _a.recipe); }), operators.switchMap(function () {
11129
11367
  if (_this.recipesService.isRecipeDetailsFromPlanner) {
11130
11368
  return _this.loadPlannerProducts();
11131
11369
  }
@@ -11190,14 +11428,14 @@
11190
11428
  _this.basketsService.updateRecipesInBasket([_this.recipe.id], _this.recipe.modifiedGuests, eventTrace, true);
11191
11429
  _this.recipeDetailsProductsService.getIngredientsBasketEntries(_this.recipe.ingredients, _this.recipe.modifiedGuests, false).pipe(operators.take(1)).subscribe(function (products) {
11192
11430
  if (inBasket) {
11193
- var filteredProducts = products.filter(function (_a) {
11194
- var basketEntry = _a.basketEntry;
11431
+ var filteredProducts = products.filter(function (_b) {
11432
+ var basketEntry = _b.basketEntry;
11195
11433
  return basketEntry.status === 'active' &&
11196
11434
  basketEntry.id !== '-1' &&
11197
11435
  basketEntry.recipesIds.includes(+_this.recipe.id);
11198
11436
  });
11199
- var updateActions = filteredProducts.map(function (_a) {
11200
- var basketEntry = _a.basketEntry;
11437
+ var updateActions = filteredProducts.map(function (_b) {
11438
+ var basketEntry = _b.basketEntry;
11201
11439
  var newBasketEntry = deepCloneWithClass(basketEntry); // Clone for immutability
11202
11440
  return new UpdateRecipeFromGuestAction(newBasketEntry, {
11203
11441
  eventTrace: eventTrace,
@@ -11235,20 +11473,34 @@
11235
11473
  productsToAdd_1.push(re);
11236
11474
  }
11237
11475
  }); });
11238
- var addRecipes = productsToAdd_1.map(function (re) { return new AddRecipeAction(re.entry, {
11476
+ var addRecipes_1 = productsToAdd_1.map(function (re) { return new AddRecipeAction(re.entry, {
11239
11477
  recipeId: _this.recipe.id,
11240
11478
  ingredientId: re.ingredientId,
11241
11479
  eventTrace: eventTrace
11242
11480
  }, _this.basketsService); });
11243
- var onTimeouts = addRecipes.map(function (action) { return action.onTimeout; });
11244
- var onErrors = addRecipes.map(function (action) { return action.onError; });
11245
- var onSuccesses = addRecipes.map(function (action) { return action.onSuccess; });
11246
- if (addRecipes.length > 0) {
11247
- rxjs.merge.apply(void 0, __spread(onSuccesses, onTimeouts, onErrors)).pipe(operators.take(addRecipes.length)) // Take the same number of emissions as actions
11481
+ var failedAddCount_1 = 0;
11482
+ productsToAdd_1.forEach(function (productToAdd, index) {
11483
+ addRecipes_1[index].onTimeout.pipe(operators.take(1)).subscribe(function () {
11484
+ failedAddCount_1 += 1;
11485
+ _this.storeOutOfStockService.persistOutOfStock(productToAdd.entry);
11486
+ });
11487
+ });
11488
+ var onTimeouts = addRecipes_1.map(function (action) { return action.onTimeout; });
11489
+ var onErrors = addRecipes_1.map(function (action) { return action.onError; });
11490
+ var onSuccesses = addRecipes_1.map(function (action) { return action.onSuccess; });
11491
+ if (addRecipes_1.length > 0) {
11492
+ rxjs.merge.apply(void 0, __spread(onSuccesses, onTimeouts, onErrors)).pipe(operators.take(addRecipes_1.length)) // Take the same number of emissions as actions
11248
11493
  .subscribe({
11249
- complete: function () { return _this.allIngredientsToBasketLoading$.next(false); }
11494
+ complete: function () {
11495
+ _this.allIngredientsToBasketLoading$.next(false);
11496
+ if (failedAddCount_1 > 0) {
11497
+ _this.contextRegistryService.drawerErrorToast.emit({
11498
+ code: unavailableProductAddErrorCode(failedAddCount_1)
11499
+ });
11500
+ }
11501
+ }
11250
11502
  });
11251
- _this.basketSynchroService.addToActionList(addRecipes);
11503
+ _this.basketSynchroService.addToActionList(addRecipes_1);
11252
11504
  }
11253
11505
  else {
11254
11506
  _this.allIngredientsToBasketLoading$.next(false);
@@ -11278,7 +11530,13 @@
11278
11530
  searchString: null
11279
11531
  }, _this.basketsService); }), operators.tap(function (addIngredient) {
11280
11532
  _this.basketSynchroService.addToActionList([addIngredient]);
11281
- }), operators.switchMap(function (addIngredient) { return rxjs.merge(addIngredient.onError, addIngredient.onTimeout).pipe(operators.take(1), operators.tap(function () { return _this.recipeDetailsProductsService.ingredientToBasketLoading$.next(false); })); })).subscribe());
11533
+ }), operators.switchMap(function (addIngredient) { return rxjs.merge(addIngredient.onTimeout.pipe(operators.take(1), operators.tap(function () {
11534
+ _this.storeOutOfStockService.persistOutOfStock(product.basketEntry);
11535
+ _this.recipeDetailsProductsService.ingredientToBasketLoading$.next(false);
11536
+ _this.contextRegistryService.drawerErrorToast.emit({
11537
+ code: unavailableProductAddErrorCode(1)
11538
+ });
11539
+ })), addIngredient.onError.pipe(operators.take(1), operators.tap(function () { return _this.recipeDetailsProductsService.ingredientToBasketLoading$.next(false); }))); })).subscribe());
11282
11540
  };
11283
11541
  NewRecipeDetailsService.prototype.ingredientRemoved = function (ingredient, eventTrace) {
11284
11542
  var _this = this;
@@ -11361,7 +11619,7 @@
11361
11619
  };
11362
11620
  return NewRecipeDetailsService;
11363
11621
  }());
11364
- NewRecipeDetailsService.ɵfac = function NewRecipeDetailsService_Factory(t) { return new (t || NewRecipeDetailsService)(i0__namespace.ɵɵinject(AnalyticsService), i0__namespace.ɵɵinject(BasketsService), i0__namespace.ɵɵinject(RecipesService), i0__namespace.ɵɵinject(PendingBasketMethodsService), i0__namespace.ɵɵinject(PointOfSalesService), i0__namespace.ɵɵinject(BasketsSynchronizerService), i0__namespace.ɵɵinject(PriceService), i0__namespace.ɵɵinject(ContextRegistryService), i0__namespace.ɵɵinject(SponsorBlockService), i0__namespace.ɵɵinject(RecipesService), i0__namespace.ɵɵinject(SeoService), i0__namespace.ɵɵinject(StoreLocatorService), i0__namespace.ɵɵinject(RecipeDetailsProductsService), i0__namespace.ɵɵinject(RecipeDetailsProductsPlannerService), i0__namespace.ɵɵinject(IgnoredIngredientsService), i0__namespace.ɵɵinject(i0.LOCALE_ID)); };
11622
+ NewRecipeDetailsService.ɵfac = function NewRecipeDetailsService_Factory(t) { return new (t || NewRecipeDetailsService)(i0__namespace.ɵɵinject(AnalyticsService), i0__namespace.ɵɵinject(BasketsService), i0__namespace.ɵɵinject(RecipesService), i0__namespace.ɵɵinject(PendingBasketMethodsService), i0__namespace.ɵɵinject(PointOfSalesService), i0__namespace.ɵɵinject(BasketsSynchronizerService), i0__namespace.ɵɵinject(PriceService), i0__namespace.ɵɵinject(ContextRegistryService), i0__namespace.ɵɵinject(SponsorBlockService), i0__namespace.ɵɵinject(RecipesService), i0__namespace.ɵɵinject(SeoService), i0__namespace.ɵɵinject(StoreLocatorService), i0__namespace.ɵɵinject(RecipeDetailsProductsService), i0__namespace.ɵɵinject(RecipeDetailsProductsPlannerService), i0__namespace.ɵɵinject(IgnoredIngredientsService), i0__namespace.ɵɵinject(StoreOutOfStockService), i0__namespace.ɵɵinject(i0.LOCALE_ID)); };
11365
11623
  NewRecipeDetailsService.ɵprov = i0__namespace.ɵɵdefineInjectable({ token: NewRecipeDetailsService, factory: NewRecipeDetailsService.ɵfac, providedIn: 'root' });
11366
11624
  (function () {
11367
11625
  (typeof ngDevMode === "undefined" || ngDevMode) && i0__namespace.ɵsetClassMetadata(NewRecipeDetailsService, [{
@@ -11370,7 +11628,7 @@
11370
11628
  providedIn: 'root'
11371
11629
  }]
11372
11630
  }], function () {
11373
- return [{ type: AnalyticsService }, { type: BasketsService }, { type: RecipesService }, { type: PendingBasketMethodsService }, { type: PointOfSalesService }, { type: BasketsSynchronizerService }, { type: PriceService }, { type: ContextRegistryService }, { type: SponsorBlockService }, { type: RecipesService }, { type: SeoService }, { type: StoreLocatorService }, { type: RecipeDetailsProductsService }, { type: RecipeDetailsProductsPlannerService }, { type: IgnoredIngredientsService }, { type: undefined, decorators: [{
11631
+ return [{ type: AnalyticsService }, { type: BasketsService }, { type: RecipesService }, { type: PendingBasketMethodsService }, { type: PointOfSalesService }, { type: BasketsSynchronizerService }, { type: PriceService }, { type: ContextRegistryService }, { type: SponsorBlockService }, { type: RecipesService }, { type: SeoService }, { type: StoreLocatorService }, { type: RecipeDetailsProductsService }, { type: RecipeDetailsProductsPlannerService }, { type: IgnoredIngredientsService }, { type: StoreOutOfStockService }, { type: undefined, decorators: [{
11374
11632
  type: i0.Inject,
11375
11633
  args: [i0.LOCALE_ID]
11376
11634
  }] }];
@@ -11446,7 +11704,8 @@
11446
11704
  },
11447
11705
  eventSent$: this.analyticsService.eventEmitter,
11448
11706
  setAbTestKey: function (key) { return _this.analyticsService.setAbTestKey(key); },
11449
- attachRecipeCardShowTracking: attachRecipeCardShowTracking
11707
+ attachRecipeCardShowTracking: attachRecipeCardShowTracking,
11708
+ attachProductShowTracking: attachProductShowTracking
11450
11709
  },
11451
11710
  basket: {
11452
11711
  basketIsReady$: this.basketsService.basketStats$.pipe(operators.skipWhile(function (stats) { return !stats; }), operators.take(1), operators.map(function () { return true; })),
@@ -11760,6 +12019,9 @@
11760
12019
  config: {
11761
12020
  logLevel: this.logLevel$
11762
12021
  },
12022
+ drawer: {
12023
+ errorToast: function () { return _this.contextRegistryService.drawerErrorToast; }
12024
+ },
11763
12025
  planner: {
11764
12026
  itemToReplace$: this.recipeDetailsProductsPlannerService.itemToReplace$,
11765
12027
  itemsWithPricesList$: this.recipeDetailsProductsPlannerService.itemsWithPricesList$,
@@ -12156,6 +12418,7 @@
12156
12418
  if (!recipeId) {
12157
12419
  return rxjs.of(void 0);
12158
12420
  }
12421
+ ProductShowTracker.getInstance().clearForRecipe(recipeId);
12159
12422
  return this.recipesService.get(recipeId, { include: ['ingredients', 'recipe-steps', 'recipe-type', 'sponsors', 'tags', 'recipe-provider'] })
12160
12423
  .pipe(operators.take(1), operators.switchMap(function (recipe) {
12161
12424
  if (guests != null) {
@@ -16109,10 +16372,17 @@
16109
16372
  var _this = this;
16110
16373
  this.subscriptions.push(this.recipeDetailsService.productsByCategory$.subscribe(function () { return _this.isProductSponsored(); }), this.executeActionFromStorage(), this.loadOnRecipesActions(), this.loadOnIngredientsUpdate(), this.updateIngredientQuantityOnRecipeChange(), this.productIsBeingAdded(), this.scrollBackOnProductReplaced());
16111
16374
  this.getHeaderHeight();
16375
+ this.productShowTrackingHandle = attachProductShowTracking({
16376
+ element: this.elementRef.nativeElement,
16377
+ product: this.product,
16378
+ recipeId: this.recipeDetailsService.recipe.id,
16379
+ analyticsPath: this.path || undefined
16380
+ });
16112
16381
  };
16113
16382
  ProductCardComponent.prototype.ngOnDestroy = function () {
16114
- var _a;
16115
- (_a = this.subscriptions) === null || _a === void 0 ? void 0 : _a.forEach(function (sub) { return sub.unsubscribe(); });
16383
+ var _a, _b;
16384
+ (_a = this.productShowTrackingHandle) === null || _a === void 0 ? void 0 : _a.disconnect();
16385
+ (_b = this.subscriptions) === null || _b === void 0 ? void 0 : _b.forEach(function (sub) { return sub.unsubscribe(); });
16116
16386
  };
16117
16387
  ProductCardComponent.prototype.scrollBackOnProductReplaced = function () {
16118
16388
  var _this = this;
@@ -17065,12 +17335,14 @@
17065
17335
  }
17066
17336
  };
17067
17337
  BasketPreviewProductComponent.prototype.removeProduct = function () {
17338
+ var _a;
17068
17339
  this.analyticsService.sendEvent('entry.deleted', this.path, {
17069
17340
  entry_name: this.basketEntry.name,
17070
17341
  item_id: this.basketEntry.selectedItem.id,
17071
17342
  ext_item_id: this.basketEntry.selectedItem.attributes['ext-id'],
17072
17343
  item_ean: this.basketEntry.selectedItem.ean,
17073
17344
  product_quantity: this.basketEntry.quantity.toString(),
17345
+ product_base_price: ((_a = this.basketEntry.price) !== null && _a !== void 0 ? _a : 0).toString(),
17074
17346
  recipe_id: this.basketEntry.recipesIds.length > 0 ? this.basketEntry.recipesIds[0].toString() : null
17075
17347
  });
17076
17348
  var deletedBasketEntry = deepCloneWithClass(this.basketEntry);