feat(medusa,brightpearl,segment,webshipper): claims (#163)

* chore: create tests

* chore: models

* fix: passing initial tests

* test: adds integration test

* test: clean up integration implementation

* fix: claims

* fix: brightpearl + webshipper

* tests: passing

* fix: update claim items

* fix: adds gitignore

* fix: pr comments

* fix: single migration

* fix(medusa-plugin-segment): adds item claimed event to segment
This commit is contained in:
Sebastian Rindom
2021-02-03 09:49:12 +01:00
committed by GitHub
parent 0e0dfea571
commit 690d339667
60 changed files with 9429 additions and 189 deletions
@@ -11,6 +11,7 @@ class BrightpearlService extends BaseService {
regionService,
orderService,
swapService,
claimService,
discountService,
},
options
@@ -25,6 +26,7 @@ class BrightpearlService extends BaseService {
this.discountService_ = discountService
this.oauthService_ = oauthService
this.swapService_ = swapService
this.claimService_ = claimService
}
async getClient() {
@@ -609,6 +611,63 @@ class BrightpearlService extends BaseService {
})
}
async createClaimCredit(fromOrder, fromClaim) {
const region = fromOrder.region
const client = await this.getClient()
const authData = await this.getAuthData()
const orderId = fromOrder.metadata.brightpearl_sales_order_id
const cIndex = fromOrder.claims.findIndex((c) => fromClaim.id === c.id)
if (orderId) {
const parentSo = await client.orders.retrieve(orderId)
const order = {
currency: parentSo.currency,
ref: `${parentSo.ref}-C${cIndex + 1}`,
externalRef: `${parentSo.externalRef}.${fromClaim.id}`,
channelId: this.options.channel_id || `1`,
installedIntegrationInstanceId: authData.installation_instance_id,
customer: parentSo.customer,
delivery: parentSo.delivery,
parentId: orderId,
rows: [
{
name: `#${fromOrder.display_id}: Claim ${fromClaim.id}`,
net: this.bpnum_(
fromClaim.refund_amount,
10000 / (100 + fromOrder.tax_rate)
),
tax: this.bpnum_(
fromClaim.refund_amount * (1 - 100 / (100 + fromOrder.tax_rate))
),
taxCode: region.tax_code,
nominalCode: this.options.sales_account_code || `4000`,
quantity: 1,
},
],
}
return client.orders
.createCredit(order)
.then(async (creditId) => {
const paymentMethod = fromOrder.payments[0]
const paymentType = "PAYMENT"
const payment = {
transactionRef: `${paymentMethod.id}.${paymentType}-${fromClaim.id}`,
transactionCode: fromClaim.id,
paymentMethodCode: this.options.payment_method_code || "1220",
orderId: creditId,
currencyIsoCode: fromOrder.currency_code.toUpperCase(),
amountPaid: this.bpnum_(fromClaim.refund_amount),
paymentDate: new Date(),
paymentType,
}
await client.payments.create(payment)
})
.catch((err) => console.log(err.response.data.errors))
}
}
async createSwapCredit(fromOrder, fromSwap) {
const region = fromOrder.region
const client = await this.getClient()
@@ -717,7 +776,10 @@ class BrightpearlService extends BaseService {
return client.payments.create(payment)
}
async getBrightpearlRows(fromOrder) {
async getBrightpearlRows(
fromOrder,
config = { include_price: true, is_claim: false }
) {
const { region } = fromOrder
const discount = fromOrder.discounts.find(
({ rule }) => rule.type !== "free_shipping"
@@ -741,15 +803,27 @@ class BrightpearlService extends BaseService {
} else {
row.name = item.title
}
row.net = this.bpnum_(item.unit_price * item.quantity - ld.amount)
row.tax = this.bpnum_(
item.unit_price * item.quantity - ld.amount,
fromOrder.tax_rate
)
if (config.include_price) {
row.net = this.bpnum_(item.unit_price * item.quantity - ld.amount)
row.tax = this.bpnum_(
item.unit_price * item.quantity - ld.amount,
fromOrder.tax_rate
)
} else if (config.is_claim) {
row.net = await this.retrieveProductPrice(
bpProduct.productId,
this.options.cost_price_list || `1`
)
row.tax = this.bpnum_(row.net * 100, fromOrder.tax_rate)
}
row.quantity = item.quantity
row.taxCode = region.tax_code
row.externalRef = item.id
row.nominalCode = this.options.sales_account_code || "4000"
row.nominalCode = config.is_claim
? this.options.claims_account_code || "4000"
: this.options.sales_account_code || "4000"
if (item.is_giftcard) {
row.nominalCode = this.options.gift_card_account_code || "4000"
@@ -793,6 +867,106 @@ class BrightpearlService extends BaseService {
return lines
}
async createClaim(fromOrder, fromClaim) {
const client = await this.getClient()
let customer = await this.retrieveCustomerByEmail(fromOrder.email)
if (!customer) {
customer = await this.createCustomer({
...fromOrder,
...fromClaim,
})
}
const authData = await this.getAuthData()
const cIndex = fromOrder.claims.findIndex((s) => fromClaim.id === s.id)
const { shipping_address } = fromClaim
const order = {
currency: {
code: this.options.base_currency || `EUR`,
},
priceListId: this.options.cost_price_list || `1`,
ref: `${fromOrder.display_id}-C${cIndex + 1}`,
externalRef: `${fromOrder.id}.${fromClaim.id}`,
channelId: this.options.channel_id || `1`,
installedIntegrationInstanceId: authData.installation_instance_id,
statusId:
this.options.claim_status_id || this.options.default_status_id || `3`,
customer: {
id: customer.contactId,
address: {
addressFullName: `${shipping_address.first_name} ${shipping_address.last_name}`,
addressLine1: shipping_address.address_1,
addressLine2: shipping_address.address_2,
postalCode: shipping_address.postal_code,
countryIsoCode: shipping_address.country_code,
telephone: shipping_address.phone,
email: fromOrder.email,
},
},
delivery: {
shippingMethodId: 0,
address: {
addressFullName: `${shipping_address.first_name} ${shipping_address.last_name}`,
addressLine1: shipping_address.address_1,
addressLine2: shipping_address.address_2,
postalCode: shipping_address.postal_code,
countryIsoCode: shipping_address.country_code,
telephone: shipping_address.phone,
email: fromOrder.email,
},
},
rows: await this.getBrightpearlRows(
{
region: fromOrder.region,
discounts: fromOrder.discounts,
tax_rate: fromOrder.tax_rate,
items: fromClaim.additional_items,
shipping_methods: [],
},
{ include_price: false, is_claim: true }
),
}
return client.orders
.create(order)
.then(async (salesOrderId) => {
const order = await client.orders.retrieve(salesOrderId)
await client.warehouses.createReservation(order, this.options.warehouse)
const total = order.rows.reduce((acc, next) => {
return acc + parseFloat(next.net) + parseFloat(next.tax)
}, 0)
const paymentMethod = fromOrder.payments[0]
const paymentType = "RECEIPT"
const payment = {
transactionRef: `${paymentMethod.id}.${paymentType}-${fromClaim.id}`,
transactionCode: fromOrder.id,
paymentMethodCode: this.options.payment_method_code || "1220",
orderId: salesOrderId,
currencyIsoCode: this.options.base_currency || "EUR",
amountPaid: total,
paymentDate: new Date(),
paymentType,
}
await client.payments.create(payment)
return this.claimService_.update(fromClaim.id, {
metadata: {
brightpearl_sales_order_id: salesOrderId,
},
})
})
.catch((err) => {
// console.log(err.response.data)
throw err
})
}
async retrieveCustomerByEmail(email) {
const client = await this.getClient()
return client.customers.retrieveByEmail(email).then((customers) => {
@@ -818,6 +992,20 @@ class BrightpearlService extends BaseService {
})
}
async retrieveProductPrice(id, priceList) {
const client = await this.getClient()
return client.products.retrievePrice(id).then((data) => {
if (!data.priceLists.length) {
return null
} else {
const found = data.priceLists.find(
(p) => `${p.priceListId}` === `${priceList}`
)
return found.quantityPrice["1"]
}
})
}
async createFulfillmentFromGoodsOut(id) {
const client = await this.getClient()
@@ -851,12 +1039,20 @@ class BrightpearlService extends BaseService {
.filter((i) => !!i)
// Orders with a concatenated externalReference are swap orders
const [_, swapId] = order.externalRef.split(".")
const [_, partId] = order.externalRef.split(".")
if (swapId) {
return this.swapService_.createFulfillment(swapId, {
goods_out_note: id,
})
if (partId) {
if (partId.startsWith("claim")) {
return this.claimService_.createFulfillment(partId, {
goods_out_note: id,
})
}
if (partId.startsWith("swap")) {
return this.swapService_.createFulfillment(partId, {
goods_out_note: id,
})
}
}
return this.orderService_.createFulfillment(order.externalRef, lines, {
@@ -6,6 +6,7 @@ class OrderSubscriber {
returnService,
paymentProviderService,
brightpearlService,
claimService,
fulfillmentService,
}) {
this.orderService_ = orderService
@@ -14,9 +15,12 @@ class OrderSubscriber {
this.returnService_ = returnService
this.paymentProviderService_ = paymentProviderService
this.fulfillmentService_ = fulfillmentService
this.claimService_ = claimService
eventBusService.subscribe("order.placed", this.sendToBrightpearl)
eventBusService.subscribe("claim.created", this.registerClaim)
eventBusService.subscribe("order.refund_created", this.registerRefund)
eventBusService.subscribe("order.items_returned", this.registerReturn)
@@ -28,6 +32,7 @@ class OrderSubscriber {
eventBusService.subscribe("order.shipment_created", this.registerShipment)
eventBusService.subscribe("swap.shipment_created", this.registerShipment)
eventBusService.subscribe("claim.shipment_created", this.registerShipment)
// Before we initiate a swap we wait for the payment and the return
eventBusService.subscribe(
@@ -86,6 +91,34 @@ class OrderSubscriber {
await this.brightpearlService_.createSwapOrder(fromOrder, fromSwap)
}
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)
}
}
registerShipment = async (data) => {
const { fulfillment_id } = data
const shipment = await this.fulfillmentService_.retrieve(fulfillment_id)
@@ -301,6 +301,13 @@ class BrightpearlClient {
return this.buildSearchResults_(data.response)
})
},
retrievePrice: (productId) => {
return this.client_
.request({
url: `/product-service/product-price/${productId}`,
})
.then(({ data }) => data.response.length && data.response[0])
},
}
}