diff --git a/packages/medusa-plugin-brightpearl/.gitignore b/packages/medusa-plugin-brightpearl/.gitignore index 702ac6c2fc..c44abdae0b 100644 --- a/packages/medusa-plugin-brightpearl/.gitignore +++ b/packages/medusa-plugin-brightpearl/.gitignore @@ -12,5 +12,6 @@ yarn.lock /services /models /subscribers +/loaders /utils diff --git a/packages/medusa-plugin-brightpearl/.npmignore b/packages/medusa-plugin-brightpearl/.npmignore index 486581be18..8f08af1c52 100644 --- a/packages/medusa-plugin-brightpearl/.npmignore +++ b/packages/medusa-plugin-brightpearl/.npmignore @@ -3,6 +3,7 @@ node_modules .DS_store .env* /*.js +src !index.js yarn.lock diff --git a/packages/medusa-plugin-brightpearl/README.md b/packages/medusa-plugin-brightpearl/README.md new file mode 100644 index 0000000000..b0dc9384ab --- /dev/null +++ b/packages/medusa-plugin-brightpearl/README.md @@ -0,0 +1,35 @@ +# medusa-plugin-brightpearl + +Sends orders to Brightpearl, listens for stock movements, handles returns. + +## Options + +``` + account: [the Brightpearl account] (required) + channel_id: [channel id to map sales and credits to] (required) + backend_url: [the url where the Medusa server is running, needed for webhooks] (required) + event_owner: [the id of the user who will own goods out events] (required), + warehouse: [the warehouse id to allocate orders from] (required) + default_status_id: [the status id to assign new orders with] (optional: defaults to 1) + payment_method_code: [the method code to register payments with] (optional: defaults to 1220) + sales_account_code: [nominal code to assign line items to] (optional: defaults to 4000) + shipping_account_code: [nominal code to assign shipping line to] (optional: defaults to 4040)jk + discount_account_code: [nominal code to use for Discount-type refunds] (optional) + inventory_sync_cron: [cron pattern for inventory sync, if left out the job will not be created] (default: false) +``` + + +## Orders + +When an order is created in Medusa it will automatically be sent to Brightpearl and allocated there. Once allocated it is up to Brightpearl to figure out how the order is to be fulfilled - the plugin listens for goods out notes and tries to map each of these to a Medusa order, if the matching succeeds Medusa will send the order to the fulfillment provider associated with the shipping method selected by the Customer. + +When line items on an order are returned the plugin will generate a sales credit in Brightpearl. + +## Products + +The plugin doesn't automatically create products in Medusa, but listens for inventory changes in Brightpearl. The plugin updates each product variant to reflect the inventory quantity listed in Brightpearl, thereby ensuring that the inventory levels in Medusa are always in sync with Brightpearl. + +## OAuth + +The plugin registers an OAuth app in Medusa allowing installation at https://medusa-commerce.com/a/settings/apps. The OAuth tokens are refreshed every hour to prevent unauthorized requests. + diff --git a/packages/medusa-plugin-brightpearl/loaders/inventory.js b/packages/medusa-plugin-brightpearl/loaders/inventory.js deleted file mode 100644 index edbcb5c6bb..0000000000 --- a/packages/medusa-plugin-brightpearl/loaders/inventory.js +++ /dev/null @@ -1,61 +0,0 @@ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports["default"] = void 0; - -function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } } - -function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; } - -var inventorySync = /*#__PURE__*/function () { - var _ref = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee(container) { - var brightpearlService, eventBus, client, pattern; - return regeneratorRuntime.wrap(function _callee$(_context) { - while (1) { - switch (_context.prev = _context.next) { - case 0: - brightpearlService = container.resolve("brightpearlService"); - eventBus = container.resolve("eventBusService"); - _context.prev = 2; - _context.next = 5; - return brightpearlService.getClient(); - - case 5: - client = _context.sent; - pattern = "43 4,10,14,20 * * *"; // nice for tests "*/10 * * * * *" - - eventBus.createCronJob("inventory-sync", {}, pattern, brightpearlService.syncInventory()); - _context.next = 15; - break; - - case 10: - _context.prev = 10; - _context.t0 = _context["catch"](2); - - if (!(_context.t0.name === "not_allowed")) { - _context.next = 14; - break; - } - - return _context.abrupt("return"); - - case 14: - throw _context.t0; - - case 15: - case "end": - return _context.stop(); - } - } - }, _callee, null, [[2, 10]]); - })); - - return function inventorySync(_x) { - return _ref.apply(this, arguments); - }; -}(); - -var _default = inventorySync; -exports["default"] = _default; \ No newline at end of file diff --git a/packages/medusa-plugin-brightpearl/loaders/webhooks.js b/packages/medusa-plugin-brightpearl/loaders/webhooks.js deleted file mode 100644 index b4616e9d82..0000000000 --- a/packages/medusa-plugin-brightpearl/loaders/webhooks.js +++ /dev/null @@ -1,61 +0,0 @@ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports["default"] = void 0; - -function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } } - -function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; } - -var webhookLoader = /*#__PURE__*/function () { - var _ref = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee(container) { - var brightpearlService, client; - return regeneratorRuntime.wrap(function _callee$(_context) { - while (1) { - switch (_context.prev = _context.next) { - case 0: - brightpearlService = container.resolve("brightpearlService"); - _context.prev = 1; - _context.next = 4; - return brightpearlService.getClient(); - - case 4: - client = _context.sent; - _context.next = 7; - return brightpearlService.verifyWebhooks(); - - case 7: - _context.next = 14; - break; - - case 9: - _context.prev = 9; - _context.t0 = _context["catch"](1); - - if (!(_context.t0.name === "not_allowed")) { - _context.next = 13; - break; - } - - return _context.abrupt("return"); - - case 13: - throw _context.t0; - - case 14: - case "end": - return _context.stop(); - } - } - }, _callee, null, [[1, 9]]); - })); - - return function webhookLoader(_x) { - return _ref.apply(this, arguments); - }; -}(); - -var _default = webhookLoader; -exports["default"] = _default; \ No newline at end of file diff --git a/packages/medusa-plugin-brightpearl/src/loaders/inventory.js b/packages/medusa-plugin-brightpearl/src/loaders/inventory.js index 97b44b189d..f60a6423a1 100644 --- a/packages/medusa-plugin-brightpearl/src/loaders/inventory.js +++ b/packages/medusa-plugin-brightpearl/src/loaders/inventory.js @@ -1,21 +1,24 @@ -const inventorySync = async (container) => { - const brightpearlService = container.resolve("brightpearlService") - const eventBus = container.resolve("eventBusService") - - try { - const client = await brightpearlService.getClient() - const pattern = "43 4,10,14,20 * * *" // nice for tests "*/10 * * * * *" - eventBus.createCronJob( - "inventory-sync", - {}, - pattern, - brightpearlService.syncInventory() - ) - } catch (err) { - if (err.name === "not_allowed") { - return +const inventorySync = async (container, options) => { + if (!options.inventory_sync_cron) { + return + } else { + const brightpearlService = container.resolve("brightpearlService") + const eventBus = container.resolve("eventBusService") + try { + const client = await brightpearlService.getClient() + const pattern = options.inventory_sync_cron + eventBus.createCronJob( + "inventory-sync", + {}, + pattern, + brightpearlService.syncInventory + ) + } catch (err) { + if (err.name === "not_allowed") { + return + } + throw err } - throw err } } diff --git a/packages/medusa-plugin-brightpearl/src/loaders/token-refresh.js b/packages/medusa-plugin-brightpearl/src/loaders/token-refresh.js new file mode 100644 index 0000000000..5dd3a46b7d --- /dev/null +++ b/packages/medusa-plugin-brightpearl/src/loaders/token-refresh.js @@ -0,0 +1,25 @@ +const REFRESH_CRON = process.env.BP_REFRESH_CRON || "5 4 * * */6" + +const refreshToken = async (container) => { + const logger = container.resolve("logger") + const oauthService = container.resolve("oauthService") + const eventBus = container.resolve("eventBusService") + + try { + logger.info("registering refresh cron job BP") + eventBus.createCronJob("refresh-token-bp", {}, REFRESH_CRON, async () => { + const appData = await oauthService.retrieveByName("brightpearl") + const data = appData.data + if (data && data.refresh_token) { + return oauthService.refreshToken("brightpearl", data.refresh_token) + } + }) + } catch (err) { + if (err.name === "not_allowed") { + return + } + throw err + } +} + +export default refreshToken diff --git a/packages/medusa-plugin-brightpearl/src/utils/brightpearl.js b/packages/medusa-plugin-brightpearl/src/utils/brightpearl.js index b8a4779c18..fddcdf6652 100644 --- a/packages/medusa-plugin-brightpearl/src/utils/brightpearl.js +++ b/packages/medusa-plugin-brightpearl/src/utils/brightpearl.js @@ -41,7 +41,11 @@ class BrightpearlClient { "content-type": "application/x-www-form-urlencoded", }, data: qs.stringify(params), - }).then(({ data }) => data) + }) + .then(({ data }) => data) + .catch((err) => { + throw err + }) } constructor(options, onRefreshToken) { diff --git a/packages/medusa-plugin-brightpearl/utils/brightpearl.js b/packages/medusa-plugin-brightpearl/utils/brightpearl.js deleted file mode 100644 index a709ed2367..0000000000 --- a/packages/medusa-plugin-brightpearl/utils/brightpearl.js +++ /dev/null @@ -1,426 +0,0 @@ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports["default"] = void 0; - -var _axios = _interopRequireDefault(require("axios")); - -var _axiosRateLimit = _interopRequireDefault(require("axios-rate-limit")); - -var _querystring = _interopRequireDefault(require("querystring")); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } - -function _instanceof(left, right) { if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) { return !!right[Symbol.hasInstance](left); } else { return left instanceof right; } } - -function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } } - -function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; } - -function _classCallCheck(instance, Constructor) { if (!_instanceof(instance, Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } - -function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } - -function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } - -// Brightpearl allows 200 requests per minute -var RATE_LIMIT_REQUESTS = 200; -var RATE_LIMIT_INTERVAL = 60 * 1000; - -var BrightpearlClient = /*#__PURE__*/function () { - _createClass(BrightpearlClient, null, [{ - key: "createToken", - value: function createToken(account, data) { - var params = { - grant_type: "authorization_code", - code: data.code, - client_id: data.client_id, - client_secret: data.client_secret, - redirect_uri: data.redirect - }; - return (0, _axios["default"])({ - url: "https://ws-eu1.brightpearl.com/".concat(account, "/oauth/token"), - method: "POST", - headers: { - "content-type": "application/x-www-form-urlencoded" - }, - data: _querystring["default"].stringify(params) - }).then(function (_ref) { - var data = _ref.data; - return data; - }); - } - }, { - key: "refreshToken", - value: function refreshToken(account, data) { - var params = { - grant_type: "refresh_token", - refresh_token: data.refresh_token, - client_id: data.client_id, - client_secret: data.client_secret - }; - return (0, _axios["default"])({ - url: "https://ws-eu1.brightpearl.com/".concat(account, "/oauth/token"), - method: "POST", - headers: { - "content-type": "application/x-www-form-urlencoded" - }, - data: _querystring["default"].stringify(params) - }).then(function (_ref2) { - var data = _ref2.data; - return data; - }); - } - }]); - - function BrightpearlClient(options, onRefreshToken) { - var _this = this; - - _classCallCheck(this, BrightpearlClient); - - _defineProperty(this, "buildWebhookEndpoints", function () { - return { - list: function list() { - return _this.client_.request({ - url: "/integration-service/webhook", - method: "GET" - }).then(function (_ref3) { - var data = _ref3.data; - return data.response; - }); - }, - create: function create(data) { - return _this.client_.request({ - url: "/integration-service/webhook", - method: "POST", - data: data - }); - } - }; - }); - - _defineProperty(this, "buildPaymentEndpoints", function () { - return { - create: function create(payment) { - return _this.client_.request({ - url: "/accounting-service/customer-payment", - method: "POST", - data: payment - }).then(function (_ref4) { - var data = _ref4.data; - return data.response; - }); - } - }; - }); - - _defineProperty(this, "buildWarehouseEndpoints", function () { - return { - retrieveReservation: function retrieveReservation(orderId) { - return _this.client_.request({ - url: "/warehouse-service/order/".concat(orderId, "/reservation"), - method: "GET" - }).then(function (_ref5) { - var data = _ref5.data; - return data.response; - }); - }, - retrieveGoodsOutNote: function retrieveGoodsOutNote(id) { - return _this.client_.request({ - url: "/warehouse-service/order/*/goods-note/goods-out/".concat(id), - method: "GET" - }).then(function (_ref6) { - var data = _ref6.data; - return data.response && data.response[id]; - }); - }, - createGoodsOutNote: function createGoodsOutNote(orderId, data) { - return _this.client_.request({ - url: "/warehouse-service/order/".concat(orderId, "/goods-note/goods-out"), - method: "POST", - data: data - }).then(function (_ref7) { - var data = _ref7.data; - return data.response; - }); - }, - updateGoodsOutNote: function updateGoodsOutNote(noteId, update) { - return _this.client_.request({ - url: "/warehouse-service/goods-note/goods-out/".concat(noteId), - method: "PUT", - data: update - }); - }, - registerGoodsOutEvent: function registerGoodsOutEvent(noteId, data) { - return _this.client_.request({ - url: "/warehouse-service/goods-note/goods-out/".concat(noteId, "/event"), - method: "POST", - data: data - }); - }, - createReservation: function createReservation(order, warehouse) { - var id = order.id; - var data = order.rows.map(function (r) { - return { - productId: r.productId, - salesOrderRowId: r.id, - quantity: r.quantity - }; - }); - return _this.client_.request({ - url: "/warehouse-service/order/".concat(id, "/reservation/warehouse/").concat(warehouse), - method: "POST", - data: { - products: data - } - }).then(function (_ref8) { - var data = _ref8.data; - return data.response; - }); - } - }; - }); - - _defineProperty(this, "buildOrderEndpoints", function () { - return { - retrieve: function retrieve(orderId) { - return _this.client_.request({ - url: "/order-service/sales-order/".concat(orderId), - method: "GET" - }).then(function (_ref9) { - var data = _ref9.data; - return data.response.length && data.response[0]; - })["catch"](function (err) { - return console.log(err); - }); - }, - create: function create(order) { - return _this.client_.request({ - url: "/order-service/sales-order", - method: "POST", - data: order - }).then(function (_ref10) { - var data = _ref10.data; - return data.response; - }); - }, - createCredit: function createCredit(salesCredit) { - return _this.client_.request({ - url: "/order-service/sales-credit", - method: "POST", - data: salesCredit - }).then(function (_ref11) { - var data = _ref11.data; - return data.response; - }); - } - }; - }); - - _defineProperty(this, "buildAddressEndpoints", function () { - return { - create: function create(address) { - return _this.client_.request({ - url: "/contact-service/postal-address", - method: "POST", - data: address - }).then(function (_ref12) { - var data = _ref12.data; - return data.response; - }); - } - }; - }); - - _defineProperty(this, "buildProductEndpoints", function () { - return { - retrieveAvailability: function retrieveAvailability(productId) { - return _this.client_.request({ - url: "/warehouse-service/product-availability/".concat(productId) - }).then(function (_ref13) { - var data = _ref13.data; - return data.response && data.response; - }); - }, - retrieve: function retrieve(productId) { - return _this.client_.request({ - url: "/product-service/product/".concat(productId) - }).then(function (_ref14) { - var data = _ref14.data; - return data.response && data.response[0]; - }); - }, - search: function search(_search) { - return _this.client_.request({ - url: "/product-service/product-search?".concat(_search) - }).then(function (_ref15) { - var data = _ref15.data; - return { - products: _this.buildSearchResults_(data.response), - metadata: data.response.metaData - }; - }); - }, - retrieveBySKU: function retrieveBySKU(sku) { - return _this.client_.request({ - url: "/product-service/product-search?SKU=".concat(sku) - }).then(function (_ref16) { - var data = _ref16.data; - return _this.buildSearchResults_(data.response); - }); - } - }; - }); - - _defineProperty(this, "buildCustomerEndpoints", function () { - return { - retrieveByEmail: function retrieveByEmail(email) { - return _this.client_.request({ - url: "/contact-service/contact-search?primaryEmail=".concat(email) - }).then(function (_ref17) { - var data = _ref17.data; - return _this.buildSearchResults_(data.response); - }); - }, - create: function create(customerData) { - return _this.client_.request({ - url: "/contact-service/contact", - method: "POST", - data: customerData - }).then(function (_ref18) { - var data = _ref18.data; - return data.response; - }); - } - }; - }); - - this.client_ = (0, _axiosRateLimit["default"])(_axios["default"].create({ - baseURL: "https://".concat(options.url, "/public-api/").concat(options.account), - headers: { - "brightpearl-app-ref": "medusa-dev", - "brightpearl-dev-ref": "sebrindom" - } - }), { - maxRequests: RATE_LIMIT_REQUESTS, - perMilliseconds: RATE_LIMIT_INTERVAL - }); - this.authType_ = options.auth_type; - this.token_ = options.access_token; - this.webhooks = this.buildWebhookEndpoints(); - this.payments = this.buildPaymentEndpoints(); - this.warehouses = this.buildWarehouseEndpoints(); - this.orders = this.buildOrderEndpoints(); - this.addresses = this.buildAddressEndpoints(); - this.customers = this.buildCustomerEndpoints(); - this.products = this.buildProductEndpoints(); - this.buildRefreshTokenInterceptor_(onRefreshToken); - } - - _createClass(BrightpearlClient, [{ - key: "updateAuth", - value: function updateAuth(data) { - if (data.auth_type) { - this.authType_ = data.auth_type; - } - - if (data.access_token) { - this.token_ = data.access_token; - } - } - }, { - key: "buildRefreshTokenInterceptor_", - value: function buildRefreshTokenInterceptor_(onRefresh) { - var _this2 = this; - - this.client_.interceptors.request.use(function (request) { - var authType = _this2.authType_; - var token = _this2.token_; - - if (token) { - request.headers["Authorization"] = "Bearer ".concat(token); - } - - return request; - }); - this.client_.interceptors.response.use(undefined, /*#__PURE__*/function () { - var _ref19 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee(error) { - var response; - return regeneratorRuntime.wrap(function _callee$(_context) { - while (1) { - switch (_context.prev = _context.next) { - case 0: - response = error.response; - - if (!response) { - _context.next = 13; - break; - } - - if (!(response.status === 401 && error.config && !error.config.__isRetryRequest)) { - _context.next = 13; - break; - } - - _context.prev = 3; - _context.next = 6; - return onRefresh(_this2); - - case 6: - _context.next = 11; - break; - - case 8: - _context.prev = 8; - _context.t0 = _context["catch"](3); - return _context.abrupt("return", Promise.reject(error)); - - case 11: - // retry the original request - error.config.__isRetryRequest = true; - return _context.abrupt("return", _this2.client_(error.config)); - - case 13: - return _context.abrupt("return", Promise.reject(error)); - - case 14: - case "end": - return _context.stop(); - } - } - }, _callee, null, [[3, 8]]); - })); - - return function (_x) { - return _ref19.apply(this, arguments); - }; - }()); - } - }, { - key: "buildSearchResults_", - value: function buildSearchResults_(response) { - var results = response.results, - metaData = response.metaData; // Map the column names to the columns - - return results.map(function (resColumns) { - var object = {}; - - for (var i = 0; i < resColumns.length; i++) { - var fieldName = metaData.columns[i].name; - object[fieldName] = resColumns[i]; - } - - return object; - }); - } - }]); - - return BrightpearlClient; -}(); - -var _default = BrightpearlClient; -exports["default"] = _default; \ No newline at end of file diff --git a/packages/medusa/src/loaders/plugins.js b/packages/medusa/src/loaders/plugins.js index 78dcdb31b8..1e3d856169 100644 --- a/packages/medusa/src/loaders/plugins.js +++ b/packages/medusa/src/loaders/plugins.js @@ -74,9 +74,11 @@ async function runLoaders(pluginDetails, container) { try { const module = require(loader).default if (typeof module === "function") { - await module(container) + await module(container, pluginDetails.options) } } catch (err) { + const logger = container.resolve("logger") + logger.warn(`Running loader failed: ${err.message}`) return Promise.resolve() } }) diff --git a/packages/medusa/src/services/event-bus.js b/packages/medusa/src/services/event-bus.js index a2517f3f6c..d9483db6c5 100644 --- a/packages/medusa/src/services/event-bus.js +++ b/packages/medusa/src/services/event-bus.js @@ -17,12 +17,6 @@ class EventBusService { /** @private {object} to handle cron jobs */ this.cronHandlers_ = {} - /** @private {BullQueue} used for cron jobs */ - this.cronQueue_ = new Bull( - `cron-jobs:queue`, - config.projectConfig.redis_url - ) - const opts = { createClient: type => { switch (type) { @@ -36,6 +30,9 @@ class EventBusService { }, } + /** @private {BullQueue} used for cron jobs */ + this.cronQueue_ = new Bull(`cron-jobs:queue`, opts) + /** @private {BullQueue} */ this.queue_ = new Bull(`${this.constructor.name}:queue`, opts) @@ -86,12 +83,15 @@ class EventBusService { * @return {BullJob} - the job from our queue */ emit(eventName, data) { - return this.queue_.add({ - eventName, - data, - }, { - removeOnComplete: true - }) + return this.queue_.add( + { + eventName, + data, + }, + { + removeOnComplete: true, + } + ) } /** @@ -140,9 +140,15 @@ class EventBusService { /** * Registers a cron job. + * @param {string} eventName - the name of the event + * @param {object} data - the data to be sent with the event + * @param {string} cron - the cron pattern + * @param {function} handler - the handler to call on each cron job + * @return void */ createCronJob(eventName, data, cron, handler) { - this.registerCronHandler(eventName, handler) + this.logger_.info(`Registering ${eventName}`) + this.registerCronHandler_(eventName, handler) return this.cronQueue_.add( { eventName,