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:
@@ -0,0 +1,13 @@
|
||||
let ignore = [`**/dist`];
|
||||
|
||||
// Jest needs to compile this code, but generally we don't want this copied
|
||||
// to output folders
|
||||
if (process.env.NODE_ENV !== `test`) {
|
||||
ignore.push(`**/__tests__`);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
sourceMaps: true,
|
||||
presets: ["babel-preset-medusa-package"],
|
||||
ignore,
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
dist
|
||||
node_modules
|
||||
*yarn-error.log
|
||||
|
||||
@@ -0,0 +1,469 @@
|
||||
const { dropDatabase } = require("pg-god");
|
||||
const path = require("path");
|
||||
|
||||
const setupServer = require("../../../helpers/setup-server");
|
||||
const { useApi } = require("../../../helpers/use-api");
|
||||
const { initDb } = require("../../../helpers/use-db");
|
||||
|
||||
const orderSeeder = require("../../helpers/order-seeder");
|
||||
const adminSeeder = require("../../helpers/admin-seeder");
|
||||
|
||||
jest.setTimeout(30000);
|
||||
|
||||
describe("/admin/orders", () => {
|
||||
let medusaProcess;
|
||||
let dbConnection;
|
||||
|
||||
beforeAll(async () => {
|
||||
const cwd = path.resolve(path.join(__dirname, "..", ".."));
|
||||
dbConnection = await initDb({ cwd });
|
||||
medusaProcess = await setupServer({ cwd });
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await dbConnection.close();
|
||||
await dropDatabase({ databaseName: "medusa-integration" });
|
||||
|
||||
medusaProcess.kill();
|
||||
});
|
||||
|
||||
describe("GET /admin/orders", () => {
|
||||
beforeEach(async () => {
|
||||
try {
|
||||
await adminSeeder(dbConnection);
|
||||
await orderSeeder(dbConnection);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
const manager = dbConnection.manager;
|
||||
await manager.query(`DELETE FROM "cart"`);
|
||||
await manager.query(`DELETE FROM "fulfillment"`);
|
||||
await manager.query(`DELETE FROM "swap"`);
|
||||
await manager.query(`DELETE FROM "return"`);
|
||||
await manager.query(`DELETE FROM "claim_image"`);
|
||||
await manager.query(`DELETE FROM "claim_tag"`);
|
||||
await manager.query(`DELETE FROM "claim_item"`);
|
||||
await manager.query(`DELETE FROM "claim_order"`);
|
||||
await manager.query(`DELETE FROM "line_item"`);
|
||||
await manager.query(`DELETE FROM "money_amount"`);
|
||||
await manager.query(`DELETE FROM "product_variant"`);
|
||||
await manager.query(`DELETE FROM "product"`);
|
||||
await manager.query(`DELETE FROM "shipping_method"`);
|
||||
await manager.query(`DELETE FROM "shipping_option"`);
|
||||
await manager.query(`DELETE FROM "discount"`);
|
||||
await manager.query(`DELETE FROM "payment"`);
|
||||
await manager.query(`DELETE FROM "order"`);
|
||||
await manager.query(`DELETE FROM "customer"`);
|
||||
await manager.query(
|
||||
`UPDATE "country" SET region_id=NULL WHERE iso_2 = 'us'`
|
||||
);
|
||||
await manager.query(`DELETE FROM "region"`);
|
||||
await manager.query(`DELETE FROM "user"`);
|
||||
});
|
||||
|
||||
it("creates a cart", async () => {
|
||||
const api = useApi();
|
||||
|
||||
const response = await api
|
||||
.get("/admin/orders", {
|
||||
headers: {
|
||||
Authorization: "Bearer test_token",
|
||||
},
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
});
|
||||
expect(response.status).toEqual(200);
|
||||
});
|
||||
});
|
||||
|
||||
describe("POST /admin/orders/:id/claims", () => {
|
||||
beforeEach(async () => {
|
||||
try {
|
||||
await adminSeeder(dbConnection);
|
||||
await orderSeeder(dbConnection);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
const manager = dbConnection.manager;
|
||||
await manager.query(`DELETE FROM "cart"`);
|
||||
await manager.query(`DELETE FROM "fulfillment_item"`);
|
||||
await manager.query(`DELETE FROM "fulfillment"`);
|
||||
await manager.query(`DELETE FROM "swap"`);
|
||||
await manager.query(`DELETE FROM "return"`);
|
||||
await manager.query(`DELETE FROM "claim_image"`);
|
||||
await manager.query(`DELETE FROM "claim_tag"`);
|
||||
await manager.query(`DELETE FROM "claim_item"`);
|
||||
await manager.query(`DELETE FROM "shipping_method"`);
|
||||
await manager.query(`DELETE FROM "line_item"`);
|
||||
await manager.query(`DELETE FROM "claim_order"`);
|
||||
await manager.query(`DELETE FROM "money_amount"`);
|
||||
await manager.query(`DELETE FROM "product_variant"`);
|
||||
await manager.query(`DELETE FROM "product"`);
|
||||
await manager.query(`DELETE FROM "shipping_option"`);
|
||||
await manager.query(`DELETE FROM "discount"`);
|
||||
await manager.query(`DELETE FROM "payment"`);
|
||||
await manager.query(`DELETE FROM "order"`);
|
||||
await manager.query(`DELETE FROM "customer"`);
|
||||
await manager.query(
|
||||
`UPDATE "country" SET region_id=NULL WHERE iso_2 = 'us'`
|
||||
);
|
||||
await manager.query(`DELETE FROM "region"`);
|
||||
await manager.query(`DELETE FROM "user"`);
|
||||
});
|
||||
|
||||
it("creates a claim", async () => {
|
||||
const api = useApi();
|
||||
|
||||
const response = await api.post(
|
||||
"/admin/orders/test-order/claims",
|
||||
{
|
||||
type: "replace",
|
||||
claim_items: [
|
||||
{
|
||||
item_id: "test-item",
|
||||
quantity: 1,
|
||||
reason: "production_failure",
|
||||
tags: ["fluff"],
|
||||
images: ["https://test.image.com"],
|
||||
},
|
||||
],
|
||||
additional_items: [
|
||||
{
|
||||
variant_id: "test-variant",
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
authorization: "Bearer test_token",
|
||||
},
|
||||
}
|
||||
);
|
||||
expect(response.status).toEqual(200);
|
||||
|
||||
expect(response.data.order.claims[0].claim_items).toEqual([
|
||||
expect.objectContaining({
|
||||
item_id: "test-item",
|
||||
quantity: 1,
|
||||
reason: "production_failure",
|
||||
images: [
|
||||
expect.objectContaining({
|
||||
url: "https://test.image.com",
|
||||
}),
|
||||
],
|
||||
tags: [
|
||||
expect.objectContaining({
|
||||
value: "fluff",
|
||||
}),
|
||||
],
|
||||
}),
|
||||
]);
|
||||
|
||||
expect(response.data.order.claims[0].additional_items).toEqual([
|
||||
expect.objectContaining({
|
||||
variant_id: "test-variant",
|
||||
quantity: 1,
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
it("updates a claim", async () => {
|
||||
const api = useApi();
|
||||
|
||||
const response = await api.post(
|
||||
"/admin/orders/test-order/claims",
|
||||
{
|
||||
type: "replace",
|
||||
claim_items: [
|
||||
{
|
||||
item_id: "test-item",
|
||||
quantity: 1,
|
||||
reason: "production_failure",
|
||||
tags: ["fluff"],
|
||||
images: ["https://test.image.com"],
|
||||
},
|
||||
],
|
||||
additional_items: [
|
||||
{
|
||||
variant_id: "test-variant",
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
authorization: "Bearer test_token",
|
||||
},
|
||||
}
|
||||
);
|
||||
expect(response.status).toEqual(200);
|
||||
|
||||
const cid = response.data.order.claims[0].id;
|
||||
const { status, data: updateData } = await api.post(
|
||||
`/admin/orders/test-order/claims/${cid}`,
|
||||
{
|
||||
shipping_methods: [
|
||||
{
|
||||
id: "test-method",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
authorization: "bearer test_token",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
expect(status).toEqual(200);
|
||||
expect(updateData.order.claims[0].shipping_methods).toEqual([
|
||||
expect.objectContaining({
|
||||
id: "test-method",
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
it("updates claim items", async () => {
|
||||
const api = useApi();
|
||||
|
||||
const response = await api.post(
|
||||
"/admin/orders/test-order/claims",
|
||||
{
|
||||
type: "replace",
|
||||
claim_items: [
|
||||
{
|
||||
item_id: "test-item",
|
||||
quantity: 1,
|
||||
reason: "production_failure",
|
||||
tags: ["fluff"],
|
||||
images: ["https://test.image.com"],
|
||||
},
|
||||
],
|
||||
additional_items: [
|
||||
{
|
||||
variant_id: "test-variant",
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
authorization: "Bearer test_token",
|
||||
},
|
||||
}
|
||||
);
|
||||
expect(response.status).toEqual(200);
|
||||
|
||||
let claim = response.data.order.claims[0];
|
||||
const cid = claim.id;
|
||||
const { status, data: updateData } = await api.post(
|
||||
`/admin/orders/test-order/claims/${cid}`,
|
||||
{
|
||||
claim_items: claim.claim_items.map((i) => ({
|
||||
id: i.id,
|
||||
note: "Something new",
|
||||
images: [
|
||||
...i.images.map((i) => ({ id: i.id })),
|
||||
{ url: "https://new.com/image" },
|
||||
],
|
||||
tags: [
|
||||
{ value: "completely" },
|
||||
{ value: "NEW" },
|
||||
{ value: " tags" },
|
||||
],
|
||||
})),
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
authorization: "bearer test_token",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
expect(status).toEqual(200);
|
||||
expect(updateData.order.claims.length).toEqual(1);
|
||||
|
||||
claim = updateData.order.claims[0];
|
||||
|
||||
expect(claim.claim_items.length).toEqual(1);
|
||||
expect(claim.claim_items).toEqual([
|
||||
expect.objectContaining({
|
||||
id: claim.claim_items[0].id,
|
||||
reason: "production_failure",
|
||||
note: "Something new",
|
||||
images: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
url: "https://test.image.com",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
url: "https://new.com/image",
|
||||
}),
|
||||
]),
|
||||
tags: expect.arrayContaining([
|
||||
expect.objectContaining({ value: "completely" }),
|
||||
expect.objectContaining({ value: "new" }),
|
||||
expect.objectContaining({ value: "tags" }),
|
||||
]),
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
it("updates claim items - removes image", async () => {
|
||||
const api = useApi();
|
||||
|
||||
const response = await api.post(
|
||||
"/admin/orders/test-order/claims",
|
||||
{
|
||||
type: "replace",
|
||||
claim_items: [
|
||||
{
|
||||
item_id: "test-item",
|
||||
quantity: 1,
|
||||
reason: "production_failure",
|
||||
tags: ["fluff"],
|
||||
images: ["https://test.image.com"],
|
||||
},
|
||||
],
|
||||
additional_items: [
|
||||
{
|
||||
variant_id: "test-variant",
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
authorization: "Bearer test_token",
|
||||
},
|
||||
}
|
||||
);
|
||||
expect(response.status).toEqual(200);
|
||||
|
||||
let claim = response.data.order.claims[0];
|
||||
const cid = claim.id;
|
||||
const { status, data: updateData } = await api.post(
|
||||
`/admin/orders/test-order/claims/${cid}`,
|
||||
{
|
||||
claim_items: claim.claim_items.map((i) => ({
|
||||
id: i.id,
|
||||
note: "Something new",
|
||||
images: [],
|
||||
tags: [
|
||||
{ value: "completely" },
|
||||
{ value: "NEW" },
|
||||
{ value: " tags" },
|
||||
],
|
||||
})),
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
authorization: "bearer test_token",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
expect(status).toEqual(200);
|
||||
expect(updateData.order.claims.length).toEqual(1);
|
||||
|
||||
claim = updateData.order.claims[0];
|
||||
|
||||
expect(claim.claim_items.length).toEqual(1);
|
||||
expect(claim.claim_items).toEqual([
|
||||
expect.objectContaining({
|
||||
id: claim.claim_items[0].id,
|
||||
reason: "production_failure",
|
||||
note: "Something new",
|
||||
images: [],
|
||||
tags: expect.arrayContaining([
|
||||
expect.objectContaining({ value: "completely" }),
|
||||
expect.objectContaining({ value: "new" }),
|
||||
expect.objectContaining({ value: "tags" }),
|
||||
]),
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
it("fulfills a claim", async () => {
|
||||
const api = useApi();
|
||||
|
||||
const response = await api
|
||||
.post(
|
||||
"/admin/orders/test-order/claims",
|
||||
{
|
||||
type: "replace",
|
||||
shipping_methods: [
|
||||
{
|
||||
id: "test-method",
|
||||
},
|
||||
],
|
||||
claim_items: [
|
||||
{
|
||||
item_id: "test-item",
|
||||
quantity: 1,
|
||||
reason: "production_failure",
|
||||
tags: ["fluff"],
|
||||
images: ["https://test.image.com"],
|
||||
},
|
||||
],
|
||||
additional_items: [
|
||||
{
|
||||
variant_id: "test-variant",
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: "Bearer test_token",
|
||||
},
|
||||
}
|
||||
)
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
});
|
||||
|
||||
const cid = response.data.order.claims[0].id;
|
||||
const fulRes = await api.post(
|
||||
`/admin/orders/test-order/claims/${cid}/fulfillments`,
|
||||
{},
|
||||
{
|
||||
headers: {
|
||||
Authorization: "Bearer test_token",
|
||||
},
|
||||
}
|
||||
);
|
||||
expect(fulRes.status).toEqual(200);
|
||||
expect(fulRes.data.order.claims).toEqual([
|
||||
expect.objectContaining({
|
||||
id: cid,
|
||||
order_id: "test-order",
|
||||
fulfillment_status: "fulfilled",
|
||||
}),
|
||||
]);
|
||||
|
||||
const fid = fulRes.data.order.claims[0].fulfillments[0].id;
|
||||
const iid = fulRes.data.order.claims[0].additional_items[0].id;
|
||||
expect(fulRes.data.order.claims[0].fulfillments).toEqual([
|
||||
expect.objectContaining({
|
||||
items: [
|
||||
{
|
||||
fulfillment_id: fid,
|
||||
item_id: iid,
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
}),
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,74 @@
|
||||
const { dropDatabase } = require("pg-god");
|
||||
const path = require("path");
|
||||
const { Region } = require("@medusajs/medusa");
|
||||
|
||||
const setupServer = require("../../../helpers/setup-server");
|
||||
const { useApi } = require("../../../helpers/use-api");
|
||||
const { initDb } = require("../../../helpers/use-db");
|
||||
|
||||
jest.setTimeout(30000);
|
||||
|
||||
describe("/store/carts", () => {
|
||||
let medusaProcess;
|
||||
let dbConnection;
|
||||
|
||||
beforeAll(async () => {
|
||||
const cwd = path.resolve(path.join(__dirname, "..", ".."));
|
||||
dbConnection = await initDb({ cwd });
|
||||
medusaProcess = await setupServer({ cwd });
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
dbConnection.close();
|
||||
dropDatabase({ databaseName: "medusa-integration" });
|
||||
|
||||
medusaProcess.kill();
|
||||
});
|
||||
|
||||
describe("POST /store/carts", () => {
|
||||
beforeEach(async () => {
|
||||
const manager = dbConnection.manager;
|
||||
await manager.insert(Region, {
|
||||
id: "region",
|
||||
name: "Test Region",
|
||||
currency_code: "usd",
|
||||
tax_rate: 0,
|
||||
});
|
||||
await manager.query(
|
||||
`UPDATE "country" SET region_id='region' WHERE iso_2 = 'us'`
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
const manager = dbConnection.manager;
|
||||
await manager.query(`DELETE FROM "cart"`);
|
||||
await manager.query(
|
||||
`UPDATE "country" SET region_id=NULL WHERE iso_2 = 'us'`
|
||||
);
|
||||
await manager.query(`DELETE FROM "region"`);
|
||||
});
|
||||
|
||||
it("creates a cart", async () => {
|
||||
const api = useApi();
|
||||
|
||||
const response = await api.post("/store/carts");
|
||||
expect(response.status).toEqual(200);
|
||||
|
||||
const getRes = await api.post(`/store/carts/${response.data.cart.id}`);
|
||||
expect(getRes.status).toEqual(200);
|
||||
});
|
||||
|
||||
it("creates a cart with country", async () => {
|
||||
const api = useApi();
|
||||
|
||||
const response = await api.post("/store/carts", {
|
||||
country_code: "us",
|
||||
});
|
||||
expect(response.status).toEqual(200);
|
||||
expect(response.data.cart.shipping_address.country_code).toEqual("us");
|
||||
|
||||
const getRes = await api.post(`/store/carts/${response.data.cart.id}`);
|
||||
expect(getRes.status).toEqual(200);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
const Scrypt = require("scrypt-kdf");
|
||||
const { User } = require("@medusajs/medusa");
|
||||
|
||||
module.exports = async (connection, data = {}) => {
|
||||
const manager = connection.manager;
|
||||
|
||||
const buf = await Scrypt.kdf("secret_password", { logN: 1, r: 1, p: 1 });
|
||||
const password_hash = buf.toString("base64");
|
||||
|
||||
await manager.insert(User, {
|
||||
id: "admin_user",
|
||||
email: "admin@medusa.js",
|
||||
api_token: "test_token",
|
||||
password_hash,
|
||||
...data,
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,147 @@
|
||||
const {
|
||||
ShippingProfile,
|
||||
Customer,
|
||||
MoneyAmount,
|
||||
LineItem,
|
||||
Country,
|
||||
ShippingOption,
|
||||
ShippingMethod,
|
||||
Product,
|
||||
ProductVariant,
|
||||
Region,
|
||||
Order,
|
||||
} = require("@medusajs/medusa");
|
||||
|
||||
module.exports = async (connection, data = {}) => {
|
||||
const manager = connection.manager;
|
||||
|
||||
const defaultProfile = await manager.findOne(ShippingProfile, {
|
||||
type: "default",
|
||||
});
|
||||
|
||||
await manager.insert(Product, {
|
||||
id: "test-product",
|
||||
title: "test product",
|
||||
profile_id: defaultProfile.id,
|
||||
options: [{ id: "test-option", title: "Size" }],
|
||||
});
|
||||
|
||||
await manager.insert(ProductVariant, {
|
||||
id: "test-variant",
|
||||
title: "test variant",
|
||||
product_id: "test-product",
|
||||
inventory_quantity: 1,
|
||||
options: [
|
||||
{
|
||||
option_id: "test-option",
|
||||
value: "Size",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const ma = manager.create(MoneyAmount, {
|
||||
variant_id: "test-variant",
|
||||
currency_code: "usd",
|
||||
amount: 8000,
|
||||
});
|
||||
await manager.save(ma);
|
||||
|
||||
await manager.insert(Region, {
|
||||
id: "test-region",
|
||||
name: "Test Region",
|
||||
currency_code: "usd",
|
||||
tax_rate: 0,
|
||||
});
|
||||
|
||||
await manager.query(
|
||||
`UPDATE "country" SET region_id='test-region' WHERE iso_2 = 'us'`
|
||||
);
|
||||
|
||||
await manager.insert(Customer, {
|
||||
id: "test-customer",
|
||||
email: "test@email.com",
|
||||
});
|
||||
|
||||
await manager.insert(ShippingOption, {
|
||||
id: "test-option",
|
||||
name: "test-option",
|
||||
provider_id: "test-ful",
|
||||
region_id: "test-region",
|
||||
profile_id: defaultProfile.id,
|
||||
price_type: "flat_rate",
|
||||
amount: 1000,
|
||||
data: {},
|
||||
});
|
||||
|
||||
const order = manager.create(Order, {
|
||||
id: "test-order",
|
||||
customer_id: "test-customer",
|
||||
email: "test@email.com",
|
||||
billing_address: {
|
||||
id: "test-billing-address",
|
||||
first_name: "lebron",
|
||||
},
|
||||
shipping_address: {
|
||||
id: "test-shipping-address",
|
||||
first_name: "lebron",
|
||||
country_code: "us",
|
||||
},
|
||||
region_id: "test-region",
|
||||
currency_code: "usd",
|
||||
tax_rate: 0,
|
||||
discounts: [
|
||||
{
|
||||
id: "test-discount",
|
||||
code: "TEST134",
|
||||
is_dynamic: false,
|
||||
rule: {
|
||||
id: "test-rule",
|
||||
description: "Test Discount",
|
||||
type: "percentage",
|
||||
value: 10,
|
||||
allocation: "total",
|
||||
},
|
||||
is_disabled: false,
|
||||
regions: [
|
||||
{
|
||||
id: "test-region",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
payments: [
|
||||
{
|
||||
id: "test-payment",
|
||||
amount: 10000,
|
||||
currency_code: "usd",
|
||||
amount_refunded: 0,
|
||||
provider_id: "test",
|
||||
data: {},
|
||||
},
|
||||
],
|
||||
items: [
|
||||
{
|
||||
id: "test-item",
|
||||
fulfilled_quantity: 1,
|
||||
title: "Line Item",
|
||||
description: "Line Item Desc",
|
||||
thumbnail: "https://test.js/1234",
|
||||
unit_price: 8000,
|
||||
quantity: 1,
|
||||
variant_id: "test-variant",
|
||||
},
|
||||
],
|
||||
...data,
|
||||
});
|
||||
|
||||
await manager.save(order);
|
||||
|
||||
await manager.insert(ShippingMethod, {
|
||||
id: "test-method",
|
||||
order_id: "test-order",
|
||||
shipping_option_id: "test-option",
|
||||
order_id: "test-order",
|
||||
price: 1000,
|
||||
data: {},
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
setupFilesAfterEnv: ["<rootDir>/setup.js"],
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
module.exports = {
|
||||
plugins: [],
|
||||
projectConfig: {
|
||||
// redis_url: REDIS_URL,
|
||||
database_url: "postgres://localhost/medusa-integration",
|
||||
database_type: "postgres",
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "api",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build": "babel src -d dist --extensions \".ts,.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@medusajs/medusa": "^1.1.3",
|
||||
"medusa-interfaces": "^1.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.12.10",
|
||||
"@babel/core": "^7.12.10",
|
||||
"@babel/node": "^7.12.10",
|
||||
"babel-preset-medusa-package": "^1.1.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import { FulfillmentService } from "medusa-interfaces";
|
||||
|
||||
class TestFulService extends FulfillmentService {
|
||||
static identifier = "test-ful";
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
getFulfillmentOptions() {
|
||||
return [
|
||||
{
|
||||
id: "manual-fulfillment",
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
validateFulfillmentData(data, cart) {
|
||||
return data;
|
||||
}
|
||||
|
||||
validateOption(data) {
|
||||
return true;
|
||||
}
|
||||
|
||||
canCalculate() {
|
||||
return false;
|
||||
}
|
||||
|
||||
calculatePrice() {
|
||||
throw Error("Manual Fulfillment service cannot calculatePrice");
|
||||
}
|
||||
|
||||
createOrder() {
|
||||
// No data is being sent anywhere
|
||||
return Promise.resolve({});
|
||||
}
|
||||
|
||||
createFulfillment() {
|
||||
// No data is being sent anywhere
|
||||
return Promise.resolve({});
|
||||
}
|
||||
|
||||
cancelFulfillment() {
|
||||
return Promise.resolve({});
|
||||
}
|
||||
}
|
||||
|
||||
export default TestFulService;
|
||||
@@ -0,0 +1,59 @@
|
||||
import { PaymentService } from "medusa-interfaces";
|
||||
|
||||
class TestPayService extends PaymentService {
|
||||
static identifier = "test-pay";
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
async getStatus(paymentData) {
|
||||
return "authorized";
|
||||
}
|
||||
|
||||
async retrieveSavedMethods(customer) {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
|
||||
async createPayment() {
|
||||
return {};
|
||||
}
|
||||
|
||||
async retrievePayment(data) {
|
||||
return {};
|
||||
}
|
||||
|
||||
async getPaymentData(sessionData) {
|
||||
return {};
|
||||
}
|
||||
|
||||
async authorizePayment(sessionData, context = {}) {
|
||||
return {};
|
||||
}
|
||||
|
||||
async updatePaymentData(sessionData, update) {
|
||||
return {};
|
||||
}
|
||||
|
||||
async updatePayment(sessionData, cart) {
|
||||
return {};
|
||||
}
|
||||
|
||||
async deletePayment(payment) {
|
||||
return {};
|
||||
}
|
||||
|
||||
async capturePayment(payment) {
|
||||
return {};
|
||||
}
|
||||
|
||||
async refundPayment(payment, amountToRefund) {
|
||||
return {};
|
||||
}
|
||||
|
||||
async cancelPayment(payment) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
export default TestPayService;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,30 @@
|
||||
const path = require("path");
|
||||
const { spawn } = require("child_process");
|
||||
|
||||
const { setPort } = require("./use-api");
|
||||
|
||||
module.exports = ({ cwd }) => {
|
||||
const serverPath = path.join(__dirname, "test-server.js");
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const medusaProcess = spawn("node", [path.resolve(serverPath)], {
|
||||
cwd,
|
||||
env: {
|
||||
...process.env,
|
||||
NODE_ENV: "development",
|
||||
JWT_SECRET: "test",
|
||||
COOKIE_SECRET: "test",
|
||||
},
|
||||
stdio: ["ignore", "ignore", "inherit", "ipc"],
|
||||
});
|
||||
|
||||
medusaProcess.on("uncaughtException", (err) => {
|
||||
medusaProcess.kill();
|
||||
});
|
||||
|
||||
medusaProcess.on("message", (port) => {
|
||||
setPort(port);
|
||||
resolve(medusaProcess);
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
const path = require("path");
|
||||
const express = require("express");
|
||||
const getPort = require("get-port");
|
||||
|
||||
const loaders = require("@medusajs/medusa/dist/loaders").default;
|
||||
|
||||
const initialize = async () => {
|
||||
const app = express();
|
||||
|
||||
const { dbConnection } = await loaders({
|
||||
directory: path.resolve(process.cwd()),
|
||||
expressApp: app,
|
||||
});
|
||||
|
||||
const PORT = await getPort();
|
||||
|
||||
return {
|
||||
db: dbConnection,
|
||||
app,
|
||||
port: PORT,
|
||||
};
|
||||
};
|
||||
|
||||
const setup = async () => {
|
||||
const { app, port } = await initialize();
|
||||
|
||||
app.listen(port, (err) => {
|
||||
process.send(port);
|
||||
});
|
||||
};
|
||||
|
||||
setup();
|
||||
@@ -0,0 +1,21 @@
|
||||
const axios = require("axios").default;
|
||||
|
||||
const ServerTestUtil = {
|
||||
port_: null,
|
||||
client_: null,
|
||||
|
||||
setPort: function (port) {
|
||||
this.client_ = axios.create({ baseURL: `http://localhost:${port}` });
|
||||
},
|
||||
};
|
||||
|
||||
const instance = ServerTestUtil;
|
||||
|
||||
module.exports = {
|
||||
setPort: function (port) {
|
||||
instance.setPort(port);
|
||||
},
|
||||
useApi: function () {
|
||||
return instance.client_;
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,73 @@
|
||||
const { dropDatabase, createDatabase } = require("pg-god");
|
||||
const { createConnection } = require("typeorm");
|
||||
|
||||
const path = require("path");
|
||||
|
||||
const DbTestUtil = {
|
||||
db_: null,
|
||||
|
||||
setDb: function (connection) {
|
||||
this.db_ = connection;
|
||||
},
|
||||
|
||||
clear: function () {
|
||||
return this.db_.synchronize(true);
|
||||
},
|
||||
|
||||
shutdown: async function () {
|
||||
await this.db_.close();
|
||||
return dropDatabase({ databaseName });
|
||||
},
|
||||
};
|
||||
|
||||
const instance = DbTestUtil;
|
||||
|
||||
module.exports = {
|
||||
initDb: async function ({ cwd }) {
|
||||
const migrationDir = path.resolve(
|
||||
path.join(
|
||||
cwd,
|
||||
`node_modules`,
|
||||
`@medusajs`,
|
||||
`medusa`,
|
||||
`dist`,
|
||||
`migrations`
|
||||
)
|
||||
);
|
||||
|
||||
const databaseName = "medusa-integration";
|
||||
await createDatabase({ databaseName });
|
||||
|
||||
const connection = await createConnection({
|
||||
type: "postgres",
|
||||
url: "postgres://localhost/medusa-integration",
|
||||
migrations: [`${migrationDir}/*.js`],
|
||||
});
|
||||
|
||||
await connection.runMigrations();
|
||||
await connection.close();
|
||||
|
||||
const modelsLoader = require(path.join(
|
||||
cwd,
|
||||
`node_modules`,
|
||||
`@medusajs`,
|
||||
`medusa`,
|
||||
`dist`,
|
||||
`loaders`,
|
||||
`models`
|
||||
)).default;
|
||||
|
||||
const entities = modelsLoader({}, { register: false });
|
||||
const dbConnection = await createConnection({
|
||||
type: "postgres",
|
||||
url: "postgres://localhost/medusa-integration",
|
||||
entities,
|
||||
});
|
||||
|
||||
instance.setDb(dbConnection);
|
||||
return dbConnection;
|
||||
},
|
||||
useDb: function () {
|
||||
return instance;
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
const ServerTestUtil = {
|
||||
server_: null,
|
||||
app_: null,
|
||||
|
||||
setApp: function (app) {
|
||||
this.app_ = app;
|
||||
},
|
||||
|
||||
start: async function () {
|
||||
this.server_ = await new Promise((resolve, reject) => {
|
||||
const s = this.app_.listen(PORT, (err) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
resolve(s);
|
||||
});
|
||||
},
|
||||
|
||||
kill: function () {
|
||||
return new Promise((resolve, _) => {
|
||||
if (this.server_) {
|
||||
this.server_.close(() => resolve());
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
const instance = ServerTestUtil;
|
||||
|
||||
module.exports = {
|
||||
setApp: function (app) {
|
||||
instance.setApp(app);
|
||||
return instance;
|
||||
},
|
||||
|
||||
useServer: function () {
|
||||
return instance;
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
const glob = require(`glob`);
|
||||
|
||||
const pkgs = glob
|
||||
.sync(`${__dirname}/*/`)
|
||||
.map((p) => p.replace(__dirname, `<rootDir>/integration-tests`));
|
||||
|
||||
module.exports = {
|
||||
testEnvironment: `node`,
|
||||
rootDir: `../`,
|
||||
roots: pkgs,
|
||||
testPathIgnorePatterns: [
|
||||
`/examples/`,
|
||||
`/www/`,
|
||||
`/dist/`,
|
||||
`/node_modules/`,
|
||||
`__tests__/fixtures`,
|
||||
`__testfixtures__`,
|
||||
`.cache`,
|
||||
],
|
||||
transform: { "^.+\\.[jt]s$": `<rootDir>/jest-transformer.js` },
|
||||
};
|
||||
+9
-2
@@ -11,18 +11,25 @@
|
||||
"@babel/preset-env": "^7.11.5",
|
||||
"@babel/register": "^7.11.5",
|
||||
"@babel/runtime": "^7.11.2",
|
||||
"axios": "^0.21.1",
|
||||
"axios-mock-adapter": "^1.19.0",
|
||||
"babel-jest": "^26.6.3",
|
||||
"babel-preset-medusa-package": "^1.0.0",
|
||||
"cross-env": "^7.0.2",
|
||||
"express": "^4.17.1",
|
||||
"get-port": "^5.1.1",
|
||||
"jest": "^26.6.3",
|
||||
"lerna": "^3.22.1",
|
||||
"mongoose": "^5.10.15",
|
||||
"prettier": "^2.1.1"
|
||||
"pg-god": "^1.0.11",
|
||||
"prettier": "^2.1.1",
|
||||
"resolve-cwd": "^3.0.0",
|
||||
"typeorm": "^0.2.30"
|
||||
},
|
||||
"scripts": {
|
||||
"bootstrap": "lerna bootstrap",
|
||||
"jest": "jest",
|
||||
"test": "jest"
|
||||
"test": "jest",
|
||||
"test:integration": "jest --config=integration-tests/jest.config.js --runInBand"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import Webshipper from "../utils/webshipper"
|
||||
class WebshipperFulfillmentService extends FulfillmentService {
|
||||
static identifier = "webshipper"
|
||||
|
||||
constructor({ logger, swapService, orderService }, options) {
|
||||
constructor({ logger, claimService, swapService, orderService }, options) {
|
||||
super()
|
||||
|
||||
this.options_ = options
|
||||
@@ -18,6 +18,9 @@ class WebshipperFulfillmentService extends FulfillmentService {
|
||||
/** @private @const {SwapService} */
|
||||
this.swapService_ = swapService
|
||||
|
||||
/** @private @const {SwapService} */
|
||||
this.claimService_ = claimService
|
||||
|
||||
/** @private @const {AxiosClient} */
|
||||
this.client_ = new Webshipper({
|
||||
account: this.options_.account,
|
||||
@@ -82,19 +85,20 @@ class WebshipperFulfillmentService extends FulfillmentService {
|
||||
* return lines.
|
||||
*/
|
||||
async createReturn(returnOrder) {
|
||||
let fromOrder
|
||||
let orderId
|
||||
if (returnOrder.order_id) {
|
||||
fromOrder = await this.orderService_.retrieve(returnOrder.order_id, {
|
||||
select: ["total"],
|
||||
relations: ["shipping_address", "returns"],
|
||||
})
|
||||
orderId = returnOrder.order_id
|
||||
} else if (returnOrder.swap) {
|
||||
fromOrder = await this.orderService_.retrieve(returnOrder.swap.order_id, {
|
||||
select: ["total"],
|
||||
relations: ["shipping_address", "returns"],
|
||||
})
|
||||
orderId = returnOrder.swap.order_id
|
||||
} else if (returnOrder.claim_order) {
|
||||
orderId = returnOrder.claim_order.order_id
|
||||
}
|
||||
|
||||
const fromOrder = await this.orderService_.retrieve(orderId, {
|
||||
select: ["total"],
|
||||
relations: ["discounts", "shipping_address", "returns"],
|
||||
})
|
||||
|
||||
const methodData = returnOrder.shipping_method.data
|
||||
|
||||
const relationships = {
|
||||
@@ -384,6 +388,12 @@ class WebshipperFulfillmentService extends FulfillmentService {
|
||||
trackingNumbers
|
||||
)
|
||||
}
|
||||
} else if (orderId.charAt(0).toLowerCase() === "c") {
|
||||
return this.claimService_.createShipment(
|
||||
orderId,
|
||||
fulfillmentIndex,
|
||||
trackingNumbers
|
||||
)
|
||||
} else {
|
||||
if (fulfillmentIndex.startsWith("ful")) {
|
||||
return this.orderService_.createShipment(
|
||||
|
||||
@@ -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])
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,12 +3,55 @@ class OrderSubscriber {
|
||||
segmentService,
|
||||
eventBusService,
|
||||
orderService,
|
||||
claimService,
|
||||
returnService,
|
||||
}) {
|
||||
this.orderService_ = orderService
|
||||
|
||||
this.returnService_ = returnService
|
||||
|
||||
this.claimService_ = claimService
|
||||
|
||||
eventBusService.subscribe("claim.created", async ({ id }) => {
|
||||
const claim = await this.claimService_.retrieve(id, {
|
||||
relations: [
|
||||
"order",
|
||||
"claim_items",
|
||||
"claim_items.item",
|
||||
"claim_items.tags",
|
||||
"claim_items.variant",
|
||||
],
|
||||
})
|
||||
|
||||
for (const ci of claim.claim_items) {
|
||||
const price = ci.item.unit_price / 100
|
||||
const reporting_price = await segmentService.getReportingValue(
|
||||
claim.order.currency_code,
|
||||
price
|
||||
)
|
||||
const event = {
|
||||
event: "Item Claimed",
|
||||
userId: claim.order.customer_id,
|
||||
timestamp: claim.created_at,
|
||||
properties: {
|
||||
price,
|
||||
reporting_price,
|
||||
order_id: claim.order_id,
|
||||
claim_id: claim.id,
|
||||
claim_item_id: ci.id,
|
||||
type: claim.type,
|
||||
quantity: ci.quantity,
|
||||
variant: ci.variant.sku,
|
||||
product_id: ci.variant.product_id,
|
||||
reason: ci.reason,
|
||||
note: ci.note,
|
||||
tags: ci.tags.map((t) => ({ id: t.id, value: t.value })),
|
||||
},
|
||||
}
|
||||
await segmentService.track(event)
|
||||
}
|
||||
})
|
||||
|
||||
eventBusService.subscribe(
|
||||
"order.items_returned",
|
||||
async ({ id, return_id }) => {
|
||||
@@ -130,13 +173,12 @@ class OrderSubscriber {
|
||||
],
|
||||
})
|
||||
|
||||
const date = new Date(parseInt(order.created))
|
||||
const orderData = await segmentService.buildOrder(order)
|
||||
const orderEvent = {
|
||||
event: "Order Completed",
|
||||
userId: order.customer_id,
|
||||
properties: orderData,
|
||||
timestamp: date,
|
||||
timestamp: order.created_at,
|
||||
}
|
||||
|
||||
segmentService.track(orderEvent)
|
||||
|
||||
@@ -1,74 +1,93 @@
|
||||
export default ({
|
||||
create,
|
||||
update,
|
||||
remove,
|
||||
softRemove,
|
||||
find,
|
||||
findOne,
|
||||
findOneOrFail,
|
||||
save,
|
||||
findAndCount,
|
||||
} = {}) => {
|
||||
return {
|
||||
create: jest.fn().mockImplementation((...args) => {
|
||||
if (create) {
|
||||
return create(...args);
|
||||
}
|
||||
return {};
|
||||
}),
|
||||
softRemove: jest.fn().mockImplementation((...args) => {
|
||||
if (softRemove) {
|
||||
return softRemove(...args);
|
||||
}
|
||||
return {};
|
||||
}),
|
||||
remove: jest.fn().mockImplementation((...args) => {
|
||||
if (remove) {
|
||||
return remove(...args);
|
||||
}
|
||||
return {};
|
||||
}),
|
||||
update: jest.fn().mockImplementation((...args) => {
|
||||
if (update) {
|
||||
return update(...args);
|
||||
}
|
||||
}),
|
||||
findOneOrFail: jest.fn().mockImplementation((...args) => {
|
||||
if (findOneOrFail) {
|
||||
return findOneOrFail(...args);
|
||||
}
|
||||
}),
|
||||
findOne: jest.fn().mockImplementation((...args) => {
|
||||
if (findOne) {
|
||||
return findOne(...args);
|
||||
}
|
||||
}),
|
||||
findOneOrFail: jest.fn().mockImplementation((...args) => {
|
||||
if (findOneOrFail) {
|
||||
return findOneOrFail(...args);
|
||||
}
|
||||
}),
|
||||
find: jest.fn().mockImplementation((...args) => {
|
||||
if (find) {
|
||||
return find(...args);
|
||||
}
|
||||
}),
|
||||
softRemove: jest.fn().mockImplementation((...args) => {
|
||||
if (softRemove) {
|
||||
return softRemove(...args);
|
||||
}
|
||||
}),
|
||||
save: jest.fn().mockImplementation((...args) => {
|
||||
if (save) {
|
||||
return save(...args);
|
||||
}
|
||||
return Promise.resolve(...args);
|
||||
}),
|
||||
findAndCount: jest.fn().mockImplementation((...args) => {
|
||||
if (findAndCount) {
|
||||
return findAndCount(...args);
|
||||
}
|
||||
return {};
|
||||
}),
|
||||
};
|
||||
class MockRepo {
|
||||
constructor({
|
||||
create,
|
||||
update,
|
||||
remove,
|
||||
softRemove,
|
||||
find,
|
||||
findOne,
|
||||
findOneOrFail,
|
||||
save,
|
||||
findAndCount,
|
||||
}) {
|
||||
this.create_ = create;
|
||||
this.update_ = update;
|
||||
this.remove_ = remove;
|
||||
this.softRemove_ = softRemove;
|
||||
this.find_ = find;
|
||||
this.findOne_ = findOne;
|
||||
this.findOneOrFail_ = findOneOrFail;
|
||||
this.save_ = save;
|
||||
this.findAndCount_ = findAndCount;
|
||||
}
|
||||
|
||||
setFindOne(fn) {
|
||||
this.findOne_ = fn;
|
||||
}
|
||||
|
||||
create = jest.fn().mockImplementation((...args) => {
|
||||
if (this.create_) {
|
||||
return this.create_(...args);
|
||||
}
|
||||
return {};
|
||||
});
|
||||
softRemove = jest.fn().mockImplementation((...args) => {
|
||||
if (this.softRemove_) {
|
||||
return this.softRemove_(...args);
|
||||
}
|
||||
return {};
|
||||
});
|
||||
remove = jest.fn().mockImplementation((...args) => {
|
||||
if (this.remove_) {
|
||||
return this.remove_(...args);
|
||||
}
|
||||
return {};
|
||||
});
|
||||
update = jest.fn().mockImplementation((...args) => {
|
||||
if (this.update_) {
|
||||
return this.update_(...args);
|
||||
}
|
||||
});
|
||||
findOneOrFail = jest.fn().mockImplementation((...args) => {
|
||||
if (this.findOneOrFail_) {
|
||||
return this.findOneOrFail_(...args);
|
||||
}
|
||||
});
|
||||
findOne = jest.fn().mockImplementation((...args) => {
|
||||
if (this.findOne_) {
|
||||
return this.findOne_(...args);
|
||||
}
|
||||
});
|
||||
findOneOrFail = jest.fn().mockImplementation((...args) => {
|
||||
if (this.findOneOrFail_) {
|
||||
return this.findOneOrFail_(...args);
|
||||
}
|
||||
});
|
||||
find = jest.fn().mockImplementation((...args) => {
|
||||
if (this.find_) {
|
||||
return this.find_(...args);
|
||||
}
|
||||
});
|
||||
softRemove = jest.fn().mockImplementation((...args) => {
|
||||
if (this.softRemove_) {
|
||||
return this.softRemove_(...args);
|
||||
}
|
||||
});
|
||||
save = jest.fn().mockImplementation((...args) => {
|
||||
if (this.save_) {
|
||||
return this.save_(...args);
|
||||
}
|
||||
return Promise.resolve(...args);
|
||||
});
|
||||
|
||||
findAndCount = jest.fn().mockImplementation((...args) => {
|
||||
if (this.findAndCount_) {
|
||||
return this.findAndCount_(...args);
|
||||
}
|
||||
return {};
|
||||
});
|
||||
}
|
||||
|
||||
export default (methods = {}) => {
|
||||
return new MockRepo(methods);
|
||||
};
|
||||
|
||||
@@ -971,6 +971,18 @@
|
||||
exec-sh "^0.3.2"
|
||||
minimist "^1.2.0"
|
||||
|
||||
"@hapi/hoek@^9.0.0":
|
||||
version "9.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.1.1.tgz#9daf5745156fd84b8e9889a2dc721f0c58e894aa"
|
||||
integrity sha512-CAEbWH7OIur6jEOzaai83jq3FmKmv4PmX1JYfs9IrYcGEVI/lyL1EXJGCj7eFVJ0bg5QR8LMxBlEtA+xKiLpFw==
|
||||
|
||||
"@hapi/topo@^5.0.0":
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-5.0.0.tgz#c19af8577fa393a06e9c77b60995af959be721e7"
|
||||
integrity sha512-tFJlT47db0kMqVm3H4nQYgn6Pwg10GTZHb1pwmSiv1K4ks6drQOtfEF5ZnPjkvC+y4/bUPHK+bc87QvLcL+WMw==
|
||||
dependencies:
|
||||
"@hapi/hoek" "^9.0.0"
|
||||
|
||||
"@istanbuljs/load-nyc-config@^1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.0.0.tgz#10602de5570baea82f8afbfa2630b24e7a8cfe5b"
|
||||
@@ -1154,6 +1166,23 @@
|
||||
"@types/yargs" "^15.0.0"
|
||||
chalk "^3.0.0"
|
||||
|
||||
"@sideway/address@^4.1.0":
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.0.tgz#0b301ada10ac4e0e3fa525c90615e0b61a72b78d"
|
||||
integrity sha512-wAH/JYRXeIFQRsxerIuLjgUu2Xszam+O5xKeatJ4oudShOOirfmsQ1D6LL54XOU2tizpCYku+s1wmU0SYdpoSA==
|
||||
dependencies:
|
||||
"@hapi/hoek" "^9.0.0"
|
||||
|
||||
"@sideway/formula@^3.0.0":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.0.tgz#fe158aee32e6bd5de85044be615bc08478a0a13c"
|
||||
integrity sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg==
|
||||
|
||||
"@sideway/pinpoint@^2.0.0":
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@sideway/pinpoint/-/pinpoint-2.0.0.tgz#cff8ffadc372ad29fd3f78277aeb29e632cc70df"
|
||||
integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==
|
||||
|
||||
"@sinonjs/commons@^1.7.0":
|
||||
version "1.7.2"
|
||||
resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.7.2.tgz#505f55c74e0272b43f6c52d81946bed7058fc0e2"
|
||||
@@ -3242,6 +3271,22 @@ jest@^25.5.2:
|
||||
import-local "^3.0.2"
|
||||
jest-cli "^25.5.2"
|
||||
|
||||
joi-objectid@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/joi-objectid/-/joi-objectid-3.0.1.tgz#63ace7860f8e1a993a28d40c40ffd8eff01a3668"
|
||||
integrity sha512-V/3hbTlGpvJ03Me6DJbdBI08hBTasFOmipsauOsxOSnsF1blxV537WTl1zPwbfcKle4AK0Ma4OPnzMH4LlvTpQ==
|
||||
|
||||
joi@^17.3.0:
|
||||
version "17.3.0"
|
||||
resolved "https://registry.yarnpkg.com/joi/-/joi-17.3.0.tgz#f1be4a6ce29bc1716665819ac361dfa139fff5d2"
|
||||
integrity sha512-Qh5gdU6niuYbUIUV5ejbsMiiFmBdw8Kcp8Buj2JntszCkCfxJ9Cz76OtHxOZMPXrt5810iDIXs+n1nNVoquHgg==
|
||||
dependencies:
|
||||
"@hapi/hoek" "^9.0.0"
|
||||
"@hapi/topo" "^5.0.0"
|
||||
"@sideway/address" "^4.1.0"
|
||||
"@sideway/formula" "^3.0.0"
|
||||
"@sideway/pinpoint" "^2.0.0"
|
||||
|
||||
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
|
||||
@@ -3470,17 +3515,18 @@ map-visit@^1.0.0:
|
||||
dependencies:
|
||||
object-visit "^1.0.0"
|
||||
|
||||
<<<<<<< HEAD
|
||||
math-random@^1.0.1:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/math-random/-/math-random-1.0.4.tgz#5dd6943c938548267016d4e34f057583080c514c"
|
||||
integrity sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==
|
||||
=======
|
||||
memory-pager@^1.0.2:
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/memory-pager/-/memory-pager-1.5.0.tgz#d8751655d22d384682741c972f2c3d6dfa3e66b5"
|
||||
integrity sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==
|
||||
>>>>>>> master
|
||||
|
||||
medusa-core-utils@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/medusa-core-utils/-/medusa-core-utils-1.1.0.tgz#0641b365b769dbf99856025d935eef5cf5d81f2c"
|
||||
integrity sha512-zocRthKhLK3eSjrXbAhZZkIMBRxyvU7GcAMFh5UCEgfe7f935vjE7r5lGTr5jTEwgwaoTUk9ep0VBekz0SEdyw==
|
||||
dependencies:
|
||||
joi "^17.3.0"
|
||||
joi-objectid "^3.0.1"
|
||||
|
||||
merge-stream@^2.0.0:
|
||||
version "2.0.0"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "@medusajs/medusa",
|
||||
"version": "1.1.4",
|
||||
"description": "E-commerce for JAMstack",
|
||||
"main": "dist/app.js",
|
||||
"main": "dist/index.js",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/medusajs/medusa",
|
||||
@@ -39,7 +39,8 @@
|
||||
"prepare": "cross-env NODE_ENV=production npm run build",
|
||||
"build": "babel src -d dist --ignore **/__tests__ --extensions \".ts,.js\"",
|
||||
"serve": "node dist/app.js",
|
||||
"test": "jest"
|
||||
"test": "jest",
|
||||
"test:unit": "jest"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"medusa-interfaces": "1.x",
|
||||
|
||||
@@ -5,7 +5,7 @@ export default () => {
|
||||
const logger = req.scope.resolve("logger")
|
||||
logger.error(err.message)
|
||||
|
||||
console.log(err)
|
||||
console.error(err)
|
||||
|
||||
let statusCode = 500
|
||||
switch (err.name) {
|
||||
|
||||
@@ -13,6 +13,15 @@ const defaultRelations = [
|
||||
"returns",
|
||||
"gift_cards",
|
||||
"gift_card_transactions",
|
||||
"claims",
|
||||
"claims.return_order",
|
||||
"claims.shipping_methods",
|
||||
"claims.shipping_address",
|
||||
"claims.additional_items",
|
||||
"claims.fulfillments",
|
||||
"claims.claim_items",
|
||||
"claims.claim_items.images",
|
||||
"claims.claim_items.tags",
|
||||
"swaps",
|
||||
"swaps.return_order",
|
||||
"swaps.payment",
|
||||
|
||||
@@ -0,0 +1,245 @@
|
||||
import { MedusaError, Validator } from "medusa-core-utils"
|
||||
import { defaultRelations, defaultFields } from "./"
|
||||
|
||||
export default async (req, res) => {
|
||||
const { id } = req.params
|
||||
|
||||
const schema = Validator.object().keys({
|
||||
type: Validator.string()
|
||||
.valid("replace", "refund")
|
||||
.required(),
|
||||
claim_items: Validator.array()
|
||||
.items({
|
||||
item_id: Validator.string().required(),
|
||||
quantity: Validator.number().required(),
|
||||
note: Validator.string().optional(),
|
||||
reason: Validator.string().valid(
|
||||
"missing_item",
|
||||
"wrong_item",
|
||||
"production_failure",
|
||||
"other"
|
||||
),
|
||||
tags: Validator.array().items(Validator.string()),
|
||||
images: Validator.array().items(Validator.string()),
|
||||
})
|
||||
.required(),
|
||||
return_shipping: Validator.object()
|
||||
.keys({
|
||||
option_id: Validator.string().optional(),
|
||||
price: Validator.number()
|
||||
.integer()
|
||||
.optional(),
|
||||
})
|
||||
.optional(),
|
||||
additional_items: Validator.array()
|
||||
.items({
|
||||
variant_id: Validator.string().required(),
|
||||
quantity: Validator.number().required(),
|
||||
})
|
||||
.optional(),
|
||||
shipping_methods: Validator.array()
|
||||
.items({
|
||||
id: Validator.string().optional(),
|
||||
option_id: Validator.string().optional(),
|
||||
price: Validator.number()
|
||||
.integer()
|
||||
.optional(),
|
||||
})
|
||||
.optional(),
|
||||
refund_amount: Validator.number()
|
||||
.integer()
|
||||
.optional(),
|
||||
metadata: Validator.object().optional(),
|
||||
})
|
||||
|
||||
const { value, error } = schema.validate(req.body)
|
||||
if (error) {
|
||||
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
|
||||
}
|
||||
|
||||
const idempotencyKeyService = req.scope.resolve("idempotencyKeyService")
|
||||
|
||||
const headerKey = req.get("Idempotency-Key") || ""
|
||||
|
||||
let idempotencyKey
|
||||
try {
|
||||
idempotencyKey = await idempotencyKeyService.initializeRequest(
|
||||
headerKey,
|
||||
req.method,
|
||||
req.params,
|
||||
req.path
|
||||
)
|
||||
} catch (error) {
|
||||
res.status(409).send("Failed to create idempotency key")
|
||||
return
|
||||
}
|
||||
|
||||
res.setHeader("Access-Control-Expose-Headers", "Idempotency-Key")
|
||||
res.setHeader("Idempotency-Key", idempotencyKey.idempotency_key)
|
||||
|
||||
try {
|
||||
const orderService = req.scope.resolve("orderService")
|
||||
const claimService = req.scope.resolve("claimService")
|
||||
const returnService = req.scope.resolve("returnService")
|
||||
|
||||
let inProgress = true
|
||||
let err = false
|
||||
|
||||
while (inProgress) {
|
||||
switch (idempotencyKey.recovery_point) {
|
||||
case "started": {
|
||||
const { key, error } = await idempotencyKeyService.workStage(
|
||||
idempotencyKey.idempotency_key,
|
||||
async manager => {
|
||||
const order = await orderService
|
||||
.withTransaction(manager)
|
||||
.retrieve(id, {
|
||||
relations: ["items", "discounts"],
|
||||
})
|
||||
|
||||
await claimService.withTransaction(manager).create({
|
||||
idempotency_key: idempotencyKey.idempotency_key,
|
||||
order,
|
||||
type: value.type,
|
||||
claim_items: value.claim_items,
|
||||
return_shipping: value.return_shipping,
|
||||
additional_items: value.additional_items,
|
||||
shipping_methods: value.shipping_methods,
|
||||
metadata: value.metadata,
|
||||
})
|
||||
|
||||
return {
|
||||
recovery_point: "claim_created",
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
if (error) {
|
||||
inProgress = false
|
||||
err = error
|
||||
} else {
|
||||
idempotencyKey = key
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case "claim_created": {
|
||||
const { key, error } = await idempotencyKeyService.workStage(
|
||||
idempotencyKey.idempotency_key,
|
||||
async manager => {
|
||||
let claim = await claimService.withTransaction(manager).list({
|
||||
idempotency_key: idempotencyKey.idempotency_key,
|
||||
})
|
||||
|
||||
if (!claim.length) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`Claim not found`
|
||||
)
|
||||
}
|
||||
|
||||
claim = claim[0]
|
||||
|
||||
if (claim.type === "refund") {
|
||||
await claimService
|
||||
.withTransaction(manager)
|
||||
.processRefund(claim.id)
|
||||
}
|
||||
|
||||
return {
|
||||
recovery_point: "refund_handled",
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
if (error) {
|
||||
inProgress = false
|
||||
err = error
|
||||
} else {
|
||||
idempotencyKey = key
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case "refund_handled": {
|
||||
const { key, error } = await idempotencyKeyService.workStage(
|
||||
idempotencyKey.idempotency_key,
|
||||
async manager => {
|
||||
let order = await orderService
|
||||
.withTransaction(manager)
|
||||
.retrieve(id, {
|
||||
relations: ["items", "discounts"],
|
||||
})
|
||||
|
||||
let claim = await claimService.withTransaction(manager).list(
|
||||
{
|
||||
idempotency_key: idempotencyKey.idempotency_key,
|
||||
},
|
||||
{
|
||||
relations: ["return_order"],
|
||||
}
|
||||
)
|
||||
|
||||
if (!claim.length) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`Claim not found`
|
||||
)
|
||||
}
|
||||
|
||||
claim = claim[0]
|
||||
|
||||
if (claim.return_order) {
|
||||
await returnService
|
||||
.withTransaction(manager)
|
||||
.fulfill(claim.return_order.id)
|
||||
}
|
||||
|
||||
order = await orderService.withTransaction(manager).retrieve(id, {
|
||||
select: defaultFields,
|
||||
relations: defaultRelations,
|
||||
})
|
||||
|
||||
return {
|
||||
response_code: 200,
|
||||
response_body: { order },
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
if (error) {
|
||||
inProgress = false
|
||||
err = error
|
||||
} else {
|
||||
idempotencyKey = key
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case "finished": {
|
||||
inProgress = false
|
||||
break
|
||||
}
|
||||
|
||||
default:
|
||||
idempotencyKey = await idempotencyKeyService.update(
|
||||
idempotencyKey.idempotency_key,
|
||||
{
|
||||
recovery_point: "finished",
|
||||
response_code: 500,
|
||||
response_body: { message: "Unknown recovery point" },
|
||||
}
|
||||
)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (err) {
|
||||
throw err
|
||||
}
|
||||
|
||||
res.status(idempotencyKey.response_code).json(idempotencyKey.response_body)
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import { MedusaError, Validator } from "medusa-core-utils"
|
||||
import { defaultRelations, defaultFields } from "./"
|
||||
|
||||
export default async (req, res) => {
|
||||
const { id, claim_id } = req.params
|
||||
|
||||
const schema = Validator.object().keys({
|
||||
metadata: Validator.object().optional(),
|
||||
})
|
||||
|
||||
const { value, error } = schema.validate(req.body)
|
||||
if (error) {
|
||||
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
|
||||
}
|
||||
|
||||
try {
|
||||
const orderService = req.scope.resolve("orderService")
|
||||
const claimService = req.scope.resolve("claimService")
|
||||
const entityManager = req.scope.resolve("manager")
|
||||
|
||||
await entityManager.transaction(async manager => {
|
||||
await claimService
|
||||
.withTransaction(manager)
|
||||
.createFulfillment(claim_id, value.metadata)
|
||||
})
|
||||
|
||||
const order = await orderService.retrieve(id, {
|
||||
select: defaultFields,
|
||||
relations: defaultRelations,
|
||||
})
|
||||
|
||||
res.status(200).json({ order })
|
||||
} catch (err) {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -140,6 +140,27 @@ export default app => {
|
||||
middlewares.wrap(require("./process-swap-payment").default)
|
||||
)
|
||||
|
||||
/**
|
||||
* Creates a claim
|
||||
*/
|
||||
route.post("/:id/claims", middlewares.wrap(require("./create-claim").default))
|
||||
|
||||
/**
|
||||
* Updates a claim
|
||||
*/
|
||||
route.post(
|
||||
"/:id/claims/:claim_id",
|
||||
middlewares.wrap(require("./update-claim").default)
|
||||
)
|
||||
|
||||
/**
|
||||
* Creates claim fulfillment
|
||||
*/
|
||||
route.post(
|
||||
"/:id/claims/:claim_id/fulfillments",
|
||||
middlewares.wrap(require("./fulfill-claim").default)
|
||||
)
|
||||
|
||||
/**
|
||||
* Delete metadata key / value pair.
|
||||
*/
|
||||
@@ -162,6 +183,15 @@ export const defaultRelations = [
|
||||
"returns",
|
||||
"gift_cards",
|
||||
"gift_card_transactions",
|
||||
"claims",
|
||||
"claims.return_order",
|
||||
"claims.shipping_methods",
|
||||
"claims.shipping_address",
|
||||
"claims.additional_items",
|
||||
"claims.fulfillments",
|
||||
"claims.claim_items",
|
||||
"claims.claim_items.images",
|
||||
"claims.claim_items.tags",
|
||||
"swaps",
|
||||
"swaps.return_order",
|
||||
"swaps.payment",
|
||||
@@ -234,6 +264,7 @@ export const allowedRelations = [
|
||||
"payments",
|
||||
"fulfillments",
|
||||
"returns",
|
||||
"claims",
|
||||
"swaps",
|
||||
"swaps.return_order",
|
||||
"swaps.additional_items",
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import { MedusaError, Validator } from "medusa-core-utils"
|
||||
import { defaultRelations, defaultFields } from "./"
|
||||
|
||||
export default async (req, res) => {
|
||||
const { id, claim_id } = req.params
|
||||
|
||||
const schema = Validator.object().keys({
|
||||
claim_items: Validator.array()
|
||||
.items({
|
||||
id: Validator.string().required(),
|
||||
note: Validator.string().allow(null, ""),
|
||||
reason: Validator.string().allow(null, ""),
|
||||
images: Validator.array().items({
|
||||
id: Validator.string().optional(),
|
||||
url: Validator.string().optional(),
|
||||
}),
|
||||
tags: Validator.array().items({
|
||||
id: Validator.string().optional(),
|
||||
value: Validator.string().optional(),
|
||||
}),
|
||||
metadata: Validator.object().optional(),
|
||||
})
|
||||
.optional(),
|
||||
shipping_methods: Validator.array()
|
||||
.items({
|
||||
id: Validator.string().optional(),
|
||||
option_id: Validator.string().optional(),
|
||||
price: Validator.number()
|
||||
.integer()
|
||||
.optional(),
|
||||
})
|
||||
.optional(),
|
||||
metadata: Validator.object().optional(),
|
||||
})
|
||||
|
||||
const { value, error } = schema.validate(req.body)
|
||||
if (error) {
|
||||
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
|
||||
}
|
||||
|
||||
try {
|
||||
const orderService = req.scope.resolve("orderService")
|
||||
const claimService = req.scope.resolve("claimService")
|
||||
|
||||
await claimService.update(claim_id, value)
|
||||
|
||||
const data = await orderService.retrieve(id, {
|
||||
select: defaultFields,
|
||||
relations: defaultRelations,
|
||||
})
|
||||
|
||||
res.json({ order: data })
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
import "core-js/stable"
|
||||
import "regenerator-runtime/runtime"
|
||||
import "reflect-metadata"
|
||||
import express from "express"
|
||||
import loaders from "./loaders"
|
||||
import Logger from "./loaders/logger"
|
||||
|
||||
const PORT = process.env.PORT || 80
|
||||
|
||||
const startServer = async () => {
|
||||
const app = express()
|
||||
|
||||
await loaders({ expressApp: app })
|
||||
|
||||
app.listen(PORT, err => {
|
||||
if (err) {
|
||||
console.log(err)
|
||||
return
|
||||
}
|
||||
Logger.info(`Server is ready on port: ${PORT}!`)
|
||||
})
|
||||
}
|
||||
|
||||
startServer()
|
||||
@@ -0,0 +1,39 @@
|
||||
export { Address } from "./models/address"
|
||||
export { Cart } from "./models/cart"
|
||||
export { ClaimImage } from "./models/claim-image"
|
||||
export { ClaimItem } from "./models/claim-item"
|
||||
export { ClaimTag } from "./models/claim-tag"
|
||||
export { Country } from "./models/country"
|
||||
export { Currency } from "./models/currency"
|
||||
export { Customer } from "./models/customer"
|
||||
export { DiscountRule } from "./models/discount-rule"
|
||||
export { Discount } from "./models/discount"
|
||||
export { FulfillmentItem } from "./models/fulfillment-item"
|
||||
export { FulfillmentProvider } from "./models/fulfillment-provider"
|
||||
export { GiftCardTransaction } from "./models/gift-card-transaction"
|
||||
export { GiftCard } from "./models/gift-card"
|
||||
export { IdempotencyKey } from "./models/idempotency-key"
|
||||
export { Image } from "./models/image"
|
||||
export { LineItem } from "./models/line-item"
|
||||
export { MoneyAmount } from "./models/money-amount"
|
||||
export { Oauth } from "./models/oauth"
|
||||
export { Order } from "./models/order"
|
||||
export { PaymentProvider } from "./models/payment-provider"
|
||||
export { PaymentSession } from "./models/payment-session"
|
||||
export { Payment } from "./models/payment"
|
||||
export { ProductOptionValue } from "./models/product-option-value"
|
||||
export { ProductOption } from "./models/product-option"
|
||||
export { ProductVariant } from "./models/product-variant"
|
||||
export { Product } from "./models/product"
|
||||
export { Refund } from "./models/refund"
|
||||
export { Region } from "./models/region"
|
||||
export { ReturnItem } from "./models/return-item"
|
||||
export { Return } from "./models/return"
|
||||
export { ShippingMethod } from "./models/shipping-method"
|
||||
export { ShippingOptionRequirement } from "./models/shipping-option-requirement"
|
||||
export { ShippingOption } from "./models/shipping-option"
|
||||
export { ShippingProfile } from "./models/shipping-profile"
|
||||
export { StagedJob } from "./models/staged-job"
|
||||
export { Store } from "./models/store"
|
||||
export { Swap } from "./models/swap"
|
||||
export { User } from "./models/user"
|
||||
@@ -7,25 +7,33 @@ import { Lifetime, asClass, asValue } from "awilix"
|
||||
/**
|
||||
* Registers all models in the model directory
|
||||
*/
|
||||
export default ({ container }) => {
|
||||
export default ({ container }, config = { register: true }) => {
|
||||
let corePath = "../models/*.js"
|
||||
const coreFull = path.join(__dirname, corePath)
|
||||
|
||||
const toReturn = []
|
||||
|
||||
const core = glob.sync(coreFull, { cwd: __dirname })
|
||||
core.forEach(fn => {
|
||||
const loaded = require(fn)
|
||||
|
||||
Object.entries(loaded).map(([key, val]) => {
|
||||
if (typeof val === "function" || val instanceof EntitySchema) {
|
||||
const name = formatRegistrationName(fn)
|
||||
container.register({
|
||||
[name]: asClass(val),
|
||||
})
|
||||
if (config.register) {
|
||||
const name = formatRegistrationName(fn)
|
||||
container.register({
|
||||
[name]: asClass(val),
|
||||
})
|
||||
|
||||
container.registerAdd("db_entities", asValue(val))
|
||||
container.registerAdd("db_entities", asValue(val))
|
||||
}
|
||||
|
||||
toReturn.push(val)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
return toReturn
|
||||
}
|
||||
|
||||
function formatRegistrationName(fn) {
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm"
|
||||
|
||||
export class claims1612284947120 implements MigrationInterface {
|
||||
name = "claims1612284947120"
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "shipping_method" DROP CONSTRAINT "CHK_3c00b878c1426d119cd70aa065"`
|
||||
)
|
||||
await queryRunner.query(
|
||||
`CREATE TABLE "claim_image" ("id" character varying NOT NULL, "claim_item_id" character varying NOT NULL, "url" character varying NOT NULL, "created_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "deleted_at" TIMESTAMP WITH TIME ZONE, "metadata" jsonb, CONSTRAINT "PK_7c49e44bfe8840ca7d917890101" PRIMARY KEY ("id"))`
|
||||
)
|
||||
await queryRunner.query(
|
||||
`CREATE TABLE "claim_tag" ("id" character varying NOT NULL, "value" character varying NOT NULL, "created_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "deleted_at" TIMESTAMP WITH TIME ZONE, "metadata" jsonb, CONSTRAINT "PK_7761180541142a5926501018d34" PRIMARY KEY ("id"))`
|
||||
)
|
||||
await queryRunner.query(
|
||||
`CREATE INDEX "IDX_ec10c54769877840c132260e4a" ON "claim_tag" ("value") `
|
||||
)
|
||||
await queryRunner.query(
|
||||
`CREATE TYPE "claim_item_reason_enum" AS ENUM('missing_item', 'wrong_item', 'production_failure', 'other')`
|
||||
)
|
||||
await queryRunner.query(
|
||||
`CREATE TABLE "claim_item" ("id" character varying NOT NULL, "claim_order_id" character varying NOT NULL, "item_id" character varying NOT NULL, "variant_id" character varying NOT NULL, "reason" "claim_item_reason_enum" NOT NULL, "note" character varying, "quantity" integer NOT NULL, "created_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "deleted_at" TIMESTAMP WITH TIME ZONE, "metadata" jsonb, CONSTRAINT "PK_5679662039bc4c7c6bc7fa1be2d" PRIMARY KEY ("id"))`
|
||||
)
|
||||
await queryRunner.query(
|
||||
`CREATE INDEX "IDX_900a9c3834257304396b2b0fe7" ON "claim_item" ("claim_order_id") `
|
||||
)
|
||||
await queryRunner.query(
|
||||
`CREATE INDEX "IDX_6e0cad0daef76bb642675910b9" ON "claim_item" ("item_id") `
|
||||
)
|
||||
await queryRunner.query(
|
||||
`CREATE INDEX "IDX_64980511ca32c8e92b417644af" ON "claim_item" ("variant_id") `
|
||||
)
|
||||
await queryRunner.query(
|
||||
`CREATE TYPE "claim_order_payment_status_enum" AS ENUM('na', 'not_refunded', 'refunded')`
|
||||
)
|
||||
await queryRunner.query(
|
||||
`CREATE TYPE "claim_order_fulfillment_status_enum" AS ENUM('not_fulfilled', 'partially_fulfilled', 'fulfilled', 'partially_shipped', 'shipped', 'partially_returned', 'returned', 'canceled', 'requires_action')`
|
||||
)
|
||||
await queryRunner.query(
|
||||
`CREATE TYPE "claim_order_type_enum" AS ENUM('refund', 'replace')`
|
||||
)
|
||||
await queryRunner.query(
|
||||
`CREATE TABLE "claim_order" ("id" character varying NOT NULL, "payment_status" "claim_order_payment_status_enum" NOT NULL DEFAULT 'na', "fulfillment_status" "claim_order_fulfillment_status_enum" NOT NULL DEFAULT 'not_fulfilled', "type" "claim_order_type_enum" NOT NULL, "order_id" character varying NOT NULL, "shipping_address_id" character varying, "refund_amount" integer, "canceled_at" TIMESTAMP WITH TIME ZONE, "created_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "deleted_at" TIMESTAMP WITH TIME ZONE, "metadata" jsonb, "idempotency_key" character varying, CONSTRAINT "PK_8981f5595a4424021466aa4c7a4" PRIMARY KEY ("id"))`
|
||||
)
|
||||
await queryRunner.query(
|
||||
`CREATE TABLE "claim_item_tags" ("item_id" character varying NOT NULL, "tag_id" character varying NOT NULL, CONSTRAINT "PK_54ab8ce0f7e99167068188fbd81" PRIMARY KEY ("item_id", "tag_id"))`
|
||||
)
|
||||
await queryRunner.query(
|
||||
`CREATE INDEX "IDX_c2c0f3edf39515bd15432afe6e" ON "claim_item_tags" ("item_id") `
|
||||
)
|
||||
await queryRunner.query(
|
||||
`CREATE INDEX "IDX_dc9bbf9fcb9ba458d25d512811" ON "claim_item_tags" ("tag_id") `
|
||||
)
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "shipping_method" ADD "claim_order_id" character varying`
|
||||
)
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "return" ADD "claim_order_id" character varying`
|
||||
)
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "return" ADD CONSTRAINT "UQ_71773d56eb2bacb922bc3283398" UNIQUE ("claim_order_id")`
|
||||
)
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "fulfillment" ADD "claim_order_id" character varying`
|
||||
)
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "line_item" ADD "claim_order_id" character varying`
|
||||
)
|
||||
await queryRunner.query(
|
||||
`ALTER TYPE "public"."refund_reason_enum" RENAME TO "refund_reason_enum_old"`
|
||||
)
|
||||
await queryRunner.query(
|
||||
`CREATE TYPE "refund_reason_enum" AS ENUM('discount', 'return', 'swap', 'claim', 'other')`
|
||||
)
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "refund" ALTER COLUMN "reason" TYPE "refund_reason_enum" USING "reason"::"text"::"refund_reason_enum"`
|
||||
)
|
||||
await queryRunner.query(`DROP TYPE "refund_reason_enum_old"`)
|
||||
await queryRunner.query(`COMMENT ON COLUMN "refund"."reason" IS NULL`)
|
||||
await queryRunner.query(
|
||||
`CREATE INDEX "IDX_d783a66d1c91c0858752c933e6" ON "shipping_method" ("claim_order_id") `
|
||||
)
|
||||
await queryRunner.query(
|
||||
`CREATE INDEX "IDX_118e3c48f09a7728f41023c94e" ON "line_item" ("claim_order_id") `
|
||||
)
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "shipping_method" ADD CONSTRAINT "CHK_a7020b08665bbd64d84a6641cf" CHECK ("claim_order_id" IS NOT NULL OR "order_id" IS NOT NULL OR "cart_id" IS NOT NULL OR "swap_id" IS NOT NULL OR "return_id" IS NOT NULL)`
|
||||
)
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "claim_image" ADD CONSTRAINT "FK_21cbfedd83d736d86f4c6f4ce56" FOREIGN KEY ("claim_item_id") REFERENCES "claim_item"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`
|
||||
)
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "claim_item" ADD CONSTRAINT "FK_900a9c3834257304396b2b0fe7c" FOREIGN KEY ("claim_order_id") REFERENCES "claim_order"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`
|
||||
)
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "claim_item" ADD CONSTRAINT "FK_6e0cad0daef76bb642675910b9d" FOREIGN KEY ("item_id") REFERENCES "line_item"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`
|
||||
)
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "claim_item" ADD CONSTRAINT "FK_64980511ca32c8e92b417644afa" FOREIGN KEY ("variant_id") REFERENCES "product_variant"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`
|
||||
)
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "shipping_method" ADD CONSTRAINT "FK_d783a66d1c91c0858752c933e68" FOREIGN KEY ("claim_order_id") REFERENCES "claim_order"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`
|
||||
)
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "return" ADD CONSTRAINT "FK_71773d56eb2bacb922bc3283398" FOREIGN KEY ("claim_order_id") REFERENCES "claim_order"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`
|
||||
)
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "claim_order" ADD CONSTRAINT "FK_f49e3974465d3c3a33d449d3f31" FOREIGN KEY ("order_id") REFERENCES "order"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`
|
||||
)
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "claim_order" ADD CONSTRAINT "FK_017d58bf8260c6e1a2588d258e2" FOREIGN KEY ("shipping_address_id") REFERENCES "address"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`
|
||||
)
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "fulfillment" ADD CONSTRAINT "FK_d73e55964e0ff2db8f03807d52e" FOREIGN KEY ("claim_order_id") REFERENCES "claim_order"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`
|
||||
)
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "line_item" ADD CONSTRAINT "FK_118e3c48f09a7728f41023c94ef" FOREIGN KEY ("claim_order_id") REFERENCES "claim_order"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`
|
||||
)
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "claim_item_tags" ADD CONSTRAINT "FK_c2c0f3edf39515bd15432afe6e5" FOREIGN KEY ("item_id") REFERENCES "claim_item"("id") ON DELETE CASCADE ON UPDATE NO ACTION`
|
||||
)
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "claim_item_tags" ADD CONSTRAINT "FK_dc9bbf9fcb9ba458d25d512811b" FOREIGN KEY ("tag_id") REFERENCES "claim_tag"("id") ON DELETE CASCADE ON UPDATE NO ACTION`
|
||||
)
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "claim_item_tags" DROP CONSTRAINT "FK_dc9bbf9fcb9ba458d25d512811b"`
|
||||
)
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "claim_item_tags" DROP CONSTRAINT "FK_c2c0f3edf39515bd15432afe6e5"`
|
||||
)
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "line_item" DROP CONSTRAINT "FK_118e3c48f09a7728f41023c94ef"`
|
||||
)
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "fulfillment" DROP CONSTRAINT "FK_d73e55964e0ff2db8f03807d52e"`
|
||||
)
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "claim_order" DROP CONSTRAINT "FK_017d58bf8260c6e1a2588d258e2"`
|
||||
)
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "claim_order" DROP CONSTRAINT "FK_f49e3974465d3c3a33d449d3f31"`
|
||||
)
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "return" DROP CONSTRAINT "FK_71773d56eb2bacb922bc3283398"`
|
||||
)
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "shipping_method" DROP CONSTRAINT "FK_d783a66d1c91c0858752c933e68"`
|
||||
)
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "claim_item" DROP CONSTRAINT "FK_64980511ca32c8e92b417644afa"`
|
||||
)
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "claim_item" DROP CONSTRAINT "FK_6e0cad0daef76bb642675910b9d"`
|
||||
)
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "claim_item" DROP CONSTRAINT "FK_900a9c3834257304396b2b0fe7c"`
|
||||
)
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "claim_image" DROP CONSTRAINT "FK_21cbfedd83d736d86f4c6f4ce56"`
|
||||
)
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "shipping_method" DROP CONSTRAINT "CHK_a7020b08665bbd64d84a6641cf"`
|
||||
)
|
||||
await queryRunner.query(`DROP INDEX "IDX_118e3c48f09a7728f41023c94e"`)
|
||||
await queryRunner.query(`DROP INDEX "IDX_d783a66d1c91c0858752c933e6"`)
|
||||
await queryRunner.query(`COMMENT ON COLUMN "refund"."reason" IS NULL`)
|
||||
await queryRunner.query(
|
||||
`CREATE TYPE "refund_reason_enum_old" AS ENUM('discount', 'return', 'swap', 'other')`
|
||||
)
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "refund" ALTER COLUMN "reason" TYPE "refund_reason_enum_old" USING "reason"::"text"::"refund_reason_enum_old"`
|
||||
)
|
||||
await queryRunner.query(`DROP TYPE "refund_reason_enum"`)
|
||||
await queryRunner.query(
|
||||
`ALTER TYPE "refund_reason_enum_old" RENAME TO "refund_reason_enum"`
|
||||
)
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "line_item" DROP COLUMN "claim_order_id"`
|
||||
)
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "fulfillment" DROP COLUMN "claim_order_id"`
|
||||
)
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "return" DROP CONSTRAINT "UQ_71773d56eb2bacb922bc3283398"`
|
||||
)
|
||||
await queryRunner.query(`ALTER TABLE "return" DROP COLUMN "claim_order_id"`)
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "shipping_method" DROP COLUMN "claim_order_id"`
|
||||
)
|
||||
await queryRunner.query(`DROP INDEX "IDX_dc9bbf9fcb9ba458d25d512811"`)
|
||||
await queryRunner.query(`DROP INDEX "IDX_c2c0f3edf39515bd15432afe6e"`)
|
||||
await queryRunner.query(`DROP TABLE "claim_item_tags"`)
|
||||
await queryRunner.query(`DROP TABLE "claim_order"`)
|
||||
await queryRunner.query(`DROP TYPE "claim_order_type_enum"`)
|
||||
await queryRunner.query(`DROP TYPE "claim_order_fulfillment_status_enum"`)
|
||||
await queryRunner.query(`DROP TYPE "claim_order_payment_status_enum"`)
|
||||
await queryRunner.query(`DROP INDEX "IDX_64980511ca32c8e92b417644af"`)
|
||||
await queryRunner.query(`DROP INDEX "IDX_6e0cad0daef76bb642675910b9"`)
|
||||
await queryRunner.query(`DROP INDEX "IDX_900a9c3834257304396b2b0fe7"`)
|
||||
await queryRunner.query(`DROP TABLE "claim_item"`)
|
||||
await queryRunner.query(`DROP TYPE "claim_item_reason_enum"`)
|
||||
await queryRunner.query(`DROP INDEX "IDX_ec10c54769877840c132260e4a"`)
|
||||
await queryRunner.query(`DROP TABLE "claim_tag"`)
|
||||
await queryRunner.query(`DROP TABLE "claim_image"`)
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "shipping_method" ADD CONSTRAINT "CHK_3c00b878c1426d119cd70aa065" CHECK (((order_id IS NOT NULL) OR (cart_id IS NOT NULL) OR (swap_id IS NOT NULL) OR (return_id IS NOT NULL)))`
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import {
|
||||
Entity,
|
||||
BeforeInsert,
|
||||
DeleteDateColumn,
|
||||
CreateDateColumn,
|
||||
UpdateDateColumn,
|
||||
Column,
|
||||
PrimaryColumn,
|
||||
ManyToOne,
|
||||
JoinColumn,
|
||||
} from "typeorm"
|
||||
import { ulid } from "ulid"
|
||||
|
||||
import { ClaimItem } from "./claim-item"
|
||||
|
||||
@Entity()
|
||||
export class ClaimImage {
|
||||
@PrimaryColumn()
|
||||
id: string
|
||||
|
||||
@Column()
|
||||
claim_item_id: string
|
||||
|
||||
@ManyToOne(
|
||||
() => ClaimItem,
|
||||
ci => ci.images
|
||||
)
|
||||
@JoinColumn({ name: "claim_item_id" })
|
||||
claim_item: ClaimItem
|
||||
|
||||
@Column()
|
||||
url: string
|
||||
|
||||
@CreateDateColumn({ type: "timestamptz" })
|
||||
created_at: Date
|
||||
|
||||
@UpdateDateColumn({ type: "timestamptz" })
|
||||
updated_at: Date
|
||||
|
||||
@DeleteDateColumn({ type: "timestamptz" })
|
||||
deleted_at: Date
|
||||
|
||||
@Column({ type: "jsonb", nullable: true })
|
||||
metadata: any
|
||||
|
||||
@BeforeInsert()
|
||||
private beforeInsert() {
|
||||
if (this.id) return
|
||||
const id = ulid()
|
||||
this.id = `cimg_${id}`
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
import {
|
||||
Entity,
|
||||
BeforeInsert,
|
||||
DeleteDateColumn,
|
||||
CreateDateColumn,
|
||||
UpdateDateColumn,
|
||||
Column,
|
||||
PrimaryColumn,
|
||||
Index,
|
||||
ManyToMany,
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
JoinColumn,
|
||||
JoinTable,
|
||||
} from "typeorm"
|
||||
import { ulid } from "ulid"
|
||||
|
||||
import { LineItem } from "./line-item"
|
||||
import { ClaimImage } from "./claim-image"
|
||||
import { ClaimTag } from "./claim-tag"
|
||||
import { ClaimOrder } from "./claim-order"
|
||||
import { ProductVariant } from "./product-variant"
|
||||
|
||||
export enum ClaimReason {
|
||||
MISSING_ITEM = "missing_item",
|
||||
WRONG_ITEM = "wrong_item",
|
||||
PRODUCTION_FAILURE = "production_failure",
|
||||
OTHER = "other",
|
||||
}
|
||||
|
||||
@Entity()
|
||||
export class ClaimItem {
|
||||
@PrimaryColumn()
|
||||
id: string
|
||||
|
||||
@OneToMany(
|
||||
() => ClaimImage,
|
||||
ci => ci.claim_item,
|
||||
{ cascade: ["insert", "remove"] }
|
||||
)
|
||||
images: ClaimImage[]
|
||||
|
||||
@Index()
|
||||
@Column()
|
||||
claim_order_id: string
|
||||
|
||||
@ManyToOne(
|
||||
() => ClaimOrder,
|
||||
co => co.claim_items
|
||||
)
|
||||
@JoinColumn({ name: "claim_order_id" })
|
||||
claim_order: ClaimOrder
|
||||
|
||||
@Index()
|
||||
@Column()
|
||||
item_id: string
|
||||
|
||||
@ManyToOne(() => LineItem)
|
||||
@JoinColumn({ name: "item_id" })
|
||||
item: LineItem
|
||||
|
||||
@Index()
|
||||
@Column()
|
||||
variant_id: string
|
||||
|
||||
@ManyToOne(() => ProductVariant)
|
||||
@JoinColumn({ name: "variant_id" })
|
||||
variant: ProductVariant
|
||||
|
||||
@Column({ type: "enum", enum: ClaimReason })
|
||||
reason: ClaimReason
|
||||
|
||||
@Column({ nullable: true })
|
||||
note: string
|
||||
|
||||
@Column({ type: "int" })
|
||||
quantity: number
|
||||
|
||||
@ManyToMany(() => ClaimTag, { cascade: ["insert"] })
|
||||
@JoinTable({
|
||||
name: "claim_item_tags",
|
||||
joinColumn: {
|
||||
name: "item_id",
|
||||
referencedColumnName: "id",
|
||||
},
|
||||
inverseJoinColumn: {
|
||||
name: "tag_id",
|
||||
referencedColumnName: "id",
|
||||
},
|
||||
})
|
||||
tags: ClaimTag[]
|
||||
|
||||
@CreateDateColumn({ type: "timestamptz" })
|
||||
created_at: Date
|
||||
|
||||
@UpdateDateColumn({ type: "timestamptz" })
|
||||
updated_at: Date
|
||||
|
||||
@DeleteDateColumn({ type: "timestamptz" })
|
||||
deleted_at: Date
|
||||
|
||||
@Column({ type: "jsonb", nullable: true })
|
||||
metadata: any
|
||||
|
||||
@BeforeInsert()
|
||||
private beforeInsert() {
|
||||
if (this.id) return
|
||||
const id = ulid()
|
||||
this.id = `citm_${id}`
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
import {
|
||||
Entity,
|
||||
BeforeInsert,
|
||||
DeleteDateColumn,
|
||||
CreateDateColumn,
|
||||
UpdateDateColumn,
|
||||
Column,
|
||||
PrimaryColumn,
|
||||
Index,
|
||||
OneToOne,
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
JoinColumn,
|
||||
} from "typeorm"
|
||||
import { ulid } from "ulid"
|
||||
|
||||
import { Fulfillment } from "./fulfillment"
|
||||
import { LineItem } from "./line-item"
|
||||
import { ClaimItem } from "./claim-item"
|
||||
import { Order } from "./order"
|
||||
import { Return } from "./return"
|
||||
import { ShippingMethod } from "./shipping-method"
|
||||
import { Address } from "./address"
|
||||
|
||||
export enum ClaimType {
|
||||
REFUND = "refund",
|
||||
REPLACE = "replace",
|
||||
}
|
||||
|
||||
export enum ClaimPaymentStatus {
|
||||
NA = "na",
|
||||
NOT_REFUNDED = "not_refunded",
|
||||
REFUNDED = "refunded",
|
||||
}
|
||||
|
||||
export enum ClaimFulfillmentStatus {
|
||||
NOT_FULFILLED = "not_fulfilled",
|
||||
PARTIALLY_FULFILLED = "partially_fulfilled",
|
||||
FULFILLED = "fulfilled",
|
||||
PARTIALLY_SHIPPED = "partially_shipped",
|
||||
SHIPPED = "shipped",
|
||||
PARTIALLY_RETURNED = "partially_returned",
|
||||
RETURNED = "returned",
|
||||
CANCELED = "canceled",
|
||||
REQUIRES_ACTION = "requires_action",
|
||||
}
|
||||
|
||||
@Entity()
|
||||
export class ClaimOrder {
|
||||
@PrimaryColumn()
|
||||
id: string
|
||||
|
||||
@Column({
|
||||
type: "enum",
|
||||
enum: ClaimPaymentStatus,
|
||||
default: ClaimPaymentStatus.NA,
|
||||
})
|
||||
payment_status: ClaimPaymentStatus
|
||||
|
||||
@Column({
|
||||
type: "enum",
|
||||
enum: ClaimFulfillmentStatus,
|
||||
default: ClaimFulfillmentStatus.NOT_FULFILLED,
|
||||
})
|
||||
fulfillment_status: ClaimFulfillmentStatus
|
||||
|
||||
@OneToMany(
|
||||
() => ClaimItem,
|
||||
ci => ci.claim_order
|
||||
)
|
||||
claim_items: ClaimItem[]
|
||||
|
||||
@OneToMany(
|
||||
() => LineItem,
|
||||
li => li.claim_order,
|
||||
{ cascade: ["insert"] }
|
||||
)
|
||||
additional_items: LineItem[]
|
||||
|
||||
@Column({ type: "enum", enum: ClaimType })
|
||||
type: ClaimType
|
||||
|
||||
@Column()
|
||||
order_id: string
|
||||
|
||||
@ManyToOne(
|
||||
() => Order,
|
||||
o => o.claims
|
||||
)
|
||||
@JoinColumn({ name: "order_id" })
|
||||
order: Order
|
||||
|
||||
@OneToOne(
|
||||
() => Return,
|
||||
ret => ret.claim_order
|
||||
)
|
||||
return_order: Return
|
||||
|
||||
@Column({ nullable: true })
|
||||
shipping_address_id: string
|
||||
|
||||
@ManyToOne(() => Address, { cascade: ["insert"] })
|
||||
@JoinColumn({ name: "shipping_address_id" })
|
||||
shipping_address: Address
|
||||
|
||||
@OneToMany(
|
||||
() => ShippingMethod,
|
||||
method => method.claim_order,
|
||||
{ cascade: ["insert"] }
|
||||
)
|
||||
shipping_methods: ShippingMethod[]
|
||||
|
||||
@OneToMany(
|
||||
() => Fulfillment,
|
||||
fulfillment => fulfillment.claim_order,
|
||||
{ cascade: ["insert"] }
|
||||
)
|
||||
fulfillments: Fulfillment[]
|
||||
|
||||
@Column({ type: "int", nullable: true })
|
||||
refund_amount: number
|
||||
|
||||
@Column({ type: "timestamptz", nullable: true })
|
||||
canceled_at: Date
|
||||
|
||||
@CreateDateColumn({ type: "timestamptz" })
|
||||
created_at: Date
|
||||
|
||||
@UpdateDateColumn({ type: "timestamptz" })
|
||||
updated_at: Date
|
||||
|
||||
@DeleteDateColumn({ type: "timestamptz" })
|
||||
deleted_at: Date
|
||||
|
||||
@Column({ type: "jsonb", nullable: true })
|
||||
metadata: any
|
||||
|
||||
@Column({ nullable: true })
|
||||
idempotency_key: string
|
||||
|
||||
@BeforeInsert()
|
||||
private beforeInsert() {
|
||||
if (this.id) return
|
||||
const id = ulid()
|
||||
this.id = `claim_${id}`
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import {
|
||||
Entity,
|
||||
BeforeInsert,
|
||||
DeleteDateColumn,
|
||||
CreateDateColumn,
|
||||
UpdateDateColumn,
|
||||
Column,
|
||||
PrimaryColumn,
|
||||
Index,
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
JoinColumn,
|
||||
} from "typeorm"
|
||||
import { ulid } from "ulid"
|
||||
|
||||
@Entity()
|
||||
export class ClaimTag {
|
||||
@PrimaryColumn()
|
||||
id: string
|
||||
|
||||
@Index()
|
||||
@Column()
|
||||
value: string
|
||||
|
||||
@CreateDateColumn({ type: "timestamptz" })
|
||||
created_at: Date
|
||||
|
||||
@UpdateDateColumn({ type: "timestamptz" })
|
||||
updated_at: Date
|
||||
|
||||
@DeleteDateColumn({ type: "timestamptz" })
|
||||
deleted_at: Date
|
||||
|
||||
@Column({ type: "jsonb", nullable: true })
|
||||
metadata: any
|
||||
|
||||
@BeforeInsert()
|
||||
private beforeInsert() {
|
||||
if (this.id) return
|
||||
const id = ulid()
|
||||
this.id = `ctag_${id}`
|
||||
}
|
||||
}
|
||||
@@ -20,12 +20,23 @@ import { Order } from "./order"
|
||||
import { FulfillmentProvider } from "./fulfillment-provider"
|
||||
import { FulfillmentItem } from "./fulfillment-item"
|
||||
import { Swap } from "./swap"
|
||||
import { ClaimOrder } from "./claim-order"
|
||||
|
||||
@Entity()
|
||||
export class Fulfillment {
|
||||
@PrimaryColumn()
|
||||
id: string
|
||||
|
||||
@Column({ nullable: true })
|
||||
claim_order_id: string
|
||||
|
||||
@ManyToOne(
|
||||
() => ClaimOrder,
|
||||
co => co.fulfillments
|
||||
)
|
||||
@JoinColumn({ name: "claim_order_id" })
|
||||
claim_order: ClaimOrder
|
||||
|
||||
@Column({ nullable: true })
|
||||
swap_id: string
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import { ulid } from "ulid"
|
||||
import { Swap } from "./swap"
|
||||
import { Cart } from "./cart"
|
||||
import { Order } from "./order"
|
||||
import { ClaimOrder } from "./claim-order"
|
||||
import { ProductVariant } from "./product-variant"
|
||||
|
||||
@Check(`"fulfilled_quantity" <= "quantity"`)
|
||||
@@ -59,6 +60,17 @@ export class LineItem {
|
||||
@JoinColumn({ name: "swap_id" })
|
||||
swap: Swap
|
||||
|
||||
@Index()
|
||||
@Column({ nullable: true })
|
||||
claim_order_id: string
|
||||
|
||||
@ManyToOne(
|
||||
() => ClaimOrder,
|
||||
co => co.additional_items
|
||||
)
|
||||
@JoinColumn({ name: "claim_order_id" })
|
||||
claim_order: ClaimOrder
|
||||
|
||||
@Column()
|
||||
title: string
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ import { Fulfillment } from "./fulfillment"
|
||||
import { Return } from "./return"
|
||||
import { Refund } from "./refund"
|
||||
import { Swap } from "./swap"
|
||||
import { ClaimOrder } from "./claim-order"
|
||||
import { ShippingMethod } from "./shipping-method"
|
||||
|
||||
export enum OrderStatus {
|
||||
@@ -183,6 +184,13 @@ export class Order {
|
||||
)
|
||||
returns: Return[]
|
||||
|
||||
@OneToMany(
|
||||
() => ClaimOrder,
|
||||
co => co.order,
|
||||
{ cascade: ["insert"] }
|
||||
)
|
||||
claims: ClaimOrder[]
|
||||
|
||||
@OneToMany(
|
||||
() => Refund,
|
||||
ref => ref.order,
|
||||
|
||||
@@ -19,6 +19,7 @@ export enum RefundReason {
|
||||
DISCOUNT = "discount",
|
||||
RETURN = "return",
|
||||
SWAP = "swap",
|
||||
CLAIM = "claim",
|
||||
OTHER = "other",
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import { ulid } from "ulid"
|
||||
|
||||
import { Order } from "./order"
|
||||
import { Swap } from "./swap"
|
||||
import { ClaimOrder } from "./claim-order"
|
||||
import { ReturnItem } from "./return-item"
|
||||
import { ShippingMethod } from "./shipping-method"
|
||||
|
||||
@@ -52,6 +53,16 @@ export class Return {
|
||||
@JoinColumn({ name: "swap_id" })
|
||||
swap: Swap
|
||||
|
||||
@Column({ nullable: true })
|
||||
claim_order_id: string
|
||||
|
||||
@OneToOne(
|
||||
() => ClaimOrder,
|
||||
co => co.return_order
|
||||
)
|
||||
@JoinColumn({ name: "claim_order_id" })
|
||||
claim_order: ClaimOrder
|
||||
|
||||
@Column({ nullable: true })
|
||||
order_id: string
|
||||
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* models/money-amount.js
|
||||
*
|
||||
******************************************************************************/
|
||||
import mongoose from "mongoose"
|
||||
|
||||
export default new mongoose.Schema({
|
||||
region_id: { type: String },
|
||||
currency_code: { type: String, required: true },
|
||||
amount: { type: Number, required: true, min: 0 },
|
||||
sale_amount: { type: Number, min: 0 },
|
||||
})
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
} from "typeorm"
|
||||
import { ulid } from "ulid"
|
||||
|
||||
import { ClaimOrder } from "./claim-order"
|
||||
import { Order } from "./order"
|
||||
import { Cart } from "./cart"
|
||||
import { Swap } from "./swap"
|
||||
@@ -18,7 +19,7 @@ import { Return } from "./return"
|
||||
import { ShippingOption } from "./shipping-option"
|
||||
|
||||
@Check(
|
||||
`"order_id" IS NOT NULL OR "cart_id" IS NOT NULL OR "swap_id" IS NOT NULL OR "return_id" IS NOT NULL`
|
||||
`"claim_order_id" IS NOT NULL OR "order_id" IS NOT NULL OR "cart_id" IS NOT NULL OR "swap_id" IS NOT NULL OR "return_id" IS NOT NULL`
|
||||
)
|
||||
@Check(`"price" >= 0`)
|
||||
@Entity()
|
||||
@@ -38,6 +39,14 @@ export class ShippingMethod {
|
||||
@JoinColumn({ name: "order_id" })
|
||||
order: Order
|
||||
|
||||
@Index()
|
||||
@Column({ nullable: true })
|
||||
claim_order_id: string
|
||||
|
||||
@ManyToOne(() => ClaimOrder)
|
||||
@JoinColumn({ name: "claim_order_id" })
|
||||
claim_order: ClaimOrder
|
||||
|
||||
@Index()
|
||||
@Column({ nullable: true })
|
||||
cart_id: string
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import { EntityRepository, Repository } from "typeorm"
|
||||
import { ClaimImage } from "../models/claim-image"
|
||||
|
||||
@EntityRepository(ClaimImage)
|
||||
export class ClaimImageRepository extends Repository<ClaimImage> {}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { EntityRepository, Repository } from "typeorm"
|
||||
import { ClaimItem } from "../models/claim-item"
|
||||
|
||||
@EntityRepository(ClaimItem)
|
||||
export class ClaimItemRepository extends Repository<ClaimItem> {}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { EntityRepository, Repository } from "typeorm"
|
||||
import { ClaimTag } from "../models/claim-tag"
|
||||
|
||||
@EntityRepository(ClaimTag)
|
||||
export class ClaimTagRepository extends Repository<ClaimTag> {}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { EntityRepository, Repository } from "typeorm"
|
||||
import { ClaimOrder } from "../models/claim-order"
|
||||
|
||||
@EntityRepository(ClaimOrder)
|
||||
export class ClaimRepository extends Repository<ClaimOrder> {}
|
||||
@@ -0,0 +1,128 @@
|
||||
import _ from "lodash"
|
||||
import { IdMap, MockRepository, MockManager } from "medusa-test-utils"
|
||||
import ClaimItemService from "../claim-item"
|
||||
|
||||
const withTransactionMock = jest.fn()
|
||||
const eventBusService = {
|
||||
emit: jest.fn(),
|
||||
withTransaction: function() {
|
||||
withTransactionMock("eventBus")
|
||||
return this
|
||||
},
|
||||
}
|
||||
|
||||
describe("ClaimItemService", () => {
|
||||
describe("create", () => {
|
||||
const testItem = {
|
||||
claim_order_id: "claim_13",
|
||||
item_id: "itm_1",
|
||||
tags: ["fluff"],
|
||||
reason: "production_failure",
|
||||
note: "Details",
|
||||
quantity: 1,
|
||||
images: ["url.com/1234"],
|
||||
}
|
||||
|
||||
const claimTagRepo = MockRepository({
|
||||
findOne: () => Promise.resolve(),
|
||||
create: d => d,
|
||||
})
|
||||
|
||||
const claimImgRepo = MockRepository({
|
||||
findOne: () => Promise.resolve(),
|
||||
create: d => d,
|
||||
})
|
||||
|
||||
const claimItemRepo = MockRepository({
|
||||
create: d => ({ id: "ci_1234", ...d }),
|
||||
})
|
||||
|
||||
const lineItemService = {
|
||||
withTransaction: function() {
|
||||
withTransactionMock("lineItem")
|
||||
return this
|
||||
},
|
||||
}
|
||||
|
||||
const claimItemService = new ClaimItemService({
|
||||
manager: MockManager,
|
||||
lineItemService,
|
||||
claimTagRepository: claimTagRepo,
|
||||
claimItemRepository: claimItemRepo,
|
||||
claimImageRepository: claimImgRepo,
|
||||
eventBusService,
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("successfully creates a claim item", async () => {
|
||||
lineItemService.retrieve = jest.fn(() =>
|
||||
Promise.resolve({ fulfilled_quantity: 1 })
|
||||
)
|
||||
await claimItemService.create(testItem)
|
||||
|
||||
expect(withTransactionMock).toHaveBeenCalledWith("lineItem")
|
||||
expect(lineItemService.retrieve).toHaveBeenCalledTimes(1)
|
||||
|
||||
expect(claimTagRepo.findOne).toHaveBeenCalledTimes(1)
|
||||
expect(claimTagRepo.findOne).toHaveBeenCalledWith({
|
||||
where: { value: "fluff" },
|
||||
})
|
||||
|
||||
expect(claimTagRepo.create).toHaveBeenCalledTimes(1)
|
||||
expect(claimTagRepo.create).toHaveBeenCalledWith({
|
||||
value: "fluff",
|
||||
})
|
||||
|
||||
expect(claimItemRepo.create).toHaveBeenCalledTimes(1)
|
||||
expect(claimItemRepo.create).toHaveBeenCalledWith({
|
||||
claim_order_id: "claim_13",
|
||||
item_id: "itm_1",
|
||||
reason: "production_failure",
|
||||
note: "Details",
|
||||
quantity: 1,
|
||||
tags: [{ value: "fluff" }],
|
||||
images: [{ url: "url.com/1234" }],
|
||||
})
|
||||
})
|
||||
|
||||
it("normalizes claim tag value", async () => {
|
||||
lineItemService.retrieve = jest.fn(() =>
|
||||
Promise.resolve({ fulfilled_quantity: 1 })
|
||||
)
|
||||
await claimItemService.create({
|
||||
...testItem,
|
||||
tags: [" FLUFF "],
|
||||
})
|
||||
expect(claimTagRepo.findOne).toHaveBeenCalledTimes(1)
|
||||
expect(claimTagRepo.findOne).toHaveBeenCalledWith({
|
||||
where: { value: "fluff" },
|
||||
})
|
||||
})
|
||||
|
||||
it("fails if fulfilled_quantity < quantity", async () => {
|
||||
lineItemService.retrieve = jest.fn(() =>
|
||||
Promise.resolve({ fulfilled_quantity: 0 })
|
||||
)
|
||||
await expect(claimItemService.create(testItem)).rejects.toThrow(
|
||||
"Cannot claim more of an item than has been fulfilled"
|
||||
)
|
||||
})
|
||||
|
||||
it("fails if reason is unknown", async () => {
|
||||
lineItemService.retrieve = jest.fn(() =>
|
||||
Promise.resolve({ fulfilled_quantity: 1 })
|
||||
)
|
||||
await expect(
|
||||
claimItemService.create({
|
||||
...testItem,
|
||||
reason: "unknown",
|
||||
})
|
||||
).rejects.toThrow(
|
||||
`Claim Item reason must be one of "missing_item", "wrong_item", "production_failure" or "other".`
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,539 @@
|
||||
import _ from "lodash"
|
||||
import { IdMap, MockRepository, MockManager } from "medusa-test-utils"
|
||||
import ClaimService from "../claim"
|
||||
|
||||
const withTransactionMock = jest.fn()
|
||||
const eventBusService = {
|
||||
emit: jest.fn(),
|
||||
withTransaction: function() {
|
||||
withTransactionMock("eventBus")
|
||||
return this
|
||||
},
|
||||
}
|
||||
|
||||
const totalsService = {
|
||||
getRefundTotal: jest.fn(() => 1000),
|
||||
}
|
||||
|
||||
describe("ClaimService", () => {
|
||||
describe("create", () => {
|
||||
const testClaim = {
|
||||
type: "refund",
|
||||
order: {
|
||||
id: "1234",
|
||||
region_id: "order_region",
|
||||
items: [
|
||||
{
|
||||
id: "itm_1",
|
||||
unit_price: 8000,
|
||||
},
|
||||
],
|
||||
},
|
||||
claim_items: [
|
||||
{
|
||||
item_id: "itm_1",
|
||||
tags: ["fluff"],
|
||||
reason: "production_failure",
|
||||
note: "Details",
|
||||
quantity: 1,
|
||||
images: ["url.com/1234"],
|
||||
},
|
||||
],
|
||||
return_shipping: {
|
||||
option_id: "opt_13",
|
||||
price: 0,
|
||||
},
|
||||
additional_items: [
|
||||
{
|
||||
variant_id: "var_123",
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
shipping_address_id: "adr_1234",
|
||||
}
|
||||
|
||||
const claimRepo = MockRepository({
|
||||
create: d => ({ id: "claim_134", ...d }),
|
||||
})
|
||||
|
||||
const returnService = {
|
||||
create: jest.fn(),
|
||||
withTransaction: function() {
|
||||
withTransactionMock("return")
|
||||
return this
|
||||
},
|
||||
}
|
||||
|
||||
const lineItemService = {
|
||||
generate: jest.fn((d, _, q) => ({ variant_id: d, quantity: q })),
|
||||
withTransaction: function() {
|
||||
withTransactionMock("lineItem")
|
||||
return this
|
||||
},
|
||||
}
|
||||
|
||||
const claimItemService = {
|
||||
create: jest.fn(),
|
||||
withTransaction: function() {
|
||||
withTransactionMock("claimItem")
|
||||
return this
|
||||
},
|
||||
}
|
||||
|
||||
const claimService = new ClaimService({
|
||||
manager: MockManager,
|
||||
claimRepository: claimRepo,
|
||||
totalsService,
|
||||
returnService,
|
||||
lineItemService,
|
||||
claimItemService,
|
||||
eventBusService,
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("successfully creates a claim", async () => {
|
||||
await claimService.create(testClaim)
|
||||
|
||||
expect(withTransactionMock).toHaveBeenCalledWith("return")
|
||||
|
||||
expect(returnService.create).toHaveBeenCalledTimes(1)
|
||||
expect(returnService.create).toHaveBeenCalledWith(
|
||||
{
|
||||
claim_order_id: "claim_134",
|
||||
shipping_method: {
|
||||
option_id: "opt_13",
|
||||
price: 0,
|
||||
},
|
||||
items: [
|
||||
{
|
||||
item_id: "itm_1",
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "1234",
|
||||
region_id: "order_region",
|
||||
items: [
|
||||
{
|
||||
id: "itm_1",
|
||||
unit_price: 8000,
|
||||
},
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
expect(withTransactionMock).toHaveBeenCalledWith("lineItem")
|
||||
expect(lineItemService.generate).toHaveBeenCalledTimes(1)
|
||||
expect(lineItemService.generate).toHaveBeenCalledWith(
|
||||
"var_123",
|
||||
"order_region",
|
||||
1
|
||||
)
|
||||
|
||||
expect(withTransactionMock).toHaveBeenCalledWith("claimItem")
|
||||
expect(claimItemService.create).toHaveBeenCalledTimes(1)
|
||||
expect(claimItemService.create).toHaveBeenCalledWith({
|
||||
claim_order_id: "claim_134",
|
||||
item_id: "itm_1",
|
||||
tags: ["fluff"],
|
||||
reason: "production_failure",
|
||||
note: "Details",
|
||||
quantity: 1,
|
||||
images: ["url.com/1234"],
|
||||
})
|
||||
|
||||
expect(claimRepo.create).toHaveBeenCalledTimes(1)
|
||||
expect(claimRepo.create).toHaveBeenCalledWith({
|
||||
payment_status: "not_refunded",
|
||||
refund_amount: 1000,
|
||||
type: "refund",
|
||||
order_id: "1234",
|
||||
additional_items: [
|
||||
{
|
||||
variant_id: "var_123",
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
shipping_address_id: "adr_1234",
|
||||
})
|
||||
|
||||
expect(claimRepo.save).toHaveBeenCalledTimes(1)
|
||||
|
||||
expect(withTransactionMock).toHaveBeenCalledWith("eventBus")
|
||||
expect(eventBusService.emit).toHaveBeenCalledTimes(1)
|
||||
expect(eventBusService.emit).toHaveBeenCalledWith("claim.created", {
|
||||
id: "claim_134",
|
||||
})
|
||||
})
|
||||
|
||||
it("return only created when return shipping provided", async () => {
|
||||
await claimService.create({
|
||||
...testClaim,
|
||||
return_shipping: null,
|
||||
})
|
||||
expect(returnService.create).toHaveBeenCalledTimes(0)
|
||||
})
|
||||
|
||||
it("fails if replace and no additional items", async () => {
|
||||
await expect(
|
||||
claimService.create({
|
||||
...testClaim,
|
||||
type: "replace",
|
||||
additional_items: null,
|
||||
})
|
||||
).rejects.toThrow(
|
||||
`Claims with type "replace" must have at least one additional item.`
|
||||
)
|
||||
})
|
||||
|
||||
it("fails if replace and refund amount", async () => {
|
||||
await expect(
|
||||
claimService.create({
|
||||
...testClaim,
|
||||
type: "replace",
|
||||
refund_amount: 102,
|
||||
})
|
||||
).rejects.toThrow(
|
||||
`Claim has type "replace" but must be type "refund" to have a refund_amount.`
|
||||
)
|
||||
})
|
||||
|
||||
it("fails if type is unknown", async () => {
|
||||
await expect(
|
||||
claimService.create({
|
||||
...testClaim,
|
||||
type: "unknown",
|
||||
})
|
||||
).rejects.toThrow(`Claim type must be one of "refund" or "replace".`)
|
||||
})
|
||||
|
||||
it("fails if no claim items", async () => {
|
||||
await expect(
|
||||
claimService.create({
|
||||
...testClaim,
|
||||
claim_items: [],
|
||||
})
|
||||
).rejects.toThrow(`Claims must have at least one claim item.`)
|
||||
})
|
||||
})
|
||||
|
||||
describe("retrieve", () => {
|
||||
const claimRepo = MockRepository()
|
||||
const claimService = new ClaimService({
|
||||
manager: MockManager,
|
||||
claimRepository: claimRepo,
|
||||
eventBusService,
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("successfully creates a claim", async () => {
|
||||
claimRepo.setFindOne(() => Promise.resolve({ id: "claim_id" }))
|
||||
await claimService.retrieve("claim_id", {
|
||||
relations: ["order"],
|
||||
})
|
||||
|
||||
expect(claimRepo.findOne).toHaveBeenCalledWith({
|
||||
where: { id: "claim_id" },
|
||||
relations: ["order"],
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("createFulfillment", () => {
|
||||
const fulfillmentService = {
|
||||
createFulfillment: jest.fn((_, items) =>
|
||||
Promise.resolve([{ id: "ful", items }])
|
||||
),
|
||||
withTransaction: function() {
|
||||
withTransactionMock("fulfillment")
|
||||
return this
|
||||
},
|
||||
}
|
||||
|
||||
const order = {
|
||||
email: "hi@123",
|
||||
discounts: [
|
||||
{
|
||||
id: "disc_1234",
|
||||
},
|
||||
],
|
||||
payments: [{ id: "pay_test" }],
|
||||
currency_code: "dkk",
|
||||
tax_rate: 25,
|
||||
region_id: "test_region",
|
||||
display_id: 112345,
|
||||
billing_address: { first_name: "hi" },
|
||||
}
|
||||
|
||||
const claim = {
|
||||
type: "replace",
|
||||
fulfillment_status: "not_fulfilled",
|
||||
order,
|
||||
additional_items: [{ id: "item_test", quantity: 1 }],
|
||||
shipping_methods: [{ id: "method_test" }],
|
||||
}
|
||||
|
||||
const claimRepo = MockRepository({
|
||||
findOne: () => Promise.resolve({ ...claim }),
|
||||
})
|
||||
|
||||
const lineItemService = {
|
||||
update: jest.fn(),
|
||||
withTransaction: function() {
|
||||
withTransactionMock("lineItem")
|
||||
return this
|
||||
},
|
||||
}
|
||||
|
||||
const claimService = new ClaimService({
|
||||
manager: MockManager,
|
||||
claimRepository: claimRepo,
|
||||
fulfillmentService,
|
||||
lineItemService,
|
||||
eventBusService,
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("successfully creates fulfillment", async () => {
|
||||
await claimService.createFulfillment("claim_id", { meta: "data" })
|
||||
|
||||
expect(withTransactionMock).toHaveBeenCalledTimes(3)
|
||||
expect(withTransactionMock).toHaveBeenCalledWith("eventBus")
|
||||
|
||||
expect(withTransactionMock).toHaveBeenCalledWith("fulfillment")
|
||||
expect(fulfillmentService.createFulfillment).toHaveBeenCalledTimes(1)
|
||||
expect(fulfillmentService.createFulfillment).toHaveBeenCalledWith(
|
||||
{
|
||||
...claim,
|
||||
payments: order.payments,
|
||||
email: order.email,
|
||||
discounts: order.discounts,
|
||||
currency_code: order.currency_code,
|
||||
tax_rate: order.tax_rate,
|
||||
region_id: order.region_id,
|
||||
display_id: order.display_id,
|
||||
billing_address: order.billing_address,
|
||||
items: claim.additional_items,
|
||||
shipping_methods: claim.shipping_methods,
|
||||
is_claim: true,
|
||||
},
|
||||
[
|
||||
{
|
||||
item_id: "item_test",
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
{ claim_order_id: "claim_id", metadata: { meta: "data" } }
|
||||
)
|
||||
|
||||
expect(withTransactionMock).toHaveBeenCalledWith("lineItem")
|
||||
expect(lineItemService.update).toHaveBeenCalledTimes(1)
|
||||
expect(lineItemService.update).toHaveBeenCalledWith("item_test", {
|
||||
fulfilled_quantity: 1,
|
||||
})
|
||||
})
|
||||
|
||||
it("fails if no shipping_methods", async () => {
|
||||
claimRepo.setFindOne(() =>
|
||||
Promise.resolve({ ...claim, shipping_methods: [] })
|
||||
)
|
||||
|
||||
await expect(
|
||||
claimService.createFulfillment("claim_id", { meta: "data" })
|
||||
).rejects.toThrow(`Cannot fulfill a claim without a shipping method.`)
|
||||
})
|
||||
|
||||
it("fails if already fulfilled", async () => {
|
||||
claimRepo.setFindOne(() =>
|
||||
Promise.resolve({ ...claim, fulfillment_status: "fulfilled" })
|
||||
)
|
||||
|
||||
await expect(
|
||||
claimService.createFulfillment("claim_id", { meta: "data" })
|
||||
).rejects.toThrow(`The claim has already been fulfilled.`)
|
||||
})
|
||||
|
||||
it("fails if type is refund", async () => {
|
||||
claimRepo.setFindOne(() => Promise.resolve({ ...claim, type: "refund" }))
|
||||
|
||||
await expect(
|
||||
claimService.createFulfillment("claim_id", { meta: "data" })
|
||||
).rejects.toThrow(`Claims with the type "refund" can not be fulfilled`)
|
||||
})
|
||||
})
|
||||
|
||||
describe("createShipment", () => {
|
||||
const fulfillmentService = {
|
||||
createShipment: jest.fn(() => {
|
||||
return Promise.resolve({
|
||||
items: [
|
||||
{
|
||||
item_id: "item_1",
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
})
|
||||
}),
|
||||
withTransaction: function() {
|
||||
withTransactionMock("fulfillment")
|
||||
return this
|
||||
},
|
||||
}
|
||||
|
||||
const lineItemService = {
|
||||
update: jest.fn(),
|
||||
withTransaction: function() {
|
||||
withTransactionMock("lineItem")
|
||||
return this
|
||||
},
|
||||
}
|
||||
|
||||
const claimRepo = MockRepository()
|
||||
|
||||
const claimService = new ClaimService({
|
||||
manager: MockManager,
|
||||
claimRepository: claimRepo,
|
||||
fulfillmentService,
|
||||
lineItemService,
|
||||
eventBusService,
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("calls fulfillment service", async () => {
|
||||
claimRepo.setFindOne(() =>
|
||||
Promise.resolve({
|
||||
additional_items: [
|
||||
{
|
||||
id: "item_1",
|
||||
shippied_quantity: 1,
|
||||
},
|
||||
],
|
||||
})
|
||||
)
|
||||
|
||||
await claimService.createShipment("claim", "ful_123", ["track1234"], {
|
||||
meta: "data",
|
||||
})
|
||||
|
||||
expect(withTransactionMock).toHaveBeenCalledTimes(3)
|
||||
expect(withTransactionMock).toHaveBeenCalledWith("fulfillment")
|
||||
expect(withTransactionMock).toHaveBeenCalledWith("lineItem")
|
||||
expect(withTransactionMock).toHaveBeenCalledWith("eventBus")
|
||||
|
||||
expect(fulfillmentService.createShipment).toHaveBeenCalledTimes(1)
|
||||
expect(fulfillmentService.createShipment).toHaveBeenCalledWith(
|
||||
"ful_123",
|
||||
["track1234"],
|
||||
{ meta: "data" }
|
||||
)
|
||||
|
||||
expect(lineItemService.update).toHaveBeenCalledTimes(1)
|
||||
expect(lineItemService.update).toHaveBeenCalledWith("item_1", {
|
||||
shipped_quantity: 1,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("cancel", () => {
|
||||
const fulfillmentService = {
|
||||
cancelFulfillment: jest.fn(),
|
||||
withTransaction: function() {
|
||||
withTransactionMock("fulfillment")
|
||||
return this
|
||||
},
|
||||
}
|
||||
|
||||
const returnService = {
|
||||
cancel: jest.fn(),
|
||||
withTransaction: function() {
|
||||
withTransactionMock("return")
|
||||
return this
|
||||
},
|
||||
}
|
||||
|
||||
const claimRepo = MockRepository()
|
||||
|
||||
const claimService = new ClaimService({
|
||||
manager: MockManager,
|
||||
claimRepository: claimRepo,
|
||||
fulfillmentService,
|
||||
eventBusService,
|
||||
returnService,
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("calls fulfillment service", async () => {
|
||||
claimRepo.setFindOne(() =>
|
||||
Promise.resolve({
|
||||
return_order: {
|
||||
id: "ret",
|
||||
status: "requested",
|
||||
},
|
||||
fulfillments: [
|
||||
{
|
||||
id: "ful_21",
|
||||
},
|
||||
],
|
||||
})
|
||||
)
|
||||
|
||||
await claimService.cancel("claim")
|
||||
|
||||
expect(withTransactionMock).toHaveBeenCalledTimes(3)
|
||||
expect(withTransactionMock).toHaveBeenCalledWith("fulfillment")
|
||||
expect(withTransactionMock).toHaveBeenCalledWith("return")
|
||||
expect(withTransactionMock).toHaveBeenCalledWith("eventBus")
|
||||
|
||||
expect(fulfillmentService.cancelFulfillment).toHaveBeenCalledTimes(1)
|
||||
expect(fulfillmentService.cancelFulfillment).toHaveBeenCalledWith({
|
||||
id: "ful_21",
|
||||
})
|
||||
|
||||
expect(returnService.cancel).toHaveBeenCalledTimes(1)
|
||||
expect(returnService.cancel).toHaveBeenCalledWith("ret")
|
||||
})
|
||||
|
||||
it("fails if fulfillment is shipped", async () => {
|
||||
claimRepo.setFindOne(() =>
|
||||
Promise.resolve({
|
||||
fulfillment_status: "shipped",
|
||||
})
|
||||
)
|
||||
|
||||
await expect(claimService.cancel("id")).rejects.toThrow(
|
||||
"Cannot cancel a claim that has been shipped."
|
||||
)
|
||||
})
|
||||
|
||||
it("fails if return is received", async () => {
|
||||
claimRepo.setFindOne(() =>
|
||||
Promise.resolve({
|
||||
return_order: {
|
||||
id: "ret",
|
||||
status: "received",
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
await expect(claimService.cancel("id")).rejects.toThrow(
|
||||
"Cannot cancel a claim that has a received return."
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,271 @@
|
||||
import _ from "lodash"
|
||||
import { Validator, MedusaError } from "medusa-core-utils"
|
||||
import { BaseService } from "medusa-interfaces"
|
||||
import { Brackets } from "typeorm"
|
||||
|
||||
class ClaimItemService extends BaseService {
|
||||
static Events = {
|
||||
CREATED: "claim_item.created",
|
||||
UPDATED: "claim_item.updated",
|
||||
CANCELED: "claim_item.canceled",
|
||||
}
|
||||
|
||||
constructor({
|
||||
manager,
|
||||
claimItemRepository,
|
||||
claimTagRepository,
|
||||
claimImageRepository,
|
||||
lineItemService,
|
||||
eventBusService,
|
||||
}) {
|
||||
super()
|
||||
|
||||
/** @private @constant {EntityManager} */
|
||||
this.manager_ = manager
|
||||
|
||||
/** @private @constant {ClaimRepository} */
|
||||
this.claimItemRepository_ = claimItemRepository
|
||||
this.claimTagRepository_ = claimTagRepository
|
||||
this.claimImageRepository_ = claimImageRepository
|
||||
|
||||
/** @private @constant {LineItemService} */
|
||||
this.lineItemService_ = lineItemService
|
||||
|
||||
/** @private @constant {EventBus} */
|
||||
this.eventBus_ = eventBusService
|
||||
}
|
||||
|
||||
withTransaction(manager) {
|
||||
if (!manager) {
|
||||
return this
|
||||
}
|
||||
|
||||
const cloned = new ClaimItemService({
|
||||
manager,
|
||||
claimItemRepository: this.claimItemRepository_,
|
||||
claimTagRepository: this.claimTagRepository_,
|
||||
claimImageRepository: this.claimImageRepository_,
|
||||
lineItemService: this.lineItemService_,
|
||||
eventBusService: this.eventBus_,
|
||||
})
|
||||
|
||||
cloned.transactionManager_ = manager
|
||||
|
||||
return cloned
|
||||
}
|
||||
|
||||
create(data) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
const ciRepo = manager.getCustomRepository(this.claimItemRepository_)
|
||||
|
||||
const { item_id, reason, quantity, tags, images, ...rest } = data
|
||||
|
||||
if (
|
||||
reason !== "missing_item" &&
|
||||
reason !== "wrong_item" &&
|
||||
reason !== "production_failure" &&
|
||||
reason !== "other"
|
||||
) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`Claim Item reason must be one of "missing_item", "wrong_item", "production_failure" or "other".`
|
||||
)
|
||||
}
|
||||
|
||||
const lineItem = await this.lineItemService_
|
||||
.withTransaction(manager)
|
||||
.retrieve(item_id)
|
||||
|
||||
if (lineItem.fulfilled_quantity < quantity) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
"Cannot claim more of an item than has been fulfilled."
|
||||
)
|
||||
}
|
||||
|
||||
const claimTagRepo = manager.getCustomRepository(this.claimTagRepository_)
|
||||
const tagsToAdd = await Promise.all(
|
||||
tags.map(async t => {
|
||||
const normalized = t.trim().toLowerCase()
|
||||
const existing = await claimTagRepo.findOne({
|
||||
where: { value: normalized },
|
||||
})
|
||||
if (existing) {
|
||||
return existing
|
||||
}
|
||||
|
||||
return claimTagRepo.create({ value: normalized })
|
||||
})
|
||||
)
|
||||
|
||||
const claimImgRepo = manager.getCustomRepository(
|
||||
this.claimImageRepository_
|
||||
)
|
||||
const imagesToAdd = images.map(url => {
|
||||
return claimImgRepo.create({ url })
|
||||
})
|
||||
|
||||
const created = ciRepo.create({
|
||||
...rest,
|
||||
variant_id: lineItem.variant_id,
|
||||
tags: tagsToAdd,
|
||||
images: imagesToAdd,
|
||||
item_id,
|
||||
reason,
|
||||
quantity,
|
||||
})
|
||||
|
||||
const result = await ciRepo.save(created)
|
||||
|
||||
await this.eventBus_
|
||||
.withTransaction(manager)
|
||||
.emit(ClaimItemService.Events.CREATED, {
|
||||
id: result.id,
|
||||
})
|
||||
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
||||
update(id, data) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
const ciRepo = manager.getCustomRepository(this.claimItemRepository_)
|
||||
const item = await this.retrieve(id, { relations: ["images", "tags"] })
|
||||
|
||||
const { tags, images, reason, note, metadata } = data
|
||||
|
||||
if (note) {
|
||||
item.note = note
|
||||
}
|
||||
|
||||
if (reason) {
|
||||
item.reason = reason
|
||||
}
|
||||
|
||||
if (metadata) {
|
||||
item.metadata = this.setMetadata_(item, update.metadata)
|
||||
}
|
||||
|
||||
if (tags) {
|
||||
item.tags = []
|
||||
const claimTagRepo = manager.getCustomRepository(
|
||||
this.claimTagRepository_
|
||||
)
|
||||
for (const t of tags) {
|
||||
if (t.id) {
|
||||
item.tags.push(t)
|
||||
} else {
|
||||
const normalized = t.value.trim().toLowerCase()
|
||||
|
||||
const existing = await claimTagRepo.findOne({
|
||||
where: { value: normalized },
|
||||
})
|
||||
|
||||
if (existing) {
|
||||
item.tags.push(existing)
|
||||
} else {
|
||||
item.tags.push(claimTagRepo.create({ value: normalized }))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (images) {
|
||||
const claimImgRepo = manager.getCustomRepository(
|
||||
this.claimImageRepository_
|
||||
)
|
||||
const ids = images.map(i => i.id)
|
||||
for (const i of item.images) {
|
||||
if (!ids.includes(i.id)) {
|
||||
await claimImgRepo.remove(i)
|
||||
}
|
||||
}
|
||||
|
||||
item.images = []
|
||||
|
||||
for (const i of images) {
|
||||
if (i.id) {
|
||||
item.images.push(i)
|
||||
} else {
|
||||
item.images.push(claimImgRepo.create({ url: i.url }))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await ciRepo.save(item)
|
||||
|
||||
await this.eventBus_
|
||||
.withTransaction(manager)
|
||||
.emit(ClaimItemService.Events.UPDATED, {
|
||||
id: item.id,
|
||||
})
|
||||
|
||||
return item
|
||||
})
|
||||
}
|
||||
|
||||
async cancel(id) {}
|
||||
|
||||
/**
|
||||
* @param {Object} selector - the query object for find
|
||||
* @return {Promise} the result of the find operation
|
||||
*/
|
||||
async list(
|
||||
selector,
|
||||
config = { skip: 0, take: 50, order: { created_at: "DESC" } }
|
||||
) {
|
||||
const ciRepo = this.manager_.getCustomRepository(this.claimItemRepository_)
|
||||
const query = this.buildQuery_(selector, config)
|
||||
return ciRepo.find(query)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an order by id.
|
||||
* @param {string} orderId - id of order to retrieve
|
||||
* @return {Promise<Order>} the order document
|
||||
*/
|
||||
async retrieve(id, config = {}) {
|
||||
const claimItemRepo = this.manager_.getCustomRepository(
|
||||
this.claimItemRepository_
|
||||
)
|
||||
const validatedId = this.validateId_(id)
|
||||
|
||||
const query = this.buildQuery_({ id: validatedId }, config)
|
||||
const item = await claimItemRepo.findOne(query)
|
||||
|
||||
if (!item) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`Claim item with id: ${id} was not found.`
|
||||
)
|
||||
}
|
||||
|
||||
return item
|
||||
}
|
||||
|
||||
/**
|
||||
* Dedicated method to delete metadata for an order.
|
||||
* @param {string} orderId - the order to delete metadata from.
|
||||
* @param {string} key - key for metadata field
|
||||
* @return {Promise} resolves to the updated result.
|
||||
*/
|
||||
async deleteMetadata(orderId, key) {
|
||||
const validatedId = this.validateId_(orderId)
|
||||
|
||||
if (typeof key !== "string") {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_ARGUMENT,
|
||||
"Key type is invalid. Metadata keys must be strings"
|
||||
)
|
||||
}
|
||||
|
||||
const keyPath = `metadata.${key}`
|
||||
return this.orderModel_
|
||||
.updateOne({ _id: validatedId }, { $unset: { [keyPath]: "" } })
|
||||
.catch(err => {
|
||||
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default ClaimItemService
|
||||
@@ -0,0 +1,575 @@
|
||||
import _ from "lodash"
|
||||
import { Validator, MedusaError } from "medusa-core-utils"
|
||||
import { BaseService } from "medusa-interfaces"
|
||||
import { Brackets } from "typeorm"
|
||||
|
||||
class ClaimService extends BaseService {
|
||||
static Events = {
|
||||
CREATED: "claim.created",
|
||||
UPDATED: "claim.updated",
|
||||
CANCELED: "claim.canceled",
|
||||
FULFILLMENT_CREATED: "claim.fulfillment_created",
|
||||
SHIPMENT_CREATED: "claim.shipment_created",
|
||||
REFUND_PROCESSED: "claim.refund_processed",
|
||||
}
|
||||
|
||||
constructor({
|
||||
manager,
|
||||
claimRepository,
|
||||
fulfillmentProviderService,
|
||||
fulfillmentService,
|
||||
lineItemService,
|
||||
totalsService,
|
||||
paymentProviderService,
|
||||
returnService,
|
||||
shippingOptionService,
|
||||
claimItemService,
|
||||
regionService,
|
||||
eventBusService,
|
||||
}) {
|
||||
super()
|
||||
|
||||
/** @private @constant {EntityManager} */
|
||||
this.manager_ = manager
|
||||
|
||||
/** @private @constant {ClaimRepository} */
|
||||
this.claimRepository_ = claimRepository
|
||||
|
||||
/** @private @constant {FulfillmentProviderService} */
|
||||
this.fulfillmentProviderService_ = fulfillmentProviderService
|
||||
|
||||
/** @private @constant {PaymentProviderService} */
|
||||
this.paymentProviderService_ = paymentProviderService
|
||||
|
||||
/** @private @constant {LineItemService} */
|
||||
this.lineItemService_ = lineItemService
|
||||
|
||||
/** @private @constant {RegionService} */
|
||||
this.regionService_ = regionService
|
||||
|
||||
/** @private @constant {ReturnService} */
|
||||
this.returnService_ = returnService
|
||||
|
||||
/** @private @constant {FulfillmentService} */
|
||||
this.fulfillmentService_ = fulfillmentService
|
||||
|
||||
/** @private @constant {ClaimItemService} */
|
||||
this.claimItemService_ = claimItemService
|
||||
|
||||
/** @private @constant {TotalsService} */
|
||||
this.totalsService_ = totalsService
|
||||
|
||||
/** @private @constant {EventBus} */
|
||||
this.eventBus_ = eventBusService
|
||||
|
||||
/** @private @constant {ShippingOptionService} */
|
||||
this.shippingOptionService_ = shippingOptionService
|
||||
}
|
||||
|
||||
withTransaction(manager) {
|
||||
if (!manager) {
|
||||
return this
|
||||
}
|
||||
|
||||
const cloned = new ClaimService({
|
||||
manager,
|
||||
claimRepository: this.claimRepository_,
|
||||
fulfillmentProviderService: this.fulfillmentProviderService_,
|
||||
fulfillmentService: this.fulfillmentService_,
|
||||
paymentProviderService: this.paymentProviderService_,
|
||||
lineItemService: this.lineItemService_,
|
||||
regionService: this.regionService_,
|
||||
returnService: this.returnService_,
|
||||
claimItemService: this.claimItemService_,
|
||||
eventBusService: this.eventBus_,
|
||||
totalsService: this.totalsService_,
|
||||
shippingOptionService: this.shippingOptionService_,
|
||||
})
|
||||
|
||||
cloned.transactionManager_ = manager
|
||||
|
||||
return cloned
|
||||
}
|
||||
|
||||
update(id, data) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
const claimRepo = manager.getCustomRepository(this.claimRepository_)
|
||||
const claim = await this.retrieve(id, { relations: ["shipping_methods"] })
|
||||
|
||||
const { claim_items, shipping_methods, metadata } = data
|
||||
|
||||
if (metadata) {
|
||||
claim.metadata = this.setMetadata_(claim, update.metadata)
|
||||
await claimRepo.save(claim)
|
||||
}
|
||||
|
||||
if (shipping_methods) {
|
||||
for (const m of claim.shipping_methods) {
|
||||
await this.shippingOptionService_
|
||||
.withTransaction(manager)
|
||||
.updateShippingMethod(m.id, {
|
||||
claim_order_id: null,
|
||||
})
|
||||
}
|
||||
|
||||
for (const method of shipping_methods) {
|
||||
if (method.id) {
|
||||
await this.shippingOptionService_
|
||||
.withTransaction(manager)
|
||||
.updateShippingMethod(method.id, {
|
||||
claim_order_id: claim.id,
|
||||
})
|
||||
} else {
|
||||
await this.shippingOptionService_
|
||||
.withTransaction(manager)
|
||||
.createShippingMethod(method.option_id, method.data, {
|
||||
claim_order_id: claim.id,
|
||||
price: method.price,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (claim_items) {
|
||||
for (const i of claim_items) {
|
||||
if (i.id) {
|
||||
await this.claimItemService_
|
||||
.withTransaction(manager)
|
||||
.update(i.id, i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await this.eventBus_
|
||||
.withTransaction(manager)
|
||||
.emit(ClaimService.Events.UPDATED, {
|
||||
id: claim.id,
|
||||
})
|
||||
|
||||
return claim
|
||||
})
|
||||
}
|
||||
|
||||
create(data) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
const claimRepo = manager.getCustomRepository(this.claimRepository_)
|
||||
|
||||
const {
|
||||
type,
|
||||
claim_items,
|
||||
order,
|
||||
return_shipping,
|
||||
additional_items,
|
||||
shipping_methods,
|
||||
refund_amount,
|
||||
...rest
|
||||
} = data
|
||||
|
||||
if (type !== "refund" && type !== "replace") {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`Claim type must be one of "refund" or "replace".`
|
||||
)
|
||||
}
|
||||
|
||||
if (type === "replace" && !additional_items?.length) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`Claims with type "replace" must have at least one additional item.`
|
||||
)
|
||||
}
|
||||
|
||||
if (!claim_items?.length) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`Claims must have at least one claim item.`
|
||||
)
|
||||
}
|
||||
|
||||
if (refund_amount && type !== "refund") {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`Claim has type "${type}" but must be type "refund" to have a refund_amount.`
|
||||
)
|
||||
}
|
||||
|
||||
let toRefund = refund_amount
|
||||
if (type === "refund" && typeof refund_amount === "undefined") {
|
||||
const lines = claim_items.map(ci => {
|
||||
const orderItem = order.items.find(oi => oi.id === ci.item_id)
|
||||
return {
|
||||
...orderItem,
|
||||
quantity: ci.quantity,
|
||||
}
|
||||
})
|
||||
toRefund = await this.totalsService_.getRefundTotal(order, lines)
|
||||
}
|
||||
|
||||
const newItems = await Promise.all(
|
||||
additional_items.map(i =>
|
||||
this.lineItemService_
|
||||
.withTransaction(manager)
|
||||
.generate(i.variant_id, order.region_id, i.quantity)
|
||||
)
|
||||
)
|
||||
|
||||
const created = claimRepo.create({
|
||||
shipping_address_id: order.shipping_address_id,
|
||||
payment_status: type === "refund" ? "not_refunded" : "na",
|
||||
...rest,
|
||||
refund_amount: toRefund,
|
||||
type,
|
||||
additional_items: newItems,
|
||||
order_id: order.id,
|
||||
})
|
||||
|
||||
const result = await claimRepo.save(created)
|
||||
|
||||
if (shipping_methods) {
|
||||
for (const method of shipping_methods) {
|
||||
if (method.id) {
|
||||
await this.shippingOptionService_
|
||||
.withTransaction(manager)
|
||||
.updateShippingMethod(method.id, {
|
||||
claim_order_id: result.id,
|
||||
})
|
||||
} else {
|
||||
await this.shippingOptionService_
|
||||
.withTransaction(manager)
|
||||
.createShippingMethod(method.option_id, method.data, {
|
||||
claim_order_id: result.id,
|
||||
price: method.price,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const ci of claim_items) {
|
||||
await this.claimItemService_.withTransaction(manager).create({
|
||||
...ci,
|
||||
claim_order_id: result.id,
|
||||
})
|
||||
}
|
||||
|
||||
if (return_shipping) {
|
||||
await this.returnService_.withTransaction(manager).create(
|
||||
{
|
||||
claim_order_id: result.id,
|
||||
items: claim_items.map(ci => ({
|
||||
item_id: ci.item_id,
|
||||
quantity: ci.quantity,
|
||||
metadata: ci.metadata,
|
||||
})),
|
||||
shipping_method: return_shipping,
|
||||
},
|
||||
order
|
||||
)
|
||||
}
|
||||
|
||||
await this.eventBus_
|
||||
.withTransaction(manager)
|
||||
.emit(ClaimService.Events.CREATED, {
|
||||
id: result.id,
|
||||
})
|
||||
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
||||
createFulfillment(id, metadata = {}) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
const claim = await this.retrieve(id, {
|
||||
relations: [
|
||||
"additional_items",
|
||||
"shipping_methods",
|
||||
"shipping_address",
|
||||
"order",
|
||||
"order.billing_address",
|
||||
"order.discounts",
|
||||
"order.payments",
|
||||
],
|
||||
})
|
||||
|
||||
const order = claim.order
|
||||
|
||||
if (claim.fulfillment_status !== "not_fulfilled") {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
"The claim has already been fulfilled."
|
||||
)
|
||||
}
|
||||
|
||||
if (claim.type !== "replace") {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
`Claims with the type "${claim.type}" can not be fulfilled.`
|
||||
)
|
||||
}
|
||||
|
||||
if (!claim.shipping_methods?.length) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
"Cannot fulfill a claim without a shipping method."
|
||||
)
|
||||
}
|
||||
|
||||
const fulfillments = await this.fulfillmentService_
|
||||
.withTransaction(manager)
|
||||
.createFulfillment(
|
||||
{
|
||||
...claim,
|
||||
email: order.email,
|
||||
payments: order.payments,
|
||||
discounts: order.discounts,
|
||||
currency_code: order.currency_code,
|
||||
tax_rate: order.tax_rate,
|
||||
region_id: order.region_id,
|
||||
display_id: order.display_id,
|
||||
billing_address: order.billing_address,
|
||||
items: claim.additional_items,
|
||||
shipping_methods: claim.shipping_methods,
|
||||
is_claim: true,
|
||||
},
|
||||
claim.additional_items.map(i => ({
|
||||
item_id: i.id,
|
||||
quantity: i.quantity,
|
||||
})),
|
||||
{ claim_order_id: id, metadata }
|
||||
)
|
||||
|
||||
let successfullyFulfilled = []
|
||||
for (const f of fulfillments) {
|
||||
successfullyFulfilled = successfullyFulfilled.concat(f.items)
|
||||
}
|
||||
|
||||
claim.fulfillment_status = "fulfilled"
|
||||
|
||||
for (const item of claim.additional_items) {
|
||||
const fulfillmentItem = successfullyFulfilled.find(
|
||||
f => item.id === f.item_id
|
||||
)
|
||||
|
||||
if (fulfillmentItem) {
|
||||
const fulfilledQuantity =
|
||||
(item.fulfilled_quantity || 0) + fulfillmentItem.quantity
|
||||
|
||||
// Update the fulfilled quantity
|
||||
await this.lineItemService_.withTransaction(manager).update(item.id, {
|
||||
fulfilled_quantity: fulfilledQuantity,
|
||||
})
|
||||
|
||||
if (item.quantity !== fulfilledQuantity) {
|
||||
claim.fulfillment_status = "requires_action"
|
||||
}
|
||||
} else {
|
||||
if (item.quantity !== item.fulfilled_quantity) {
|
||||
claim.fulfillment_status = "requires_action"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const claimRepo = manager.getCustomRepository(this.claimRepository_)
|
||||
const result = await claimRepo.save(claim)
|
||||
|
||||
for (const fulfillment of fulfillments) {
|
||||
await this.eventBus_
|
||||
.withTransaction(manager)
|
||||
.emit(ClaimService.Events.FULFILLMENT_CREATED, {
|
||||
id: id,
|
||||
fulfillment_id: fulfillment.id,
|
||||
})
|
||||
}
|
||||
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
||||
async processRefund(id) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
const claim = await this.retrieve(id, {
|
||||
relations: ["order", "order.payments"],
|
||||
})
|
||||
|
||||
if (claim.type !== "refund") {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
`Claim must have type "refund" to create a refund.`
|
||||
)
|
||||
}
|
||||
|
||||
if (claim.refund_amount) {
|
||||
await this.paymentProviderService_
|
||||
.withTransaction(manager)
|
||||
.refundPayment(claim.order.payments, claim.refund_amount, "claim")
|
||||
}
|
||||
|
||||
claim.payment_status = "refunded"
|
||||
|
||||
const claimRepo = manager.getCustomRepository(this.claimRepository_)
|
||||
const result = await claimRepo.save(claim)
|
||||
|
||||
await this.eventBus_
|
||||
.withTransaction(manager)
|
||||
.emit(ClaimService.Events.REFUND_PROCESSED, {
|
||||
id,
|
||||
})
|
||||
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
||||
async createShipment(id, fulfillmentId, trackingNumbers, metadata = []) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
const claim = await this.retrieve(id, {
|
||||
relations: ["additional_items"],
|
||||
})
|
||||
|
||||
const shipment = await this.fulfillmentService_
|
||||
.withTransaction(manager)
|
||||
.createShipment(fulfillmentId, trackingNumbers, metadata)
|
||||
|
||||
claim.fulfillment_status = "shipped"
|
||||
|
||||
for (const i of claim.additional_items) {
|
||||
const shipped = shipment.items.find(si => si.item_id === i.id)
|
||||
if (shipped) {
|
||||
const shippedQty = (i.shipped_quantity || 0) + shipped.quantity
|
||||
await this.lineItemService_.withTransaction(manager).update(i.id, {
|
||||
shipped_quantity: shippedQty,
|
||||
})
|
||||
|
||||
if (shippedQty !== i.quantity) {
|
||||
claim.fulfillment_status = "partially_shipped"
|
||||
}
|
||||
} else {
|
||||
if (i.shipped_quantity !== i.quantity) {
|
||||
claim.fulfillment_status = "partially_shipped"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const claimRepo = manager.getCustomRepository(this.claimRepository_)
|
||||
const result = await claimRepo.save(claim)
|
||||
|
||||
await this.eventBus_
|
||||
.withTransaction(manager)
|
||||
.emit(ClaimService.Events.SHIPMENT_CREATED, {
|
||||
id,
|
||||
fulfillment_id: shipment.id,
|
||||
})
|
||||
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
||||
async cancel(id) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
const claim = await this.retrieve(id, {
|
||||
relations: ["return_order", "fulfillments"],
|
||||
})
|
||||
|
||||
if (
|
||||
claim.fulfillment_status === "shipped" ||
|
||||
claim.fulfillment_status === "partially_shipped"
|
||||
) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
`Cannot cancel a claim that has been shipped.`
|
||||
)
|
||||
}
|
||||
|
||||
if (claim?.return_order?.status === "received") {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
`Cannot cancel a claim that has a received return.`
|
||||
)
|
||||
}
|
||||
|
||||
if (claim.return_order) {
|
||||
await this.returnService_
|
||||
.withTransaction(manager)
|
||||
.cancel(claim.return_order.id)
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
claim.fulfillments.map(f =>
|
||||
this.fulfillmentService_.withTransaction(manager).cancelFulfillment(f)
|
||||
)
|
||||
)
|
||||
|
||||
claim.fulfillment_status = "canceled"
|
||||
|
||||
const claimRepo = manager.getCustomRepository(this.claimRepository_)
|
||||
const result = await claimRepo.save(claim)
|
||||
|
||||
await this.eventBus_
|
||||
.withTransaction(manager)
|
||||
.emit(ClaimService.Events.CANCELED, {
|
||||
id: result.id,
|
||||
})
|
||||
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object} selector - the query object for find
|
||||
* @return {Promise} the result of the find operation
|
||||
*/
|
||||
async list(
|
||||
selector,
|
||||
config = { skip: 0, take: 50, order: { created_at: "DESC" } }
|
||||
) {
|
||||
const claimRepo = this.manager_.getCustomRepository(this.claimRepository_)
|
||||
const query = this.buildQuery_(selector, config)
|
||||
return claimRepo.find(query)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an order by id.
|
||||
* @param {string} orderId - id of order to retrieve
|
||||
* @return {Promise<Order>} the order document
|
||||
*/
|
||||
async retrieve(claimId, config = {}) {
|
||||
const claimRepo = this.manager_.getCustomRepository(this.claimRepository_)
|
||||
const validatedId = this.validateId_(claimId)
|
||||
|
||||
const query = this.buildQuery_({ id: validatedId }, config)
|
||||
const claim = await claimRepo.findOne(query)
|
||||
|
||||
if (!claim) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`Claim with ${claimId} was not found`
|
||||
)
|
||||
}
|
||||
|
||||
return claim
|
||||
}
|
||||
|
||||
/**
|
||||
* Dedicated method to delete metadata for an order.
|
||||
* @param {string} orderId - the order to delete metadata from.
|
||||
* @param {string} key - key for metadata field
|
||||
* @return {Promise} resolves to the updated result.
|
||||
*/
|
||||
async deleteMetadata(orderId, key) {
|
||||
const validatedId = this.validateId_(orderId)
|
||||
|
||||
if (typeof key !== "string") {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_ARGUMENT,
|
||||
"Key type is invalid. Metadata keys must be strings"
|
||||
)
|
||||
}
|
||||
|
||||
const keyPath = `metadata.${key}`
|
||||
return this.orderModel_
|
||||
.updateOne({ _id: validatedId }, { $unset: { [keyPath]: "" } })
|
||||
.catch(err => {
|
||||
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default ClaimService
|
||||
@@ -311,6 +311,7 @@ class ReturnService extends BaseService {
|
||||
"shipping_method",
|
||||
"shipping_method.shipping_option",
|
||||
"swap",
|
||||
"claim_order",
|
||||
],
|
||||
})
|
||||
|
||||
|
||||
@@ -177,6 +177,10 @@ class ShippingOptionService extends BaseService {
|
||||
method.order_id = update.order_id
|
||||
}
|
||||
|
||||
if ("claim_order_id" in update) {
|
||||
method.claim_order_id = update.claim_order_id
|
||||
}
|
||||
|
||||
return methodRepo.save(method)
|
||||
})
|
||||
}
|
||||
@@ -246,6 +250,10 @@ class ShippingOptionService extends BaseService {
|
||||
toCreate.order_id = config.order_id
|
||||
}
|
||||
|
||||
if (config.claim_order_id) {
|
||||
toCreate.claim_order_id = config.claim_order_id
|
||||
}
|
||||
|
||||
const method = await methodRepo.create(toCreate)
|
||||
|
||||
const created = await methodRepo.save(method)
|
||||
|
||||
@@ -5589,10 +5589,10 @@ medusa-core-utils@^1.1.0:
|
||||
joi "^17.3.0"
|
||||
joi-objectid "^3.0.1"
|
||||
|
||||
medusa-test-utils@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/medusa-test-utils/-/medusa-test-utils-1.1.0.tgz#283189ba1bf1710bced86b6e4a1e25b71e0a0f4c"
|
||||
integrity sha512-eLAMd2n72xI1z3KecHuVyyr96acyylNR3SDP3DuC+4O3TOAg18EzFu3/W53z8MI2I5bBgGhQc7sEsN+krfVBJg==
|
||||
medusa-test-utils@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/medusa-test-utils/-/medusa-test-utils-1.1.1.tgz#e6c9faf9339a2fa22f162c9864091d6e12792215"
|
||||
integrity sha512-Fx55x1widi9yz3tQSvQbkACyNZMu05j9q9Ta0eDWwb1q96RpTQg0BMc0Em+ZXE9EwxWHqgDUqlAfLGmCW6aQUg==
|
||||
dependencies:
|
||||
"@babel/plugin-transform-classes" "^7.9.5"
|
||||
medusa-core-utils "^1.1.0"
|
||||
|
||||
Reference in New Issue
Block a user