Skip to content

Instantly share code, notes, and snippets.

@hnestmann
Created December 9, 2022 07:39
Show Gist options
  • Save hnestmann/0d7cf86ae311d4a17aa70f48df41081d to your computer and use it in GitHub Desktop.
Save hnestmann/0d7cf86ae311d4a17aa70f48df41081d to your computer and use it in GitHub Desktop.
Job that randomizes prices to simultate operational price changes
'use strict';
/**
* Job Step Type that calculates random price changes
*
* - Covers ~25% of all products (search hits) per job run
* - Throws the dice to generate +/- 5-30% price changes
*/
const Site = require('dw/system/Site');
const File = require('dw/io/File');
const FileWriter = require('dw/io/FileWriter');
const Currency = require('dw/util/Currency');
const StringUtils = require('dw/util/StringUtils');
let configs;
/**
* Generates and returns a Pricebook Header String
*
* @param {string} priceBookId
* @param {string} currency
* @returns {string}
*/
function getPriceBookHeader(priceBookId, currency) {
return `<?xml version="1.0" encoding="UTF-8"?>
<pricebooks xmlns="http://www.demandware.com/xml/impex/pricebook/2006-10-31">
<pricebook>
<header pricebook-id="${priceBookId}">
<currency>${currency}</currency>
<display-name xml:lang="x-default">Historic Prices</display-name>
<online-flag>true</online-flag>
</header>
<price-tables>
`;
}
/**
* Returns a Pricebook Footer string
*
* @returns {String}
*/
function getPriceBookFooter() {
return ` </price-tables>
</pricebook>
</pricebooks>`;
}
/**
* Generates a Price Table entry for Product IDs, setting the given price amount
*
* @param {string} price Target Price
* @param {string} productsToWrite List of Product IDs to generate the entries for
* @returns {string}
*/
function getPriceXml(price, productsToWrite) {
let output = '';
productsToWrite.forEach(function (productID) {
output += `
<price-table product-id="${productID}">
<amount quantity="1">${price}</amount>
</price-table>` + `\n`;
});
return output;
}
/**
* Triggers Product Search and returns search hits
*
* @returns {dw.catalog.}
*/
function getProductSearchHits() {
const ProductSearchModel = require('dw/catalog/ProductSearchModel');
const ProductSearchHit = require('dw/catalog/ProductSearchHit');
const searchModel = new ProductSearchModel();
searchModel.setCategoryID('root');
// exclude bundles and sets
searchModel.addHitTypeRefinement(ProductSearchHit.HIT_TYPE_SIMPLE, ProductSearchHit.HIT_TYPE_PRODUCT_MASTER, ProductSearchHit.HIT_TYPE_VARIATION_GROUP)
// we are only interested in products with price
searchModel.setPriceMin(0.01);
// if a product is temporarily out of stock
searchModel.setOrderableProductsOnly(false);
searchModel.search();
return searchModel.productSearchHits;
}
/**
* Calculates a random price change for the given amount
*
* @param {float} priceAmonut
* @returns {float}
*/
function randomizePrice(priceAmonut) {
// add or subtract?
var operation = Math.random() > 0.5 ? 1 : -1;
let threshold = 0;
// We want more than 5%
while(threshold < 0.05) {
// Result is something between -0.5 and +0.5
var threshold = Math.random() - 0.5
// We only want +/- 30%
threshold = threshold * 2 * 0.3
}
// Sum is the original price times threshold, operation determins between "up or down"
let sum = priceAmonut * threshold * operation;
// New Price = Old Price + Sum (which can be negative)
let result = parseFloat(priceAmonut) + parseFloat(sum);
// We only need cents :)
return result.toFixed(2);
}
/**
* Run the Price Randomizer Job
*
* @param {Object} params - execution parameters
*
* @return {dw.system.Status} Exit status for a job run
*/
var run = function (params) {
const PriceBookMgr = require('dw/catalog/PriceBookMgr');
const applicablePricebooks = PriceBookMgr.getSitePriceBooks().toArray();
const todayString = StringUtils.formatCalendar(Site.current.calendar,'yyyyMMddHHmmss_S');
const outputList = [];
applicablePricebooks.forEach(pricebook => {
// Opens a File Writer for each Pricebook, collect the results in a list for reuse
let directoryPath = File.IMPEX + File.SEPARATOR + 'src' + File.SEPARATOR + 'pricebook';
let directory = new File(directoryPath);
directory.mkdirs();
let outputFilename = directoryPath + File.SEPARATOR + pricebook.getID() + '_update_' + todayString + '.xml';
let outputFile = new File(outputFilename);
let outputFileWriter = new FileWriter(outputFile, 'UTF-8', false);
let header = getPriceBookHeader(pricebook.getID(), pricebook.getCurrencyCode());
outputFileWriter.write(header);
outputList.push({
pricebook: pricebook,
fileWriter: outputFileWriter
});
});
const searchHits = getProductSearchHits();
while (searchHits.hasNext()) {
let hit = searchHits.next();
let random = Math.random();
// We only want to update ~25% of all products
if(random > 0.25) {
continue;
}
let sourceProducts = [];
let targetProductIDs = [];
let isPriceRange = hit.minPrice.value !== hit.maxPrice.value;
if(isPriceRange) {
sourceProducts = hit.getRepresentedProducts().toArray();
targetProductIDs = [];
} else {
sourceProducts = [hit.getFirstRepresentedProduct()];
targetProductIDs = hit.getRepresentedProductIDs().toArray();
}
outputList.forEach(config => {
sourceProducts.forEach(product => {
let priceModel = product.getPriceModel();
let priceBookPrice = priceModel.getPriceBookPrice(config.pricebook.getID());
// 0 price won't change
if(priceBookPrice.getValue() === 0) {
return;
}
let newPrice = randomizePrice(priceBookPrice.getValue());
let output;
if(!empty(targetProductIDs)) {
output = getPriceXml(newPrice, targetProductIDs);
} else {
output = getPriceXml(newPrice, [product.getID()]);
}
config.fileWriter.write(output);
});
});
};
let footer = getPriceBookFooter();
outputList.forEach(config => {
config.fileWriter.write(footer);
config.fileWriter.flush();
config.fileWriter.close();
});
const Status = require('dw/system/Status');
return new Status(Status.OK);
};
exports.Run = run;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment