Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ algolia.label.preference.enable.help=Enable the SFRA or SiteGenesis cartridge on
algolia.label.preference.applicationid.help=The Application ID is the unique identifier of your Algolia application. It is available in the API Keys section of your Algolia Dashboard.
algolia.label.preference.searchkey.help=The Search API key is used to perform search queries. It is available in the API Keys section of your Algolia Dashboard.
algolia.label.preference.adminkey.help=The Write API Key is used to perform indexing operations. It is available in the API Keys section of your Algolia Dashboard.
algolia.label.preference.instock.help=The InStock Threshold is used to determine if a product is in stock or not. If the stock level is below the threshold, the product is considered out of stock. The default value is 0,0.
algolia.label.preference.instock.help=The InStock Threshold is used to determine if a product is in stock or not. If the stock level is below the threshold, the product is considered out of stock (i.e. there should be at least this many products in the inventory in order for a product to be considered in-stock). The default value is 1.
algolia.label.preference.custom.help=The Additional Product Attributes is a comma-separated list of product attributes that will be indexed in Algolia. The default value is empty.
algolia.label.preference.indexprefix.help=By default the index name generated by the system looks like this: <hostname>__<siteID>__<"product" | "category">__<locale>. Setting this preference replaces the first two segments, the final index name becoming '<Algolia_IndexPrefix>__<"product" | "category">__<locale>'.
algolia.label.preference.recordmodel.help='Variation Product': create one record per variation product. 'Base Product': create one record per base product, containing all variants in a 'variants' attribute.
Expand All @@ -45,7 +45,7 @@ algolia.label.preference.enablessr.help=Server-side rendering for SFRA's CLP (ca
algolia.label.preference.enablecontentsearch.help=Enable Content Search on the storefront
algolia.label.preference.enablerecommend.help=Enable Algolia Recommend on the SFRA storefront cartridge. See the documentation for additional setup steps
algolia.label.preference.enablePricingLazyLoad.help=When enabled, prices are fetched from SFCC when search results are displayed. Permits to display the most up-to-date promotions without having to index them.
algolia.label.preference.indexoutofstock.help=Index out of stock products. When disabled, only in-stock (ATS higher than the InStock Threshold) products are indexed.
algolia.label.preference.indexoutofstock.help=Index out of stock products. When disabled, only in-stock (ATS higher than or equal to the InStock Threshold) products are indexed.
algolia.label.preference.enableRealTimeInventoryHook.help=Sends inventory updates to Algolia in real-time after each order.

# v2 job report table
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ const productFilterConfig = {
// STOCK-RELATED SETTINGS (Managed via Business Manager UI)
// ========================================================================
//
// The following stock-related settings are already configurable via
// The following stock-related settings are already configurable via
// Business Manager UI under:
// Administration > Site Preferences > Custom Site Preferences > Algolia
//
// 1. InStockThreshold:
// 1. InStockThreshold:
// - Controls the minimum ATS value to consider a product in stock
// - Default: 0
// - Default: 1
// - Used by productFilter.isInStock() function
//
// 2. IndexOutOfStock:
Expand All @@ -46,4 +46,4 @@ const productFilterConfig = {
// and do not need to be configured in this file.
};

module.exports = productFilterConfig;
module.exports = productFilterConfig;
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,13 @@
// if (product.optionProduct) return false;
// Do not include bundled product
if (product.bundled && !(product.priceModel && product.priceModel.price && product.priceModel.price.available)) return false;

// Check online status
if (!isOnline(product)) return false;

// Check searchable status
if (!isSearchable(product)) return false;

// Check if product has at least one online category
// Note: In SFCC, variant products don't have their own categories - getOnlineCategories() returns empty for variants
// We must check categories on the master product instead
Expand All @@ -82,7 +82,7 @@
} else {
if (!hasOnlineCategory(product)) return false;
}

return true;
}

Expand All @@ -101,24 +101,50 @@
// even if one variant is in stock, we consider the product as in stock
if (product.master || product.variationGroup) {
const variantsIt = product.variants.iterator();

while (variantsIt.hasNext()) {
let variant = variantsIt.next();
let variantAvailabilityModel = variant.getAvailabilityModel();

if (variantAvailabilityModel) {
let variantInvRecord = variantAvailabilityModel.getInventoryRecord();

if (variantInvRecord) {
let variantAtsValue = variantInvRecord.getATS().getValue();
if (variantAtsValue >= threshold) {
let variantATSValue = variantInvRecord.getATS().getValue();

if (variantATSValue > 0 && variantATSValue >= threshold) { // comparing to zero explicitly so that a threshold of 0 wouldn't return true
return true;
}
}
}
}

return false;
}

var invRecord = availabilityModel.getInventoryRecord();
var atsValue = invRecord ? invRecord.getATS().getValue() : 0;
return atsValue >= threshold;

return (atsValue > 0 && atsValue >= threshold);
Copy link

Copilot AI Dec 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The condition atsValue > 0 && atsValue >= threshold is redundant when threshold >= 1. Since the default is now 1 and there's a fallback of || 1, consider simplifying to atsValue >= threshold.

Copilot uses AI. Check for mistakes.
}

