Created
December 9, 2022 07:39
-
-
Save hnestmann/0d7cf86ae311d4a17aa70f48df41081d to your computer and use it in GitHub Desktop.
Job that randomizes prices to simultate operational price changes
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'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