feat: return shipping and flow (#125)

Adds support for return shipping methods and changes returns to have a request/receive flow. Store operators should now first request a return, noting the line items that will be returned. When the inventory is received the return will be registered triggering the refund. Return shipping methods can now be created for all regions.
This commit is contained in:
Sebastian Rindom
2020-10-14 17:58:23 +02:00
committed by GitHub
parent 128351a8f0
commit c1e821d9d4
31 changed files with 1745 additions and 208 deletions
@@ -0,0 +1,15 @@
import { IdMap } from "medusa-test-utils"
export const documents = [
{
_id: IdMap.getId("doc"),
name: "test doc",
base_64: "verylongstring",
},
]
export const DocumentModelMock = {
findOne: jest.fn().mockImplementation(query => {
return Promise.resolve(documents[0])
}),
}
@@ -59,6 +59,7 @@ export const orders = {
],
fulfillments: [
{
_id: IdMap.getId("fulfillment"),
provider_id: "default_provider",
data: {},
},
@@ -137,6 +138,114 @@ export const orders = {
payment_status: "captured",
status: "completed",
},
returnedOrder: {
_id: IdMap.getId("returned-order"),
email: "oliver@test.dk",
billing_address: {
first_name: "Oli",
last_name: "Medusa",
address_1: "testaddress",
city: "LA",
country_code: "US",
postal_code: "90002",
},
shipping_address: {
first_name: "Oli",
last_name: "Medusa",
address_1: "testaddress",
city: "LA",
country_code: "US",
postal_code: "90002",
},
items: [
{
_id: IdMap.getId("existingLine"),
title: "merge line",
description: "This is a new line",
thumbnail: "test-img-yeah.com/thumb",
returned_quantity: 0,
content: {
unit_price: 123,
variant: {
_id: IdMap.getId("can-cover"),
},
product: {
_id: IdMap.getId("validId"),
},
quantity: 1,
},
quantity: 10,
},
],
region_id: IdMap.getId("region-france"),
customer_id: IdMap.getId("test-customer"),
payment_method: {
provider_id: "default_provider",
data: {
hi: "hi",
},
},
returns: [
{
_id: IdMap.getId("return"),
status: "requested",
shipping_method: {
_id: IdMap.getId("return-shipping"),
is_return: true,
name: "Return Shipping",
region_id: IdMap.getId("region-france"),
profile_id: IdMap.getId("default-profile"),
data: {
id: "return_shipment",
},
price: 2,
provider_id: "default_provider",
},
shipping_data: {
id: "return_shipment",
shipped: true,
},
documents: ["doc1234"],
items: [
{
item_id: IdMap.getId("existingLine"),
content: {
unit_price: 123,
variant: {
_id: IdMap.getId("can-cover"),
},
product: {
_id: IdMap.getId("validId"),
},
quantity: 1,
},
is_requested: true,
quantity: 10,
},
],
refund_amount: 1228,
},
],
shipping_methods: [
{
_id: IdMap.getId("expensiveShipping"),
name: "Expensive Shipping",
price: 100,
provider_id: "default_provider",
profile_id: IdMap.getId("default"),
},
{
_id: IdMap.getId("freeShipping"),
name: "Free Shipping",
price: 10,
provider_id: "default_provider",
profile_id: IdMap.getId("profile1"),
},
],
fulfillment_status: "fulfilled",
payment_status: "captured",
status: "completed",
},
orderToRefund: {
_id: IdMap.getId("refund-order"),
email: "oliver@test.dk",
@@ -192,6 +301,8 @@ export const orders = {
quantity: 1,
},
quantity: 10,
returned_quantity: 0,
metadata: {},
},
],
region_id: IdMap.getId("region-france"),
@@ -199,6 +310,48 @@ export const orders = {
payment_method: {
provider_id: "default_provider",
},
returns: [
{
_id: IdMap.getId("return"),
status: "requested",
shipping_method: {
_id: IdMap.getId("return-shipping"),
is_return: true,
name: "Return Shipping",
region_id: IdMap.getId("region-france"),
profile_id: IdMap.getId("default-profile"),
data: {
id: "return_shipment",
},
price: 2,
provider_id: "default_provider",
},
shipping_data: {
id: "return_shipment",
shipped: true,
},
documents: ["doc1234"],
items: [
{
item_id: IdMap.getId("existingLine"),
content: {
unit_price: 100,
variant: {
_id: IdMap.getId("can-cover"),
},
product: {
_id: IdMap.getId("product"),
},
quantity: 1,
},
is_requested: true,
quantity: 2,
metadata: {},
},
],
refund_amount: 246,
},
],
shipping_methods: [
{
provider_id: "default_provider",
@@ -220,6 +373,13 @@ export const orders = {
export const OrderModelMock = {
create: jest.fn().mockImplementation(data => Promise.resolve(data)),
updateOne: jest.fn().mockImplementation((query, update) => {
if (query._id === IdMap.getId("returned-order")) {
return Promise.resolve(orders.returnedOrder)
}
if (query._id === IdMap.getId("order-refund")) {
orders.orderToRefund.payment_status = "captured"
return Promise.resolve(orders.orderToRefund)
}
return Promise.resolve()
}),
deleteOne: jest.fn().mockReturnValue(Promise.resolve()),
@@ -250,6 +410,9 @@ export const OrderModelMock = {
if (query._id === IdMap.getId("processed-order")) {
return Promise.resolve(orders.processedOrder)
}
if (query._id === IdMap.getId("returned-order")) {
return Promise.resolve(orders.returnedOrder)
}
if (query._id === IdMap.getId("order-refund")) {
orders.orderToRefund.payment_status = "captured"
return Promise.resolve(orders.orderToRefund)
+16
View File
@@ -0,0 +1,16 @@
import mongoose from "mongoose"
import { BaseModel } from "medusa-interfaces"
class DocumentModel extends BaseModel {
static modelName = "Document"
static schema = {
base_64: { type: String, required: true },
name: { type: String, required: true },
type: { type: String, required: true },
created: { type: String, default: Date.now },
metadata: { type: mongoose.Schema.Types.Mixed, default: {} },
}
}
export default DocumentModel
+1
View File
@@ -38,6 +38,7 @@ class OrderModel extends BaseModel {
customer_id: { type: String },
payment_method: { type: PaymentMethodSchema, required: true },
shipping_methods: { type: [ShippingMethodSchema], required: true },
documents: { type: [String], default: [] },
created: { type: String, default: Date.now },
metadata: { type: mongoose.Schema.Types.Mixed, default: {} },
}
@@ -8,5 +8,6 @@ export default new mongoose.Schema({
tracking_numbers: { type: [String], default: [] },
shipped_at: { type: String },
is_canceled: { type: Boolean, default: false },
documents: { type: [String], default: [] },
metadata: { type: mongoose.Schema.Types.Mixed, default: {} },
})
@@ -0,0 +1,23 @@
/*******************************************************************************
*
******************************************************************************/
import mongoose from "mongoose"
/**
* @typedef ReturnLineItem
* @property {String} item_id
* @property {Object} content
* @property {Number} quantity
* @property {Boolean} is_requested
* @property {Boolean} is_registered
* @property {Object} metadata
*/
export default new mongoose.Schema({
item_id: { type: String, required: true, unique: true },
content: { type: mongoose.Schema.Types.Mixed, required: true },
quantity: { type: Number, required: true },
is_requested: { type: Boolean, required: true },
requested_quantity: { type: Number },
is_registered: { type: Boolean, default: false },
metadata: { type: mongoose.Schema.Types.Mixed, default: {} },
})
+9 -2
View File
@@ -1,8 +1,15 @@
import mongoose from "mongoose"
import ReturnLineItemSchema from "./return-line-item"
import ShippingMethodSchema from "./shipping-method"
export default new mongoose.Schema({
created: { type: String, default: Date.now },
status: { type: String, default: "requested" },
refund_amount: { type: Number, required: true },
items: { type: [mongoose.Schema.Types.Mixed], required: true },
items: { type: [ReturnLineItemSchema], required: true },
shipping_method: { type: ShippingMethodSchema, default: {} },
shipping_data: { type: mongoose.Schema.Types.Mixed, default: {} },
documents: { type: [String], default: [] },
received_at: { type: String },
created: { type: String, default: Date.now },
metadata: { type: mongoose.Schema.Types.Mixed, default: {} },
})
@@ -14,6 +14,7 @@ class ShippingOptionModel extends BaseModel {
data: { type: mongoose.Schema.Types.Mixed, default: {} },
price: { type: ShippingOptionPrice, required: true },
requirements: { type: [ShippingOptionRequirement], default: [] },
is_return: { type: Boolean, default: false },
metadata: { type: mongoose.Schema.Types.Mixed, default: {} },
}
}