/**
* Checks if at least one variant in a variation model is in stock.
* @param {dw.catalog.ProductVariationModel} variationModel
* @param {Number} threshold
* @returns {Boolean} whether at least one of the variants is in stock
*/
function isCustomVariationGroupInStock(variationModel, threshold) {

Check failure on line 137 in cartridges/int_algolia/cartridge/scripts/algolia/filters/productFilter.js

View workflow job for this annotation

GitHub Actions / unit-tests-and-lints

'isCustomVariationGroupInStock' is defined but never used
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's keep the introduction of this helper in the dedicated feature branch

Copy link
Collaborator Author

@ede-somogyi-algolia ede-somogyi-algolia Dec 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't, as the changes to the would-be helper are here as well which would be lost if I removed this from here. This will nicely fall into place once the other pull requests are merged into develop and this branch is rebased. The linter error will also be gone.

Copy link
Collaborator

@sbellone sbellone Dec 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as the changes to the would-be helper are here as well which would be lost if I removed this from here

Do "the changes" refer to the variantATSValue > 0 addition? I think it can be easily added on the attribute-slicing feature branch.

const variantsIt = variationModel.getSelectedVariants().iterator();
while (variantsIt.hasNext()) {
let variant = variantsIt.next();
let variantATSValue = variant.getAvailabilityModel().getInventoryRecord().getATS().getValue();
if (variantATSValue > 0 && variantATSValue >= threshold) {
return true;
}
}

return false;
}

