fix: creates restock functionality

This commit is contained in:
Sebastian Rindom
2021-04-06 12:22:19 +02:00
parent 0a80b098a4
commit 2b2555004e
17 changed files with 2673 additions and 259 deletions

View File

@@ -1,13 +0,0 @@
{
"plugins": [
"@babel/plugin-proposal-class-properties",
"@babel/plugin-transform-instanceof",
"@babel/plugin-transform-classes"
],
"presets": ["@babel/preset-env"],
"env": {
"test": {
"plugins": ["@babel/plugin-transform-runtime"]
}
}
}

View File

@@ -1,99 +0,0 @@
# Change Log
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.1.6](https://github.com/medusajs/medusa/compare/medusa-plugin-twilio-sms@1.1.5...medusa-plugin-twilio-sms@1.1.6) (2021-03-17)
**Note:** Version bump only for package medusa-plugin-twilio-sms
## [1.1.5](https://github.com/medusajs/medusa/compare/medusa-plugin-twilio-sms@1.1.3...medusa-plugin-twilio-sms@1.1.5) (2021-03-17)
**Note:** Version bump only for package medusa-plugin-twilio-sms
## [1.1.4](https://github.com/medusajs/medusa/compare/medusa-plugin-twilio-sms@1.1.3...medusa-plugin-twilio-sms@1.1.4) (2021-03-17)
**Note:** Version bump only for package medusa-plugin-twilio-sms
## [1.1.3](https://github.com/medusajs/medusa/compare/medusa-plugin-twilio-sms@1.1.2...medusa-plugin-twilio-sms@1.1.3) (2021-02-17)
**Note:** Version bump only for package medusa-plugin-twilio-sms
## [1.1.2](https://github.com/medusajs/medusa/compare/medusa-plugin-twilio-sms@1.1.1...medusa-plugin-twilio-sms@1.1.2) (2021-02-03)
**Note:** Version bump only for package medusa-plugin-twilio-sms
## [1.1.1](https://github.com/medusajs/medusa/compare/medusa-plugin-twilio-sms@1.1.0...medusa-plugin-twilio-sms@1.1.1) (2021-01-27)
**Note:** Version bump only for package medusa-plugin-twilio-sms
# [1.1.0](https://github.com/medusajs/medusa/compare/medusa-plugin-twilio-sms@1.0.12...medusa-plugin-twilio-sms@1.1.0) (2021-01-26)
**Note:** Version bump only for package medusa-plugin-twilio-sms
## [1.0.12](https://github.com/medusajs/medusa/compare/medusa-plugin-twilio-sms@1.0.11...medusa-plugin-twilio-sms@1.0.12) (2020-12-17)
**Note:** Version bump only for package medusa-plugin-twilio-sms
## [1.0.11](https://github.com/medusajs/medusa/compare/medusa-plugin-twilio-sms@1.0.10...medusa-plugin-twilio-sms@1.0.11) (2020-11-24)
**Note:** Version bump only for package medusa-plugin-twilio-sms
## [1.0.10](https://github.com/medusajs/medusa/compare/medusa-plugin-twilio-sms@1.0.9...medusa-plugin-twilio-sms@1.0.10) (2020-10-19)
**Note:** Version bump only for package medusa-plugin-twilio-sms
## [1.0.9](https://github.com/medusajs/medusa/compare/medusa-plugin-twilio-sms@1.0.8...medusa-plugin-twilio-sms@1.0.9) (2020-09-09)
**Note:** Version bump only for package medusa-plugin-twilio-sms
## 1.0.8 (2020-09-09)
### Features
* **plugins:** Adds Twilio SMS plugin ([35a91ae](https://github.com/medusajs/medusa/commit/35a91ae6a179e750b77df97f46a6b88d6b45819d))

View File

@@ -1,4 +1,4 @@
# medusa-plugin-sendgrid
# medusa-plugin-restock-notification
Twilio SMS / Messaging plugin.

View File

@@ -0,0 +1,86 @@
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.restockNotification1617703530229 = void 0;
var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
var restockNotification1617703530229 = /*#__PURE__*/function () {
function restockNotification1617703530229() {
(0, _classCallCheck2["default"])(this, restockNotification1617703530229);
this.name = "restockNotification1617703530229";
}
(0, _createClass2["default"])(restockNotification1617703530229, [{
key: "up",
value: function () {
var _up = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee(queryRunner) {
return _regenerator["default"].wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
_context.next = 2;
return queryRunner.query("CREATE TABLE \"restock_notification\" (\"variant_id\" character varying NOT NULL, \"emails\" jsonb NOT NULL, \"created_at\" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), \"updated_at\" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), CONSTRAINT \"PK_49181ca04caac807fcec321705a\" PRIMARY KEY (\"variant_id\"))");
case 2:
_context.next = 4;
return queryRunner.query("ALTER TABLE \"restock_notification\" ADD CONSTRAINT \"FK_49181ca04caac807fcec321705a\" FOREIGN KEY (\"variant_id\") REFERENCES \"product_variant\"(\"id\") ON DELETE NO ACTION ON UPDATE NO ACTION");
case 4:
case "end":
return _context.stop();
}
}
}, _callee);
}));
function up(_x) {
return _up.apply(this, arguments);
}
return up;
}()
}, {
key: "down",
value: function () {
var _down = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee2(queryRunner) {
return _regenerator["default"].wrap(function _callee2$(_context2) {
while (1) {
switch (_context2.prev = _context2.next) {
case 0:
_context2.next = 2;
return queryRunner.query("ALTER TABLE \"restock_notification\" DROP CONSTRAINT \"FK_49181ca04caac807fcec321705a\"");
case 2:
_context2.next = 4;
return queryRunner.query("DROP TABLE \"restock_notification\"");
case 4:
case "end":
return _context2.stop();
}
}
}, _callee2);
}));
function down(_x2) {
return _down.apply(this, arguments);
}
return down;
}()
}]);
return restockNotification1617703530229;
}();
exports.restockNotification1617703530229 = restockNotification1617703530229;

