creates webhook and cron job to update inventory
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports["default"] = void 0;
|
||||
|
||||
var inventorySync = function inventorySync(container) {
|
||||
var brightpearlService = container.resolve("brightpearlService");
|
||||
var eventBus = container.resolve("eventBusService");
|
||||
var pattern = "43 4,10,14,20 * * *"; // nice for tests "*/10 * * * * *"
|
||||
|
||||
eventBus.createCronJob("inventory-sync", {}, pattern, brightpearlService.syncInventory());
|
||||
};
|
||||
|
||||
var _default = inventorySync;
|
||||
exports["default"] = _default;
|
||||
@@ -0,0 +1,37 @@
|
||||
"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;
|
||||
return regeneratorRuntime.wrap(function _callee$(_context) {
|
||||
while (1) {
|
||||
switch (_context.prev = _context.next) {
|
||||
case 0:
|
||||
brightpearlService = container.resolve("brightpearlService");
|
||||
_context.next = 3;
|
||||
return brightpearlService.verifyWebhooks();
|
||||
|
||||
case 3:
|
||||
case "end":
|
||||
return _context.stop();
|
||||
}
|
||||
}
|
||||
}, _callee);
|
||||
}));
|
||||
|
||||
return function webhookLoader(_x) {
|
||||
return _ref.apply(this, arguments);
|
||||
};
|
||||
}();
|
||||
|
||||
var _default = webhookLoader;
|
||||
exports["default"] = _default;
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Router } from "express"
|
||||
import bodyParser from "body-parser"
|
||||
|
||||
export default (container) => {
|
||||
const app = Router()
|
||||
|
||||
app.post("/brightpearl/inventory-update", bodyParser.json(), async (req, res) => {
|
||||
const { id } = req.body
|
||||
const brightpearlService = req.scope.resolve("brightpearlService")
|
||||
await brightpearlService.updateInventory(id)
|
||||
res.sendStatus(200)
|
||||
})
|
||||
|
||||
return app
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
const inventorySync = container => {
|
||||
const brightpearlService = container.resolve("brightpearlService")
|
||||
const eventBus = container.resolve("eventBusService")
|
||||
const pattern = "43 4,10,14,20 * * *" // nice for tests "*/10 * * * * *"
|
||||
eventBus.createCronJob("inventory-sync", {}, pattern, brightpearlService.syncInventory())
|
||||
}
|
||||
|
||||
export default inventorySync
|
||||
@@ -0,0 +1,6 @@
|
||||
const webhookLoader = async (container) => {
|
||||
const brightpearlService = container.resolve("brightpearlService")
|
||||
await brightpearlService.verifyWebhooks()
|
||||
}
|
||||
|
||||
export default webhookLoader
|
||||
@@ -2,10 +2,11 @@ import { BaseService } from "medusa-interfaces"
|
||||
import Brightpearl from "../utils/brightpearl"
|
||||
|
||||
class BrightpearlService extends BaseService {
|
||||
constructor({ totalsService, regionService, orderService, discountService }, options) {
|
||||
constructor({ totalsService, productVariantService, regionService, orderService, discountService }, options) {
|
||||
super()
|
||||
|
||||
this.options = options
|
||||
this.productVariantService_ = productVariantService
|
||||
this.regionService_ = regionService
|
||||
this.orderService_ = orderService
|
||||
this.totalsService_ = totalsService
|
||||
@@ -19,6 +20,69 @@ class BrightpearlService extends BaseService {
|
||||
})
|
||||
}
|
||||
|
||||
async verifyWebhooks() {
|
||||
const hooks = [
|
||||
{
|
||||
subscribeTo: "product.modified.on-hand-modified",
|
||||
httpMethod: "POST",
|
||||
uriTemplate: `${this.options.backend_url}/brightpearl/inventory-update`,
|
||||
bodyTemplate: '{"account": "${account-code}", "lifecycleEvent": "${lifecycle-event}", "resourceType": "${resource-type}", "id": "${resource-id}" }',
|
||||
contentType: "application/json",
|
||||
idSetAccepted: false,
|
||||
}
|
||||
]
|
||||
|
||||
const installedHooks = await this.brightpearl_.webhooks.list().catch(() => [])
|
||||
for (const hook of hooks) {
|
||||
const isInstalled = installedHooks.find(i =>
|
||||
i.subscribeTo === hook.subscribeTo &&
|
||||
i.httpMethod === hook.httpMethod &&
|
||||
i.uriTemplate === hook.uriTemplate &&
|
||||
i.bodyTemplate === hook.bodyTemplate &&
|
||||
i.contentType === hook.contentType &&
|
||||
i.idSetAccepted === hook.idSetAccepted
|
||||
)
|
||||
|
||||
if (!isInstalled) {
|
||||
await this.brightpearl_.webhooks.create(hook)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async syncInventory() {
|
||||
const variants = await this.productVariantService_.list()
|
||||
return Promise.all(variants.map(async v => {
|
||||
const brightpearlProduct = await this.retrieveProductBySKU(v.sku)
|
||||
if (!brightpearlProduct) {
|
||||
return
|
||||
}
|
||||
|
||||
const { productId } = brightpearlProduct
|
||||
const availability = await this.brightpearl_.products.retrieveAvailability(productId)
|
||||
const onHand = availability[productId].total.onHand
|
||||
|
||||
return this.productVariantService_.update(v._id, {
|
||||
inventory_quantity: onHand
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
async updateInventory(productId) {
|
||||
const brightpearlProduct = await this.brightpearl_.products.retrieve(productId)
|
||||
const availability = await this.brightpearl_.products.retrieveAvailability(productId)
|
||||
|
||||
const onHand = availability[productId].total.onHand
|
||||
|
||||
const sku = brightpearlProduct.identity.sku
|
||||
const [ variant ] = await this.productVariantService_.list({ sku })
|
||||
|
||||
if (variant && variant.manage_inventory) {
|
||||
await this.productVariantService_.update(variant._id, {
|
||||
inventory_quantity: onHand
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async createGoodsOutNote(fromOrder, shipment) {
|
||||
const id = fromOrder.metadata && fromOrder.metadata.brightpearl_sales_order_id
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ class BrightpearlClient {
|
||||
},
|
||||
})
|
||||
|
||||
this.webhooks = this.buildWebhookEndpoints()
|
||||
this.payments = this.buildPaymentEndpoints()
|
||||
this.warehouses = this.buildWarehouseEndpoints()
|
||||
this.orders = this.buildOrderEndpoints()
|
||||
@@ -31,6 +32,25 @@ class BrightpearlClient {
|
||||
})
|
||||
}
|
||||
|
||||
buildWebhookEndpoints = () => {
|
||||
return {
|
||||
list: () => {
|
||||
return this.client_.request({
|
||||
url: `/integration-service/webhook`,
|
||||
method: "GET",
|
||||
})
|
||||
.then(({ data }) => data.response)
|
||||
},
|
||||
create: (data) => {
|
||||
return this.client_.request({
|
||||
url: `/integration-service/webhook`,
|
||||
method: "POST",
|
||||
data
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildPaymentEndpoints = () => {
|
||||
return {
|
||||
create: (payment) => {
|
||||
@@ -128,6 +148,18 @@ class BrightpearlClient {
|
||||
|
||||
buildProductEndpoints = () => {
|
||||
return {
|
||||
retrieveAvailability: productId => {
|
||||
return this.client_.request({
|
||||
url: `/warehouse-service/product-availability/${productId}`,
|
||||
})
|
||||
.then(({ data }) => data.response && data.response)
|
||||
},
|
||||
retrieve: (productId) => {
|
||||
return this.client_.request({
|
||||
url: `/product-service/product/${productId}`,
|
||||
})
|
||||
.then(({ data }) => data.response && data.response[0])
|
||||
},
|
||||
retrieveBySKU: (sku) => {
|
||||
return this.client_.request({
|
||||
url: `/product-service/product-search?SKU=${sku}`,
|
||||
|
||||
@@ -25,6 +25,27 @@ var BrightpearlClient = /*#__PURE__*/function () {
|
||||
|
||||
_classCallCheck(this, BrightpearlClient);
|
||||
|
||||
_defineProperty(this, "buildWebhookEndpoints", function () {
|
||||
return {
|
||||
list: function list() {
|
||||
return _this.client_.request({
|
||||
url: "/integration-service/webhook",
|
||||
method: "GET"
|
||||
}).then(function (_ref) {
|
||||
var data = _ref.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) {
|
||||
@@ -32,8 +53,8 @@ var BrightpearlClient = /*#__PURE__*/function () {
|
||||
url: "/accounting-service/customer-payment",
|
||||
method: "POST",
|
||||
data: payment
|
||||
}).then(function (_ref) {
|
||||
var data = _ref.data;
|
||||
}).then(function (_ref2) {
|
||||
var data = _ref2.data;
|
||||
return data.response;
|
||||
});
|
||||
}
|
||||
@@ -46,8 +67,8 @@ var BrightpearlClient = /*#__PURE__*/function () {
|
||||
return _this.client_.request({
|
||||
url: "/warehouse-service/order/".concat(orderId, "/reservation"),
|
||||
method: "GET"
|
||||
}).then(function (_ref2) {
|
||||
var data = _ref2.data;
|
||||
}).then(function (_ref3) {
|
||||
var data = _ref3.data;
|
||||
return data.response;
|
||||
});
|
||||
},
|
||||
@@ -56,8 +77,8 @@ var BrightpearlClient = /*#__PURE__*/function () {
|
||||
url: "/warehouse-service/order/".concat(orderId, "/goods-note/goods-out"),
|
||||
method: "POST",
|
||||
data: data
|
||||
}).then(function (_ref3) {
|
||||
var data = _ref3.data;
|
||||
}).then(function (_ref4) {
|
||||
var data = _ref4.data;
|
||||
return data.response;
|
||||
});
|
||||
},
|
||||
@@ -90,8 +111,8 @@ var BrightpearlClient = /*#__PURE__*/function () {
|
||||
data: {
|
||||
products: data
|
||||
}
|
||||
}).then(function (_ref4) {
|
||||
var data = _ref4.data;
|
||||
}).then(function (_ref5) {
|
||||
var data = _ref5.data;
|
||||
return data.response;
|
||||
});
|
||||
}
|
||||
@@ -104,8 +125,8 @@ var BrightpearlClient = /*#__PURE__*/function () {
|
||||
return _this.client_.request({
|
||||
url: "/order-service/sales-order/".concat(orderId),
|
||||
method: "GET"
|
||||
}).then(function (_ref5) {
|
||||
var data = _ref5.data;
|
||||
}).then(function (_ref6) {
|
||||
var data = _ref6.data;
|
||||
return data.response.length && data.response[0];
|
||||
})["catch"](function (err) {
|
||||
return console.log(err);
|
||||
@@ -116,8 +137,8 @@ var BrightpearlClient = /*#__PURE__*/function () {
|
||||
url: "/order-service/sales-order",
|
||||
method: "POST",
|
||||
data: order
|
||||
}).then(function (_ref6) {
|
||||
var data = _ref6.data;
|
||||
}).then(function (_ref7) {
|
||||
var data = _ref7.data;
|
||||
return data.response;
|
||||
});
|
||||
}
|
||||
@@ -131,8 +152,8 @@ var BrightpearlClient = /*#__PURE__*/function () {
|
||||
url: "/contact-service/postal-address",
|
||||
method: "POST",
|
||||
data: address
|
||||
}).then(function (_ref7) {
|
||||
var data = _ref7.data;
|
||||
}).then(function (_ref8) {
|
||||
var data = _ref8.data;
|
||||
return data.response;
|
||||
});
|
||||
}
|
||||
@@ -141,11 +162,27 @@ var BrightpearlClient = /*#__PURE__*/function () {
|
||||
|
||||
_defineProperty(this, "buildProductEndpoints", function () {
|
||||
return {
|
||||
retrieveAvailability: function retrieveAvailability(productId) {
|
||||
return _this.client_.request({
|
||||
url: "/warehouse-service/product-availability/".concat(productId)
|
||||
}).then(function (_ref9) {
|
||||
var data = _ref9.data;
|
||||
return data.response && data.response;
|
||||
});
|
||||
},
|
||||
retrieve: function retrieve(productId) {
|
||||
return _this.client_.request({
|
||||
url: "/product-service/product/".concat(productId)
|
||||
}).then(function (_ref10) {
|
||||
var data = _ref10.data;
|
||||
return data.response && data.response[0];
|
||||
});
|
||||
},
|
||||
retrieveBySKU: function retrieveBySKU(sku) {
|
||||
return _this.client_.request({
|
||||
url: "/product-service/product-search?SKU=".concat(sku)
|
||||
}).then(function (_ref8) {
|
||||
var data = _ref8.data;
|
||||
}).then(function (_ref11) {
|
||||
var data = _ref11.data;
|
||||
return _this.buildSearchResults_(data.response);
|
||||
});
|
||||
}
|
||||
@@ -157,8 +194,8 @@ var BrightpearlClient = /*#__PURE__*/function () {
|
||||
retrieveByEmail: function retrieveByEmail(email) {
|
||||
return _this.client_.request({
|
||||
url: "/contact-service/contact-search?primaryEmail=".concat(email)
|
||||
}).then(function (_ref9) {
|
||||
var data = _ref9.data;
|
||||
}).then(function (_ref12) {
|
||||
var data = _ref12.data;
|
||||
return _this.buildSearchResults_(data.response);
|
||||
});
|
||||
},
|
||||
@@ -167,8 +204,8 @@ var BrightpearlClient = /*#__PURE__*/function () {
|
||||
url: "/contact-service/contact",
|
||||
method: "POST",
|
||||
data: customerData
|
||||
}).then(function (_ref10) {
|
||||
var data = _ref10.data;
|
||||
}).then(function (_ref13) {
|
||||
var data = _ref13.data;
|
||||
return data.response;
|
||||
});
|
||||
}
|
||||
@@ -182,6 +219,7 @@ var BrightpearlClient = /*#__PURE__*/function () {
|
||||
'brightpearl-account-token': options.token
|
||||
}
|
||||
});
|
||||
this.webhooks = this.buildWebhookEndpoints();
|
||||
this.payments = this.buildPaymentEndpoints();
|
||||
this.warehouses = this.buildWarehouseEndpoints();
|
||||
this.orders = this.buildOrderEndpoints();
|
||||
|
||||
@@ -16,7 +16,7 @@ import { sync as existsSync } from "fs-exists-cached"
|
||||
/**
|
||||
* Registers all services in the services directory
|
||||
*/
|
||||
export default ({ rootDirectory, container, app }) => {
|
||||
export default async ({ rootDirectory, container, app }) => {
|
||||
const { configModule, configFilePath } = getConfigFile(
|
||||
rootDirectory,
|
||||
`medusa-config`
|
||||
@@ -55,6 +55,29 @@ export default ({ rootDirectory, container, app }) => {
|
||||
registerCoreRouters(pluginDetails, container)
|
||||
registerSubscribers(pluginDetails, container)
|
||||
})
|
||||
|
||||
await Promise.all(
|
||||
resolved.map(async pluginDetails => runLoaders(pluginDetails, container))
|
||||
)
|
||||
}
|
||||
|
||||
async function runLoaders(pluginDetails, container) {
|
||||
const loaderFiles = glob.sync(
|
||||
`${pluginDetails.resolve}/loaders/[!__]*.js`,
|
||||
{}
|
||||
)
|
||||
await Promise.all(
|
||||
loaderFiles.map(async loader => {
|
||||
try {
|
||||
const module = require(loader).default
|
||||
if (typeof module === "function") {
|
||||
await module(container)
|
||||
}
|
||||
} catch (err) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
function registerMedusaApi(pluginDetails, container) {
|
||||
|
||||
@@ -13,11 +13,20 @@ class EventBusService {
|
||||
/** @private {object} */
|
||||
this.observers_ = {}
|
||||
|
||||
/** @private {object} to handle cron jobs */
|
||||
this.cronHandlers_ = {}
|
||||
|
||||
/** @private {BullQueue} used for cron jobs */
|
||||
this.cronQueue_ = new Bull(`cron-jobs:queue`, config.redisURI)
|
||||
|
||||
/** @private {BullQueue} */
|
||||
this.queue_ = new Bull(`${this.constructor.name}:queue`, config.redisURI)
|
||||
|
||||
// Register our worker to handle emit calls
|
||||
this.queue_.process(this.worker_)
|
||||
|
||||
// Register cron worker
|
||||
this.cronQueue_.process(this.cronWorker_)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -38,6 +47,21 @@ class EventBusService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
registerCronHandler_(event, subscriber) {
|
||||
if (typeof subscriber !== "function") {
|
||||
throw new Error("Handler must be a function")
|
||||
}
|
||||
|
||||
if (this.observers_[event]) {
|
||||
this.cronHandlers_[event].push(subscriber)
|
||||
} else {
|
||||
this.cronHandlers_[event] = [subscriber]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls all subscribers when an event occurs.
|
||||
* @param {string} eventName - the name of the event to be process.
|
||||
@@ -77,6 +101,37 @@ class EventBusService {
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
cronWorker_ = job => {
|
||||
const { eventName, data } = job.data
|
||||
const observers = this.cronHandlers_[eventName] || []
|
||||
this.logger_.info(`Processing cron job: ${eventName}`)
|
||||
|
||||
return Promise.all(
|
||||
observers.map(subscriber => {
|
||||
return subscriber(data).catch(err => {
|
||||
this.logger_.warn(
|
||||
`An error occured while processing ${eventName}: ${err}`
|
||||
)
|
||||
return err
|
||||
})
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a cron job.
|
||||
*/
|
||||
createCronJob(eventName, data, cron, handler) {
|
||||
this.registerCronHandler(eventName, handler)
|
||||
return this.cronQueue_.add(
|
||||
{
|
||||
eventName,
|
||||
data,
|
||||
},
|
||||
{ repeat: { cron } }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default EventBusService
|
||||
|
||||
Reference in New Issue
Block a user