/**
Expand All @@ -133,14 +159,18 @@
function isInStoreStock(product, storeId, threshold) {
const StoreMgr = require('dw/catalog/StoreMgr');
let store = StoreMgr.getStore(storeId);

if (!store || !store.inventoryList) {
return false;
}

let storeInventory = store.inventoryList;
let inventoryRecord = storeInventory.getRecord(product.ID);
if (inventoryRecord && inventoryRecord.ATS.value && inventoryRecord.ATS.value >= threshold) {

if (inventoryRecord && inventoryRecord.ATS.value && inventoryRecord.ATS.value >= threshold && inventoryRecord.ATS.value > 0) { // comparing to zero explicitly so that a threshold of 0 wouldn't return true
return true;
}

return false;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ let algoliaData = require('*/cartridge/scripts/algolia/lib/algoliaData');
function generateAlgoliaOperations(productConfig) {
let algoliaOperations = [];
let siteLocales = Site.getCurrent().getAllowedLocales();

for (let i = 0; i < siteLocales.size(); i++) {
let locale = siteLocales[i];
let indexName = algoliaData.calculateIndexName('products', locale);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ try {
} catch (e) { // eslint-disable-line no-unused-vars
}

var ALGOLIA_IN_STOCK_THRESHOLD = algoliaData.getPreference('InStockThreshold');
var ALGOLIA_IN_STOCK_THRESHOLD = algoliaData.getPreference('InStockThreshold') || 1;
var INDEX_OUT_OF_STOCK = algoliaData.getPreference('IndexOutOfStock');
var ATTRIBUTE_LIST = algoliaData.getSetOfArray('AdditionalAttributes');
const stores = [];
Expand Down Expand Up @@ -378,13 +378,7 @@ var aggregatedValueHandlers = {
return pricebooks;
},
in_stock: function (product) {
let inStock = productFilter.isInStock(product, ALGOLIA_IN_STOCK_THRESHOLD);

if (!inStock && !INDEX_OUT_OF_STOCK) {
return undefined;
}

return inStock;
return productFilter.isInStock(product, ALGOLIA_IN_STOCK_THRESHOLD);
},
image_groups: function (product) {
var imageGroupsArr = [];
Expand Down Expand Up @@ -498,7 +492,7 @@ var aggregatedValueHandlers = {
var storeElInventory = storeEl.storeInventory;
if (storeElInventory) {
var inventoryRecord = storeElInventory.getRecord(product.ID);
if (inventoryRecord && inventoryRecord.ATS.value && inventoryRecord.ATS.value >= ALGOLIA_IN_STOCK_THRESHOLD) {
if (inventoryRecord && inventoryRecord.ATS.value && inventoryRecord.ATS.value >= ALGOLIA_IN_STOCK_THRESHOLD && inventoryRecord.ATS.value > 0) { // comparing to zero explicitly so that a threshold of 0 wouldn't return true
storeArray.push(storeEl.id);
}
}
Expand All @@ -525,7 +519,7 @@ var aggregatedValueHandlers = {
*/
function algoliaLocalizedProduct(parameters) {
const product = parameters.product;
const attributeList = parameters.attributeList;
const attributeList = parameters.attributeList || []
const baseModel = parameters.baseModel;

request.setLocale(parameters.locale || 'default');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ var algoliaProductConfig = require('*/cartridge/scripts/algolia/lib/algoliaProdu
var productModelCustomizer = require('*/cartridge/scripts/algolia/customization/productModelCustomizer');
var ObjectHelper = require('*/cartridge/scripts/algolia/helper/objectHelper');

const ALGOLIA_IN_STOCK_THRESHOLD = algoliaData.getPreference('InStockThreshold');
const ALGOLIA_IN_STOCK_THRESHOLD = algoliaData.getPreference('InStockThreshold') || 1;

/**
* Get the lowest promotional price for product
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ exports.beforeStep = function(parameters, stepExecution) {
CPObjectIterator = require('*/cartridge/scripts/algolia/helper/CPObjectIterator');
AlgoliaJobReport = require('*/cartridge/scripts/algolia/helper/AlgoliaJobReport');

ALGOLIA_IN_STOCK_THRESHOLD = algoliaData.getPreference('InStockThreshold');
// Algolia preferences
ALGOLIA_IN_STOCK_THRESHOLD = algoliaData.getPreference('InStockThreshold') || 1;
INDEX_OUT_OF_STOCK = algoliaData.getPreference('IndexOutOfStock');

try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,9 @@ exports.beforeStep = function(parameters, stepExecution) {
productFilter = require('*/cartridge/scripts/algolia/filters/productFilter');
algoliaProductConfig = require('*/cartridge/scripts/algolia/lib/algoliaProductConfig');
AlgoliaJobReport = require('*/cartridge/scripts/algolia/helper/AlgoliaJobReport');
ALGOLIA_IN_STOCK_THRESHOLD = algoliaData.getPreference('InStockThreshold');

// Algolia preferences
ALGOLIA_IN_STOCK_THRESHOLD = algoliaData.getPreference('InStockThreshold') || 1;
INDEX_OUT_OF_STOCK = algoliaData.getPreference('IndexOutOfStock');

try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ function createProductConfig(product, recordModel, additionalAttributes) {
* @returns {dw.system.Status} Status
*/
exports.inventoryUpdate = function (order) {
let ALGOLIA_IN_STOCK_THRESHOLD = algoliaData.getPreference('InStockThreshold');
let ALGOLIA_IN_STOCK_THRESHOLD = algoliaData.getPreference('InStockThreshold') || 1;
let RECORD_MODEL = algoliaData.getPreference('RecordModel');
let additionalAttributes = algoliaData.getSetOfArray('AdditionalAttributes');

Expand Down
2 changes: 1 addition & 1 deletion metadata/algolia/meta/system-objecttype-extensions.xml
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@
<type>double</type>
<mandatory-flag>true</mandatory-flag>
<externally-managed-flag>false</externally-managed-flag>
<default-value>0.0</default-value>
<default-value>1.0</default-value>
</attribute-definition>

<attribute-definition attribute-id="Algolia_AdditionalAttributes">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ exports[`Order Helper generateAlgoliaOperations should create operations for all
"body": {
"in_stock": true,
"objectID": "25592581M",
"variants": [],
"variants": [
algoliaLocalizedProduct {
"variantID": "701644031206M",
},
],
},
"indexName": "test_index___products__default",
},
Expand All @@ -16,7 +20,11 @@ exports[`Order Helper generateAlgoliaOperations should create operations for all
"body": {
"in_stock": true,
"objectID": "25592581M",
"variants": [],
"variants": [
algoliaLocalizedProduct {
"variantID": "701644031206M",
},
],
},
"indexName": "test_index___products__fr",
},
Expand All @@ -25,7 +33,11 @@ exports[`Order Helper generateAlgoliaOperations should create operations for all
"body": {
"in_stock": true,
"objectID": "25592581M",
"variants": [],
"variants": [
algoliaLocalizedProduct {
"variantID": "701644031206M",
},
],
},
"indexName": "test_index___products__en",
},
Expand Down
22 changes: 19 additions & 3 deletions test/unit/int_algolia/scripts/algolia/helper/orderHelper.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,15 @@ const collectionHelper = require('../../../../../mocks/helpers/collectionHelper'
describe('Order Helper', function () {
test('generateAlgoliaOperations should create operations for all locales', function () {
// Arrange
const masterProduct = new MasterVariantMock();
const masterProduct = new MasterVariantMock({
variants: [
new VariantMock({
ID: '701644031206M',
variationAttributes: { color: 'JJB52A0', size: '004' },
ats: 5,
}),
]
});

const productConfig = {
product: masterProduct,
Expand Down Expand Up @@ -40,7 +48,15 @@ describe('Order Helper', function () {

test('generateAlgoliaOperations should handle stock status correctly', function () {
// Arrange
const masterProduct = new MasterVariantMock();
const masterProduct = new MasterVariantMock({
variants: [
new VariantMock({
ID: '701644031206M',
variationAttributes: { color: 'JJB52A0', size: '004' },
ats: 5,
}),
]
});

const productConfig = {
product: masterProduct,
Expand Down Expand Up @@ -109,4 +125,4 @@ describe('Order Helper', function () {
// Match snapshot
expect(operations).toMatchSnapshot();
});
});
});
Loading