View File

@@ -18,25 +18,28 @@
"@babel/plugin-transform-instanceof": "^7.8.3",
"@babel/plugin-transform-runtime": "^7.7.6",
"@babel/preset-env": "^7.7.5",
"@babel/preset-typescript": "^7.13.0",
"@babel/register": "^7.7.4",
"@babel/runtime": "^7.9.6",
"babel-preset-medusa-package": "^1.1.0",
"cross-env": "^5.2.1",
"eslint": "^6.8.0",
"jest": "^25.5.2",
"medusa-test-utils": "^1.1.6",
"pg": "^8.5.1",
"ulid": "^2.3.0"
},
"scripts": {
"build": "babel src -d .",
"build": "babel src -d . --ignore **/__tests__ --extensions \".ts,.js\"",
"prepare": "cross-env NODE_ENV=production npm run build",
"watch": "babel -w src --out-dir . --ignore **/__tests__",
"test": "jest"
},
"peerDependencies": {
"@medusajs/medusa": "1.x",
"medusa-interfaces": "1.x"
},
"dependencies": {
"@medusajs/medusa": "^1.1.17",
"body-parser": "^1.19.0",
"medusa-core-utils": "^1.1.3"
},

View File

@@ -0,0 +1,10 @@
import { Router } from "express"
import routes from "./routes"
export default (container) => {
const app = Router()
routes(app)
return app
}

View File

@@ -0,0 +1 @@
export default (fn) => (...args) => fn(...args).catch(args[2])

View File

@@ -0,0 +1,5 @@
import { default as wrap } from "./await-middleware"
export default {
wrap,
}

View File

@@ -0,0 +1,24 @@
import { Validator, MedusaError } from "medusa-core-utils"
export default async (req, res) => {
const { variant_id } = req.parmas
const schema = Validator.object().keys({
email: Validator.string().required(),
})
const { value, error } = schema.validate(req.body)
if (error) {
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
}
try {
const restockNotificationService = req.scope.resolve(
"restockNotificationService"
)
await restockNotificationService.addEmail(variant_id, value.email)
res.sendStatus(200)
} catch (err) {
res.sendStatus(400).json({ message: err.message })
}
}

View File

@@ -0,0 +1,16 @@
import { Router } from "express"
import bodyParser from "body-parser"
import middlewares from "../middleware"
const route = Router()
export default (app) => {
app.use("/restock-notifications", route)
route.post(
"/variants/:variant_id",
bodyParser.raw({ type: "application/json" }),
middlewares.wrap(require("./add-email").default)
)
return app
}

View File

