Adds integration to send sales orders to brightpearl
This commit is contained in:
@@ -7,6 +7,39 @@ import errorHandler from "./middlewares/error-handler"
|
||||
export default (container, config) => {
|
||||
const app = Router()
|
||||
|
||||
app.post("/create-shipment/:order_id", async (req, res) => {
|
||||
const orderService = req.scope.resolve("orderService")
|
||||
const eventBus = req.scope.resolve("eventBusService")
|
||||
const order = await orderService.retrieve(req.params.order_id)
|
||||
|
||||
await orderService.createShipment(order._id, {
|
||||
item_ids: order.items.map(({ _id }) => `${_id}`),
|
||||
tracking_number: "1234",
|
||||
})
|
||||
|
||||
res.sendStatus(200)
|
||||
})
|
||||
|
||||
app.post("/run-hook/:order_id/capture", async (req, res) => {
|
||||
const orderService = req.scope.resolve("orderService")
|
||||
const eventBus = req.scope.resolve("eventBusService")
|
||||
const order = await orderService.retrieve(req.params.order_id)
|
||||
|
||||
eventBus.emit("order.payment_captured", order)
|
||||
|
||||
res.sendStatus(200)
|
||||
})
|
||||
|
||||
app.post("/run-hook/:order_id", async (req, res) => {
|
||||
const orderService = req.scope.resolve("orderService")
|
||||
const eventBus = req.scope.resolve("eventBusService")
|
||||
const order = await orderService.retrieve(req.params.order_id)
|
||||
|
||||
eventBus.emit("order.placed", order)
|
||||
|
||||
res.sendStatus(200)
|
||||
})
|
||||
|
||||
admin(app, container, config)
|
||||
store(app, container, config)
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import PaymentMethodSchema from "./schemas/payment-method"
|
||||
import ShippingMethodSchema from "./schemas/shipping-method"
|
||||
import AddressSchema from "./schemas/address"
|
||||
import DiscountSchema from "./schemas/discount"
|
||||
import ShipmentSchema from "./schemas/shipment"
|
||||
|
||||
class OrderModel extends BaseModel {
|
||||
static modelName = "Order"
|
||||
@@ -23,6 +24,8 @@ class OrderModel extends BaseModel {
|
||||
shipping_address: { type: AddressSchema, required: true },
|
||||
items: { type: [LineItemSchema], required: true },
|
||||
currency_code: { type: String, required: true },
|
||||
tax_rate: { type: Number, required: true },
|
||||
shipments: { type: [ShipmentSchema], default: [] },
|
||||
region_id: { type: String, required: true },
|
||||
discounts: { type: [DiscountSchema], default: [] },
|
||||
customer_id: { type: String },
|
||||
|
||||
@@ -7,6 +7,7 @@ class RegionModel extends BaseModel {
|
||||
name: { type: String, required: true },
|
||||
currency_code: { type: String, required: true },
|
||||
tax_rate: { type: Number, required: true, default: 0 },
|
||||
tax_code: { type: String },
|
||||
countries: { type: [String], default: [] },
|
||||
payment_providers: { type: [String], default: [] },
|
||||
fulfillment_providers: { type: [String], default: [] },
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import mongoose from "mongoose"
|
||||
|
||||
export default new mongoose.Schema({
|
||||
item_ids: { type: [String], required: true },
|
||||
tracking_number: { type: String, default: "" },
|
||||
metadata: { type: mongoose.Schema.Types.Mixed, default: {} },
|
||||
})
|
||||
@@ -5,6 +5,8 @@ import { BaseService } from "medusa-interfaces"
|
||||
class OrderService extends BaseService {
|
||||
static Events = {
|
||||
GIFT_CARD_CREATED: "order.gift_card_created",
|
||||
PAYMENT_CAPTURED: "order.payment_captured",
|
||||
SHIPMENT_CREATED: "order.shipment_created",
|
||||
PLACED: "order.placed",
|
||||
UPDATED: "order.updated",
|
||||
CANCELLED: "order.cancelled",
|
||||
@@ -352,6 +354,7 @@ class OrderService extends BaseService {
|
||||
email: cart.email,
|
||||
customer_id: cart.customer_id,
|
||||
cart_id: cart._id,
|
||||
tax_rate: region.tax_rate,
|
||||
currency_code: region.currency_code,
|
||||
}
|
||||
|
||||
@@ -366,6 +369,58 @@ class OrderService extends BaseService {
|
||||
.then(() => this.orderModel_.findOne({ cart_id: cart._id }))
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a shipment to the order to indicate that an order has left the warehouse
|
||||
*/
|
||||
async createShipment(orderId, shipment) {
|
||||
const order = await this.retrieve(orderId)
|
||||
|
||||
console.log(order)
|
||||
if (order.fulfillment_status === "shipped") {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
"Order has already been shipped"
|
||||
)
|
||||
}
|
||||
|
||||
const shipmentSchema = Validator.object({
|
||||
item_ids: Validator.array()
|
||||
.items(Validator.string())
|
||||
.required(),
|
||||
tracking_number: Validator.string().required(),
|
||||
})
|
||||
|
||||
const { value, error } = shipmentSchema.validate(shipment)
|
||||
if (error) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`Shipment not valid: ${error}`
|
||||
)
|
||||
}
|
||||
|
||||
const existing = order.shipments || []
|
||||
const shipments = [...existing, value]
|
||||
const allCovered = order.items.every(
|
||||
i => !!shipments.find(s => s.item_ids.includes(`${i._id}`))
|
||||
)
|
||||
|
||||
const update = {
|
||||
$push: { shipments: value },
|
||||
$set: {
|
||||
fulfillment_status: allCovered ? "shipped" : "partially_shipped",
|
||||
},
|
||||
}
|
||||
|
||||
// Add the shipment to the order
|
||||
return this.orderModel_.updateOne({ _id: orderId }, update).then(result => {
|
||||
this.eventBus_.emit(OrderService.Events.SHIPMENT_CREATED, {
|
||||
order_id: orderId,
|
||||
shipment,
|
||||
})
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an order
|
||||
* @param {object} order - the order to create
|
||||
@@ -535,14 +590,19 @@ class OrderService extends BaseService {
|
||||
|
||||
await paymentProvider.capturePayment(data)
|
||||
|
||||
return this.orderModel_.updateOne(
|
||||
{
|
||||
_id: orderId,
|
||||
},
|
||||
{
|
||||
$set: updateFields,
|
||||
}
|
||||
)
|
||||
return this.orderModel_
|
||||
.updateOne(
|
||||
{
|
||||
_id: orderId,
|
||||
},
|
||||
{
|
||||
$set: updateFields,
|
||||
}
|
||||
)
|
||||
.then(result => {
|
||||
this.eventBus_.emit(OrderService.Events.PAYMENT_CAPTURED, result)
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -252,6 +252,45 @@ class TotalsService extends BaseService {
|
||||
return discounts
|
||||
}
|
||||
|
||||
async getLineDiscounts(cart, discount) {
|
||||
const subtotal = this.getSubtotal(cart)
|
||||
const { type, allocation, value } = discount.discount_rule
|
||||
if (allocation === "total") {
|
||||
let percentage = 0
|
||||
if (type === "percentage") {
|
||||
percentage = value / 100
|
||||
} else if (type === "fixed") {
|
||||
percentage = value / subtotal
|
||||
}
|
||||
|
||||
return cart.items.map(item => {
|
||||
const lineTotal = item.content.unit_price * item.quantity
|
||||
|
||||
return {
|
||||
item,
|
||||
amount: lineTotal * percentage,
|
||||
}
|
||||
})
|
||||
} else if (allocation === "item") {
|
||||
const allocationDiscounts = this.getAllocationItemDiscounts(
|
||||
discount,
|
||||
cart,
|
||||
type
|
||||
)
|
||||
return cart.items.map(item => {
|
||||
const discounted = allocationDiscounts.find(a =>
|
||||
a.lineItem._id.equals(item._id)
|
||||
)
|
||||
return {
|
||||
item,
|
||||
amount: !!discounted ? discounted.amount : 0,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return cart.items.map(i => ({ item: i, amount: 0 }))
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the total discount amount for each of the different supported
|
||||
* discount types. If discounts aren't present or invalid returns 0.
|
||||
|
||||
@@ -39,7 +39,11 @@ class OrderSubscriber {
|
||||
})
|
||||
|
||||
this.eventBus_.subscribe("order.placed", async order => {
|
||||
await this.cartService_.delete(order.cart_id)
|
||||
await this.cartService_.delete(order.cart_id).catch(err => {
|
||||
if (err.type !== "not_found") {
|
||||
throw err
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
this.eventBus_.subscribe("order.placed", this.handleDiscounts)
|
||||
|
||||
Reference in New Issue
Block a user