@@ -0,0 +1,21 @@
import { MigrationInterface, QueryRunner } from "typeorm"
export class restockNotification1617703530229 implements MigrationInterface {
name = "restockNotification1617703530229"
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TABLE "restock_notification" ("variant_id" character varying NOT NULL, "emails" jsonb NOT NULL, "created_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), CONSTRAINT "PK_49181ca04caac807fcec321705a" PRIMARY KEY ("variant_id"))`
)
await queryRunner.query(
`ALTER TABLE "restock_notification" ADD CONSTRAINT "FK_49181ca04caac807fcec321705a" FOREIGN KEY ("variant_id") REFERENCES "product_variant"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`
)
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "restock_notification" DROP CONSTRAINT "FK_49181ca04caac807fcec321705a"`
)
await queryRunner.query(`DROP TABLE "restock_notification"`)
}
}

View File

@@ -1,16 +1,10 @@
import { MedusaError } from "medusa-core-utils"
import { BaseService } from "medusa-interfaces"
import { RestockNotification } from "../models/restock-notification"
class RestockNotificationService extends BaseService {
constructor(
{
manager,
eventBusService,
productVariantService,
restockNotificationRepository,
},
options
) {
constructor({ manager, eventBusService, productVariantService }, options) {
super()
this.manager_ = manager
@@ -19,23 +13,17 @@ class RestockNotificationService extends BaseService {
this.productVariantService_ = productVariantService
this.restockNotificationRepo_ = restockNotificationRepository
this.eventBus_ = eventBusService
}
async retrieve(variantId) {
const restockRepo = this.manager_.getCustomRepository(
this.restockNotificationRepo_
)
const restockRepo = this.manager_.getRepository(RestockNotification)
return await restockRepo.findOne({ where: { variant_id: variantId } })
}
async addEmail(variantId, email) {
return this.atomicPhase_(async (manager) => {
const restockRepo = manager.getCustomRepository(
this.restockNotificationRepo_
)
const restockRepo = manager.getRepository(RestockNotification)
const existing = await this.retrieve(variantId)
if (existing) {
@@ -66,6 +54,13 @@ class RestockNotificationService extends BaseService {
})
}
async delete(variantId) {
return this.atomicPhase_(async (manager) => {
const restockRepo = manager.getRepository(RestockNotification)
return restockRepo.delete(variantId)
})
}
async triggerRestock(variantId) {
return this.atomicPhase_(async (manager) => {
const existing = await this.retrieve(variantId)
@@ -73,7 +68,13 @@ class RestockNotificationService extends BaseService {
return
}
const variant = await this.productVariantService_.retrieve(variantId)
if (variant.inventory_quantity > 0) {
await eventBus_
.withTransaction(manager)
.emit("restock_notification.restocked")
await this.delete(variantId)
}
})
}
}

View File

@@ -1,81 +1,19 @@
class VariantSubscriber {
constructor({
eventBusService,
restockNotificationService,
productVariantService,
}) {
constructor({ eventBusService, restockNotificationService }) {
this.restockNotificationService_ = restockNotificationService
eventBusService.subscribe("product_variant.updated", this.handleVariantUpdate)
eventBusService.subscribe(
"product_variant.updated",
this.handleVariantUpdate
)
}
registerClaim = async (data) => {
const { id } = data
const fromClaim = await this.claimService_.retrieve(id, {
relations: [
"order",
"order.payments",
"order.region",
"order.claims",
"order.discounts",
"claim_items",
"return_order",
"return_order.items",
"return_order.shipping_method",
"additional_items",
"shipping_address",
"shipping_methods",
],
})
const fromOrder = fromClaim.order
if (fromClaim.type === "replace") {
await this.brightpearlService_.createClaim(fromOrder, fromClaim)
} else if (fromClaim.type === "refund") {
await this.brightpearlService_.createClaimCredit(fromOrder, fromClaim)
handleVariantUpdate = async (data) => {
const { id, fields } = data
if (fields.includes("inventory_quantity")) {
return this.restockNotificationService_.triggerRestock(id)
}
}
registerShipment = async (data) => {
const { fulfillment_id } = data
const shipment = await this.fulfillmentService_.retrieve(fulfillment_id)
const noteId = shipment.metadata.goods_out_note
if (noteId) {
await this.brightpearlService_.registerGoodsOutTrackingNumber(
noteId,
shipment
)
await this.brightpearlService_.registerGoodsOutShipped(noteId, shipment)
}
}
registerReturn = async (data) => {
const { id, return_id } = data
const order = await this.orderService_.retrieve(id, {
relations: ["region", "payments"],
})
const fromReturn = await this.returnService_.retrieve(return_id, {
relations: ["items"],
})
return this.brightpearlService_
.createSalesCredit(order, fromReturn)
.catch((err) => console.log(err))
}
registerRefund = async (data) => {
const { id, refund_id } = data
const order = await this.orderService_.retrieve(id, {
relations: ["region", "payments"],
})
const refund = await this.paymentProviderService_.retrieveRefund(refund_id)
return this.brightpearlService_
.createRefundCredit(order, refund)
.catch((err) => console.log(err))
}
}
export default OrderSubscriber
export default VariantSubscriber

File diff suppressed because it is too large Load Diff

View File

@@ -1,16 +1,6 @@
class OrderSubscriber {
constructor({
totalsService,
orderService,
sendgridService,
notificationService,
fulfillmentService,
}) {
this.orderService_ = orderService
this.totalsService_ = totalsService
this.sendgridService_ = sendgridService
constructor({ notificationService }) {
this.notificationService_ = notificationService
this.fulfillmentService_ = fulfillmentService
this.notificationService_.subscribe("order.shipment_created", "sendgrid")
this.notificationService_.subscribe("order.gift_card_created", "sendgrid")
@@ -23,6 +13,10 @@ class OrderSubscriber {
this.notificationService_.subscribe("swap.created", "sendgrid")
this.notificationService_.subscribe("order.items_returned", "sendgrid")
this.notificationService_.subscribe("order.return_requested", "sendgrid")
this.notificationService_.subscribe(
"restock_notification.restocked",
"sendgrid"
)
}
}

View File

@@ -1,4 +1,5 @@
import glob from "glob"
import { EntitySchema } from "typeorm"
import {
BaseModel,
BaseService,
@@ -48,7 +49,8 @@ export default async ({ rootDirectory, container, app }) => {
await Promise.all(
resolved.map(async pluginDetails => {
registerModels(pluginDetails, container)
// registerModels(pluginDetails, container)
registerRepositories(pluginDetails, container)
await registerServices(pluginDetails, container)
registerMedusaApi(pluginDetails, container)
registerApi(pluginDetails, app, rootDirectory, container)
@@ -288,6 +290,34 @@ function registerSubscribers(pluginDetails, container) {
})
}
/**
* Registers a plugin's models at the right location in our container. Models
* must inherit from BaseModel. Models are registered directly in the container.
* Names are camelCase formatted and namespaced by the folder i.e:
* models/example-person -> examplePersonModel
* @param {object} pluginDetails - the plugin details including plugin options,
* version, id, resolved path, etc. See resolvePlugin
* @param {object} container - the container where the services will be
* registered
* @return {void}
*/
function registerRepositories(pluginDetails, container) {
const files = glob.sync(`${pluginDetails.resolve}/repositories/*.js`, {})
files.forEach(fn => {
const loaded = require(fn)
Object.entries(loaded).map(([key, val]) => {
if (typeof val === "function") {
const name = formatRegistrationName(fn)
console.log(name)
container.register({
[name]: asClass(val),
})
}
})
})
}
/**
* Registers a plugin's models at the right location in our container. Models
* must inherit from BaseModel. Models are registered directly in the container.
@@ -302,20 +332,19 @@ function registerSubscribers(pluginDetails, container) {
function registerModels(pluginDetails, container) {
const files = glob.sync(`${pluginDetails.resolve}/models/*.js`, {})
files.forEach(fn => {
const loaded = require(fn).default
const loaded = require(fn)
if (!(loaded.prototype instanceof BaseModel)) {
const logger = container.resolve("logger")
const message = `Models must inherit from BaseModel, please check ${fn}`
logger.error(message)
throw new Error(message)
}
Object.entries(loaded).map(([key, val]) => {
if (typeof val === "function" || val instanceof EntitySchema) {
if (config.register) {
const name = formatRegistrationName(fn)
container.register({
[name]: asClass(val),
})
const name = formatRegistrationName(fn)
container.register({
[name]: asFunction(
cradle => new loaded(cradle, pluginDetails.options)
).singleton(),
container.registerAdd("db_entities", asValue(val))
}
}
})
})
}