diff --git a/integration-tests/api/__tests__/admin/customer.js b/integration-tests/api/__tests__/admin/customer.js new file mode 100644 index 0000000000..14c4222977 --- /dev/null +++ b/integration-tests/api/__tests__/admin/customer.js @@ -0,0 +1,135 @@ +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 customerSeeder = require("../../helpers/customer-seeder"); +const adminSeeder = require("../../helpers/admin-seeder"); + +jest.setTimeout(30000); + +describe("/admin/customers", () => { + 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/customers", () => { + beforeEach(async () => { + try { + await adminSeeder(dbConnection); + await customerSeeder(dbConnection); + } catch (err) { + console.log(err); + throw err; + } + }); + + afterEach(async () => { + const manager = dbConnection.manager; + await manager.query(`DELETE FROM "address"`); + await manager.query(`DELETE FROM "customer"`); + await manager.query(`DELETE FROM "user"`); + }); + + it("lists customers and query count", async () => { + const api = useApi(); + + const response = await api + .get("/admin/customers", { + headers: { + Authorization: "Bearer test_token", + }, + }) + .catch((err) => { + console.log(err); + }); + + expect(response.status).toEqual(200); + expect(response.data.count).toEqual(3); + expect(response.data.customers).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + id: "test-customer-1", + }), + expect.objectContaining({ + id: "test-customer-2", + }), + expect.objectContaining({ + id: "test-customer-3", + }), + ]) + ); + }); + + it("lists customers with specific query", async () => { + const api = useApi(); + + const response = await api + .get("/admin/customers?q=test2@email.com", { + headers: { + Authorization: "Bearer test_token", + }, + }) + .catch((err) => { + console.log(err); + }); + + expect(response.status).toEqual(200); + expect(response.data.count).toEqual(1); + expect(response.data.customers).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + id: "test-customer-2", + email: "test2@email.com", + }), + ]) + ); + }); + + it("lists customers with expand query", async () => { + const api = useApi(); + + const response = await api + .get("/admin/customers?q=test1@email.com&expand=shipping_addresses", { + headers: { + Authorization: "Bearer test_token", + }, + }) + .catch((err) => { + console.log(err); + }); + + expect(response.status).toEqual(200); + expect(response.data.count).toEqual(1); + console.log(response.data.customers); + expect(response.data.customers).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + id: "test-customer-1", + shipping_addresses: expect.arrayContaining([ + expect.objectContaining({ + id: "test-address", + first_name: "Lebron", + last_name: "James", + }), + ]), + }), + ]) + ); + }); + }); +}); diff --git a/integration-tests/api/helpers/customer-seeder.js b/integration-tests/api/helpers/customer-seeder.js new file mode 100644 index 0000000000..78b3e22ec5 --- /dev/null +++ b/integration-tests/api/helpers/customer-seeder.js @@ -0,0 +1,27 @@ +const { Customer, Address } = require("@medusajs/medusa"); + +module.exports = async (connection, data = {}) => { + const manager = connection.manager; + + await manager.insert(Customer, { + id: "test-customer-1", + email: "test1@email.com", + }); + + await manager.insert(Customer, { + id: "test-customer-2", + email: "test2@email.com", + }); + + await manager.insert(Customer, { + id: "test-customer-3", + email: "test3@email.com", + }); + + await manager.insert(Address, { + id: "test-address", + first_name: "Lebron", + last_name: "James", + customer_id: "test-customer-1", + }); +}; diff --git a/integration-tests/api/src/services/test-not.js b/integration-tests/api/src/services/test-not.js new file mode 100644 index 0000000000..77f3d9340e --- /dev/null +++ b/integration-tests/api/src/services/test-not.js @@ -0,0 +1,19 @@ +import { NotificationService } from "medusa-interfaces"; + +class TestNotiService extends NotificationService { + static identifier = "test-not"; + + constructor() { + super(); + } + + async sendNotification() { + return Promise.resolve(); + } + + async resendNotification() { + return Promise.resolve(); + } +} + +export default TestNotiService; diff --git a/packages/medusa-fulfillment-webshipper/CHANGELOG.md b/packages/medusa-fulfillment-webshipper/CHANGELOG.md index ec89b0aae2..2faa0faa67 100644 --- a/packages/medusa-fulfillment-webshipper/CHANGELOG.md +++ b/packages/medusa-fulfillment-webshipper/CHANGELOG.md @@ -3,6 +3,25 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.1.3](https://github.com/medusajs/medusa/compare/medusa-fulfillment-webshipper@1.1.3-next.0...medusa-fulfillment-webshipper@1.1.3) (2021-02-25) + +**Note:** Version bump only for package medusa-fulfillment-webshipper + + + + + +## [1.1.3-next.0](https://github.com/medusajs/medusa/compare/medusa-fulfillment-webshipper@1.1.2...medusa-fulfillment-webshipper@1.1.3-next.0) (2021-02-22) + + +### Features + +* **medusa:** tracking links ([#177](https://github.com/medusajs/medusa/issues/177)) ([99ad43b](https://github.com/medusajs/medusa/commit/99ad43bf47c3922f391d433448b1c4affd88f457)) + + + + + ## [1.1.2](https://github.com/medusajs/medusa/compare/medusa-fulfillment-webshipper@1.1.1...medusa-fulfillment-webshipper@1.1.2) (2021-02-17) diff --git a/packages/medusa-fulfillment-webshipper/package.json b/packages/medusa-fulfillment-webshipper/package.json index ef1762db12..0d644f4859 100644 --- a/packages/medusa-fulfillment-webshipper/package.json +++ b/packages/medusa-fulfillment-webshipper/package.json @@ -1,6 +1,6 @@ { "name": "medusa-fulfillment-webshipper", - "version": "1.1.2", + "version": "1.1.3", "description": "Webshipper Fulfillment provider for Medusa", "main": "index.js", "repository": { diff --git a/packages/medusa-fulfillment-webshipper/src/services/__tests__/webshipper-fulfillment.js b/packages/medusa-fulfillment-webshipper/src/services/__tests__/webshipper-fulfillment.js new file mode 100644 index 0000000000..71cbe37e95 --- /dev/null +++ b/packages/medusa-fulfillment-webshipper/src/services/__tests__/webshipper-fulfillment.js @@ -0,0 +1,211 @@ +import WebshipperFulfillmentService from "../webshipper-fulfillment" + +describe("WebshipperFulfillmentService", () => { + const orderService = { + createShipment: jest.fn(), + } + const swapService = { + createShipment: jest.fn(), + } + const claimService = { + createShipment: jest.fn(), + } + + describe("handleWebhook", () => { + beforeEach(() => { + jest.clearAllMocks() + }) + + it("creates an order shipment", async () => { + const webshipper = new WebshipperFulfillmentService( + { + orderService, + claimService, + swapService, + }, + {} + ) + + webshipper.retrieveRelationship = () => { + return { + data: { + attributes: { + ext_ref: "order_test.ful_test", + }, + }, + } + } + + const body = { + data: { + attributes: { + tracking_links: [ + { + url: "https://test/1134", + number: "12324245345", + }, + { + url: "https://test/1234", + number: "12324245345", + }, + ], + }, + relationships: { + order: { + id: "order", + }, + }, + }, + } + + await webshipper.handleWebhook("", body) + + expect(claimService.createShipment).toHaveBeenCalledTimes(0) + expect(swapService.createShipment).toHaveBeenCalledTimes(0) + + expect(orderService.createShipment).toHaveBeenCalledTimes(1) + expect(orderService.createShipment).toHaveBeenCalledWith( + "order_test", + "ful_test", + [ + { + url: "https://test/1134", + tracking_number: "12324245345", + }, + { + url: "https://test/1234", + tracking_number: "12324245345", + }, + ] + ) + }) + + it("creates a claim shipment", async () => { + const webshipper = new WebshipperFulfillmentService( + { + orderService, + claimService, + swapService, + }, + {} + ) + + webshipper.retrieveRelationship = () => { + return { + data: { + attributes: { + ext_ref: "claim_test.ful_test", + }, + }, + } + } + + const body = { + data: { + attributes: { + tracking_links: [ + { + url: "https://test/1134", + number: "12324245345", + }, + { + url: "https://test/1234", + number: "12324245345", + }, + ], + }, + relationships: { + order: { + id: "order", + }, + }, + }, + } + + await webshipper.handleWebhook("", body) + + expect(orderService.createShipment).toHaveBeenCalledTimes(0) + expect(swapService.createShipment).toHaveBeenCalledTimes(0) + + expect(claimService.createShipment).toHaveBeenCalledTimes(1) + expect(claimService.createShipment).toHaveBeenCalledWith( + "claim_test", + "ful_test", + [ + { + url: "https://test/1134", + tracking_number: "12324245345", + }, + { + url: "https://test/1234", + tracking_number: "12324245345", + }, + ] + ) + }) + + it("creates a swap shipment", async () => { + const webshipper = new WebshipperFulfillmentService( + { + orderService, + claimService, + swapService, + }, + {} + ) + + webshipper.retrieveRelationship = () => { + return { + data: { + attributes: { + ext_ref: "swap_test.ful_test", + }, + }, + } + } + + const body = { + data: { + attributes: { + tracking_links: [ + { + url: "https://test/1134", + number: "12324245345", + }, + { + url: "https://test/1234", + number: "12324245345", + }, + ], + }, + relationships: { + order: { + id: "order", + }, + }, + }, + } + + await webshipper.handleWebhook("", body) + + expect(orderService.createShipment).toHaveBeenCalledTimes(0) + expect(claimService.createShipment).toHaveBeenCalledTimes(0) + + expect(swapService.createShipment).toHaveBeenCalledTimes(1) + expect(swapService.createShipment).toHaveBeenCalledWith( + "swap_test", + "ful_test", + [ + { + url: "https://test/1134", + tracking_number: "12324245345", + }, + { + url: "https://test/1234", + tracking_number: "12324245345", + }, + ] + ) + }) + }) +}) diff --git a/packages/medusa-fulfillment-webshipper/src/services/webshipper-fulfillment.js b/packages/medusa-fulfillment-webshipper/src/services/webshipper-fulfillment.js index 43d96fd573..3504b906bc 100644 --- a/packages/medusa-fulfillment-webshipper/src/services/webshipper-fulfillment.js +++ b/packages/medusa-fulfillment-webshipper/src/services/webshipper-fulfillment.js @@ -363,9 +363,10 @@ class WebshipperFulfillmentService extends FulfillmentService { body.data.relationships.order ) if (wsOrder.data && wsOrder.data.attributes.ext_ref) { - const trackingNumbers = body.data.attributes.tracking_links.map( - (l) => l.number - ) + const trackingLinks = body.data.attributes.tracking_links.map((l) => ({ + url: l.url, + tracking_number: l.number, + })) const [orderId, fulfillmentIndex] = wsOrder.data.attributes.ext_ref.split( "." ) @@ -375,7 +376,7 @@ class WebshipperFulfillmentService extends FulfillmentService { return this.swapService_.createShipment( orderId, fulfillmentIndex, - trackingNumbers + trackingLinks ) } else { const swap = await this.swapService_.retrieve(orderId.substring(1), { @@ -385,21 +386,21 @@ class WebshipperFulfillmentService extends FulfillmentService { return this.swapService_.createShipment( swap.id, fulfillment.id, - trackingNumbers + trackingLinks ) } } else if (orderId.charAt(0).toLowerCase() === "c") { return this.claimService_.createShipment( orderId, fulfillmentIndex, - trackingNumbers + trackingLinks ) } else { if (fulfillmentIndex.startsWith("ful")) { return this.orderService_.createShipment( orderId, fulfillmentIndex, - trackingNumbers + trackingLinks ) } else { const order = await this.orderService_.retrieve(orderId, { @@ -411,7 +412,7 @@ class WebshipperFulfillmentService extends FulfillmentService { return this.orderService_.createShipment( order.id, fulfillment.id, - trackingNumbers + trackingLinks ) } } diff --git a/packages/medusa-plugin-brightpearl/CHANGELOG.md b/packages/medusa-plugin-brightpearl/CHANGELOG.md index acfde4efce..6ee2fb71aa 100644 --- a/packages/medusa-plugin-brightpearl/CHANGELOG.md +++ b/packages/medusa-plugin-brightpearl/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.1.5](https://github.com/medusajs/medusa/compare/medusa-plugin-brightpearl@1.1.4...medusa-plugin-brightpearl@1.1.5) (2021-02-25) + +**Note:** Version bump only for package medusa-plugin-brightpearl + + + + + ## [1.1.4](https://github.com/medusajs/medusa/compare/medusa-plugin-brightpearl@1.1.3...medusa-plugin-brightpearl@1.1.4) (2021-02-17) **Note:** Version bump only for package medusa-plugin-brightpearl diff --git a/packages/medusa-plugin-brightpearl/package.json b/packages/medusa-plugin-brightpearl/package.json index f765b8d772..bdd3b8470f 100644 --- a/packages/medusa-plugin-brightpearl/package.json +++ b/packages/medusa-plugin-brightpearl/package.json @@ -1,6 +1,6 @@ { "name": "medusa-plugin-brightpearl", - "version": "1.1.4", + "version": "1.1.5", "description": "Brightpearl plugin for Medusa Commerce", "main": "index.js", "repository": { diff --git a/packages/medusa-plugin-contentful/CHANGELOG.md b/packages/medusa-plugin-contentful/CHANGELOG.md index aada6dc0bf..0fa6c1d8bc 100644 --- a/packages/medusa-plugin-contentful/CHANGELOG.md +++ b/packages/medusa-plugin-contentful/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.1.4](https://github.com/medusajs/medusa/compare/medusa-plugin-contentful@1.1.3...medusa-plugin-contentful@1.1.4) (2021-02-25) + + +### Bug Fixes + +* **medusa-plugin-contentful:** Allow custom fields in plugin options ([#180](https://github.com/medusajs/medusa/issues/180)) ([587a464](https://github.com/medusajs/medusa/commit/587a464e83576833ff616bde7bb26b1bb48472fe)) + + + + + ## [1.1.3](https://github.com/medusajs/medusa/compare/medusa-plugin-contentful@1.1.2...medusa-plugin-contentful@1.1.3) (2021-02-17) diff --git a/packages/medusa-plugin-contentful/package.json b/packages/medusa-plugin-contentful/package.json index f95bf1a087..0a093930de 100644 --- a/packages/medusa-plugin-contentful/package.json +++ b/packages/medusa-plugin-contentful/package.json @@ -1,6 +1,6 @@ { "name": "medusa-plugin-contentful", - "version": "1.1.3", + "version": "1.1.4", "description": "Contentful plugin for Medusa Commerce", "main": "index.js", "repository": { diff --git a/packages/medusa-plugin-contentful/src/loaders/check-types.js b/packages/medusa-plugin-contentful/src/loaders/check-types.js index 88f6ea78ef..9c224621dd 100644 --- a/packages/medusa-plugin-contentful/src/loaders/check-types.js +++ b/packages/medusa-plugin-contentful/src/loaders/check-types.js @@ -19,8 +19,16 @@ const checkContentTypes = async (container) => { if (product && product.fields) { const productFields = product.fields + const customProductFields = Object.keys( + contentfulService.options_.custom_product_fields || {} + ) const keys = Object.values(productFields).map((f) => f.id) - if (!requiredProductFields.every((f) => keys.includes(f))) { + + const missingKeys = requiredProductFields.filter( + (rpf) => !keys.includes(rpf) && !customProductFields.includes(rpf) + ) + + if (missingKeys.length) { throw Error( `Contentful: Content type ${`product`} is missing some required key(s). Required: ${requiredProductFields.join( ", " @@ -32,8 +40,16 @@ const checkContentTypes = async (container) => { if (variant && variant.fields) { const variantFields = variant.fields + const customVariantFields = Object.keys( + contentfulService.options_.custom_variant_fields || {} + ) const keys = Object.values(variantFields).map((f) => f.id) - if (!requiredVariantFields.every((f) => keys.includes(f))) { + + const missingKeys = requiredVariantFields.filter( + (rpf) => !keys.includes(rpf) && !customVariantFields.includes(rpf) + ) + + if (missingKeys.length) { throw Error( `Contentful: Content type ${`productVariant`} is missing some required key(s). Required: ${requiredVariantFields.join( ", " @@ -47,13 +63,13 @@ const requiredProductFields = [ "title", "variants", "options", - "objectId", + "medusaId", "type", "collection", "tags", "handle", ] -const requiredVariantFields = ["title", "sku", "prices", "options", "objectId"] +const requiredVariantFields = ["title", "sku", "prices", "options", "medusaId"] export default checkContentTypes diff --git a/packages/medusa-plugin-contentful/src/services/contentful.js b/packages/medusa-plugin-contentful/src/services/contentful.js index 33f45acd32..b0ec7ce842 100644 --- a/packages/medusa-plugin-contentful/src/services/contentful.js +++ b/packages/medusa-plugin-contentful/src/services/contentful.js @@ -111,10 +111,27 @@ class ContentfulService extends BaseService { return assets } + getCustomField(field, type) { + const customOptions = this.options_[`custom_${type}_fields`] + + if (customOptions) { + return customOptions[field] || field + } else { + return field + } + } + async createProductInContentful(product) { try { const p = await this.productService_.retrieve(product.id, { - relations: ["variants", "options", "tags", "type", "collection"], + relations: [ + "variants", + "options", + "tags", + "type", + "collection", + "images", + ], }) const environment = await this.getContentfulEnvironment_() @@ -122,46 +139,92 @@ class ContentfulService extends BaseService { const variantLinks = this.getVariantLinks_(variantEntries) const fields = { - title: { + [this.getCustomField("title", "product")]: { "en-US": p.title, }, - variants: { + [this.getCustomField("variants", "product")]: { "en-US": variantLinks, }, - options: { + [this.getCustomField("options", "product")]: { "en-US": p.options, }, - objectId: { + [this.getCustomField("medusaId", "product")]: { "en-US": p.id, }, } + if (p.images.length > 0) { + const imageLinks = await this.createImageAssets(product) + + const thumbnailAsset = await environment.createAsset({ + fields: { + title: { + "en-US": `${p.title}`, + }, + description: { + "en-US": "", + }, + file: { + "en-US": { + contentType: "image/xyz", + fileName: p.thumbnail, + upload: p.thumbnail, + }, + }, + }, + }) + + await thumbnailAsset.processForAllLocales() + + const thumbnailLink = { + sys: { + type: "Link", + linkType: "Asset", + id: thumbnailAsset.sys.id, + }, + } + + fields.thumbnail = { + "en-US": thumbnailLink, + } + + if (imageLinks) { + fields.images = { + "en-US": imageLinks, + } + } + } + if (p.type) { const type = { "en-US": p.type.value, } - fields.type = type + + fields[this.getCustomField("type", "product")] = type } if (p.collection) { const collection = { "en-US": p.collection.title, } - fields.collection = collection + + fields[this.getCustomField("collection", "product")] = collection } if (p.tags) { const tags = { "en-US": p.tags, } - fields.tags = tags + + fields[this.getCustomField("tags", "product")] = tags } if (p.handle) { const handle = { "en-US": p.handle, } - fields.handle = handle + + fields[this.getCustomField("handle", "product")] = handle } const result = await environment.createEntryWithId("product", p.id, { @@ -189,19 +252,19 @@ class ContentfulService extends BaseService { v.id, { fields: { - title: { + [this.getCustomField("title", "variant")]: { "en-US": v.title, }, - sku: { + [this.getCustomField("sku", "variant")]: { "en-US": v.sku, }, - prices: { + [this.getCustomField("prices", "variant")]: { "en-US": v.prices, }, - options: { + [this.getCustomField("options", "variant")]: { "en-US": v.options, }, - objectId: { + [this.getCustomField("medusaId", "variant")]: { "en-US": v.id, }, }, @@ -240,7 +303,14 @@ class ContentfulService extends BaseService { } const p = await this.productService_.retrieve(product.id, { - relations: ["options", "variants", "type", "collection", "tags"], + relations: [ + "options", + "variants", + "type", + "collection", + "tags", + "images", + ], }) const variantEntries = await this.getVariantEntries_(p.variants) @@ -248,46 +318,86 @@ class ContentfulService extends BaseService { const productEntryFields = { ...productEntry.fields, - title: { + [this.getCustomField("title", "product")]: { "en-US": p.title, }, - options: { + [this.getCustomField("options", "product")]: { "en-US": p.options, }, - variants: { + [this.getCustomField("variants", "product")]: { "en-US": variantLinks, }, - objectId: { + [this.getCustomField("medusaId", "product")]: { "en-US": p.id, }, } + if (p.thumbnail) { + const thumbnailAsset = await environment.createAsset({ + fields: { + title: { + "en-US": `${p.title}`, + }, + description: { + "en-US": "", + }, + file: { + "en-US": { + contentType: "image/xyz", + fileName: p.thumbnail, + upload: p.thumbnail, + }, + }, + }, + }) + + await thumbnailAsset.processForAllLocales() + + const thumbnailLink = { + sys: { + type: "Link", + linkType: "Asset", + id: thumbnailAsset.sys.id, + }, + } + + productEntryFields.thumbnail = { + "en-US": thumbnailLink, + } + } + if (p.type) { const type = { "en-US": p.type.value, } - productEntryFields.type = type + + productEntryFields[this.getCustomField("type", "product")] = type } if (p.collection) { const collection = { "en-US": p.collection.title, } - productEntryFields.collection = collection + + productEntryFields[ + this.getCustomField("collection", "product") + ] = collection } if (p.tags) { const tags = { "en-US": p.tags, } - productEntryFields.tags = tags + + productEntryFields[this.getCustomField("tags", "product")] = tags } if (p.handle) { const handle = { "en-US": p.handle, } - productEntryFields.handle = handle + + productEntryFields[this.getCustomField("handle", "product")] = handle } productEntry.fields = productEntryFields @@ -333,19 +443,19 @@ class ContentfulService extends BaseService { const variantEntryFields = { ...variantEntry.fields, - title: { + [this.getCustomField("title", "variant")]: { "en-US": v.title, }, - sku: { + [this.getCustomField("sku", "variant")]: { "en-US": v.sku, }, - options: { + [this.getCustomField("options", "variant")]: { "en-US": v.options, }, - prices: { + [this.getCustomField("prices", "variant")]: { "en-US": v.prices, }, - objectId: { + [this.getCustomField("medusaId", "variant")]: { "en-US": v.id, }, } @@ -377,7 +487,8 @@ class ContentfulService extends BaseService { } let update = { - title: productEntry.fields.title["en-US"], + title: + productEntry.fields[this.getCustomField("title", "product")]["en-US"], } // Get the thumbnail, if present @@ -421,7 +532,10 @@ class ContentfulService extends BaseService { const updatedVariant = await this.productVariantService_.update( variantId, { - title: variantEntry.fields.title["en-US"], + title: + variantEntry.fields[this.getCustomField("title", "variant")][ + "en-US" + ], } ) diff --git a/packages/medusa-plugin-segment/CHANGELOG.md b/packages/medusa-plugin-segment/CHANGELOG.md index a2d98bcd21..87f9ecc0bc 100644 --- a/packages/medusa-plugin-segment/CHANGELOG.md +++ b/packages/medusa-plugin-segment/CHANGELOG.md @@ -3,6 +3,59 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.1.5](https://github.com/medusajs/medusa/compare/medusa-plugin-segment@1.1.5-next.3...medusa-plugin-segment@1.1.5) (2021-02-25) + +**Note:** Version bump only for package medusa-plugin-segment + + + + + +## [1.1.5-next.3](https://github.com/medusajs/medusa/compare/medusa-plugin-segment@1.1.5-next.2...medusa-plugin-segment@1.1.5-next.3) (2021-02-25) + + +### Bug Fixes + +* normalize currency code ([98aa404](https://github.com/medusajs/medusa/commit/98aa404306d55f0818d48e56c51146351ebfe306)) + + + + + +## [1.1.5-next.2](https://github.com/medusajs/medusa/compare/medusa-plugin-segment@1.1.5-next.1...medusa-plugin-segment@1.1.5-next.2) (2021-02-25) + + +### Features + +* **segment:** track shipments ([d156911](https://github.com/medusajs/medusa/commit/d15691188348c19fc22806d8cf7584fc5f249ce9)) + + + + + +## [1.1.5-next.1](https://github.com/medusajs/medusa/compare/medusa-plugin-segment@1.1.5-next.0...medusa-plugin-segment@1.1.5-next.1) (2021-02-25) + + +### Bug Fixes + +* add subtitle to tracks ([0c294b7](https://github.com/medusajs/medusa/commit/0c294b7b3acbc1b873aab7e90a8e596bdac48899)) +* versioning ([262af34](https://github.com/medusajs/medusa/commit/262af34125543d9a80bf469b5d380019b9bc8d3f)) + + + + + +## [1.1.5-next.0](https://github.com/medusajs/medusa/compare/medusa-plugin-segment@1.1.4...medusa-plugin-segment@1.1.5-next.0) (2021-02-22) + + +### Features + +* **medusa-plugin-segment:** adds category and type to segment events ([#179](https://github.com/medusajs/medusa/issues/179)) ([e27cf72](https://github.com/medusajs/medusa/commit/e27cf72a8ca49a6586a82dde964d559c40a4415f)) + + + + + ## [1.1.4](https://github.com/medusajs/medusa/compare/medusa-plugin-segment@1.1.3...medusa-plugin-segment@1.1.4) (2021-02-17) diff --git a/packages/medusa-plugin-segment/package.json b/packages/medusa-plugin-segment/package.json index 67817d6378..e3167bb4b4 100644 --- a/packages/medusa-plugin-segment/package.json +++ b/packages/medusa-plugin-segment/package.json @@ -1,6 +1,6 @@ { "name": "medusa-plugin-segment", - "version": "1.1.4", + "version": "1.1.5", "description": "Segment Analytics", "main": "index.js", "repository": { @@ -42,5 +42,5 @@ "medusa-core-utils": "^1.1.0", "medusa-test-utils": "^1.1.3" }, - "gitHead": "0646bd395a6056657cb0aa93c13699c4a9dbbcdd" + "gitHead": "0c294b7b3acbc1b873aab7e90a8e596bdac48899" } diff --git a/packages/medusa-plugin-segment/src/services/segment.js b/packages/medusa-plugin-segment/src/services/segment.js index eaeddcf5e8..fc4c653142 100644 --- a/packages/medusa-plugin-segment/src/services/segment.js +++ b/packages/medusa-plugin-segment/src/services/segment.js @@ -10,11 +10,12 @@ class SegmentService extends BaseService { * write_key: Segment write key given in Segment dashboard * } */ - constructor({ totalsService }, options) { + constructor({ totalsService, productService }, options) { super() this.totalsService_ = totalsService this.options_ = options + this.productService_ = productService this.analytics_ = new Analytics(options.write_key) } @@ -102,7 +103,7 @@ class SegmentService extends BaseService { tax, discount, coupon, - currency: order.currency_code, + currency: order.currency_code.toUpperCase(), products: await Promise.all( order.items.map(async (item) => { let name = item.title @@ -129,12 +130,20 @@ class SegmentService extends BaseService { variant = item.variant.sku } + const product = await this.productService_.retrieve( + item.variant.product_id, + { relations: ["collection", "type"] } + ) + return { name, variant, price: lineTotal / 100 / item.quantity, reporting_revenue: revenue, product_id: item.variant.product_id, + category: product.collection?.title, + subtitle: product.subtitle, + type: product.type?.value, sku, quantity: item.quantity, } diff --git a/packages/medusa-plugin-segment/src/subscribers/order.js b/packages/medusa-plugin-segment/src/subscribers/order.js index 3ee9ad40c7..c5eac08e03 100644 --- a/packages/medusa-plugin-segment/src/subscribers/order.js +++ b/packages/medusa-plugin-segment/src/subscribers/order.js @@ -5,12 +5,76 @@ class OrderSubscriber { orderService, claimService, returnService, + fulfillmentService, }) { this.orderService_ = orderService this.returnService_ = returnService this.claimService_ = claimService + this.fulfillmentService_ = fulfillmentService + + eventBusService.subscribe( + "order.shipment_created", + async ({ id, fulfillment_id }) => { + const order = await this.orderService_.retrieve(id, { + select: [ + "shipping_total", + "discount_total", + "tax_total", + "refunded_total", + "gift_card_total", + "subtotal", + "total", + ], + relations: [ + "customer", + "billing_address", + "shipping_address", + "discounts", + "shipping_methods", + "payments", + "fulfillments", + "returns", + "items", + "gift_cards", + "gift_card_transactions", + "swaps", + "swaps.return_order", + "swaps.payment", + "swaps.shipping_methods", + "swaps.shipping_address", + "swaps.additional_items", + "swaps.fulfillments", + ], + }) + + const fulfillment = await this.fulfillmentService_.retrieve( + fulfillment_id, + { + relations: ["items"], + } + ) + + const toBuildFrom = { + ...order, + provider_id: fulfillment.provider, + items: fulfillment.items.map((i) => + order.items.find((l) => l.id === i.item_id) + ), + } + + const orderData = await segmentService.buildOrder(toBuildFrom) + const orderEvent = { + event: "Order Shipped", + userId: order.customer_id, + properties: orderData, + timestamp: fulfillment.shipped_at, + } + + segmentService.track(orderEvent) + } + ) eventBusService.subscribe("claim.created", async ({ id }) => { const claim = await this.claimService_.retrieve(id, { @@ -74,6 +138,7 @@ class OrderSubscriber { "payments", "fulfillments", "returns", + "items", "gift_cards", "gift_card_transactions", "swaps", @@ -160,6 +225,7 @@ class OrderSubscriber { "shipping_methods", "payments", "fulfillments", + "items", "returns", "gift_cards", "gift_card_transactions", diff --git a/packages/medusa-plugin-sendgrid/CHANGELOG.md b/packages/medusa-plugin-sendgrid/CHANGELOG.md index d8a161b95c..1b7107ad42 100644 --- a/packages/medusa-plugin-sendgrid/CHANGELOG.md +++ b/packages/medusa-plugin-sendgrid/CHANGELOG.md @@ -3,6 +3,18 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.1.4](https://github.com/medusajs/medusa/compare/medusa-plugin-sendgrid@1.1.3...medusa-plugin-sendgrid@1.1.4) (2021-02-25) + + +### Bug Fixes + +* add tracking links to shipments ([7be4bb5](https://github.com/medusajs/medusa/commit/7be4bb5f2daa0aad805abe0f97278f53cf3af402)) +* sendgrid tracking links ([5cfc8d8](https://github.com/medusajs/medusa/commit/5cfc8d80bd3eaee93595027d0cc3ce67ae98d275)) + + + + + ## [1.1.3](https://github.com/medusajs/medusa/compare/medusa-plugin-sendgrid@1.1.2...medusa-plugin-sendgrid@1.1.3) (2021-02-17) diff --git a/packages/medusa-plugin-sendgrid/package.json b/packages/medusa-plugin-sendgrid/package.json index 762389382a..7df27324ba 100644 --- a/packages/medusa-plugin-sendgrid/package.json +++ b/packages/medusa-plugin-sendgrid/package.json @@ -1,6 +1,6 @@ { "name": "medusa-plugin-sendgrid", - "version": "1.1.3", + "version": "1.1.4", "description": "SendGrid transactional emails", "main": "index.js", "repository": { diff --git a/packages/medusa-plugin-sendgrid/src/services/sendgrid.js b/packages/medusa-plugin-sendgrid/src/services/sendgrid.js index 7dc39038c0..0d027700f8 100644 --- a/packages/medusa-plugin-sendgrid/src/services/sendgrid.js +++ b/packages/medusa-plugin-sendgrid/src/services/sendgrid.js @@ -275,7 +275,7 @@ class SendGridService extends NotificationService { }) const shipment = await this.fulfillmentService_.retrieve(fulfillment_id, { - relations: ["items"], + relations: ["items", "tracking_links"], }) return { @@ -283,6 +283,7 @@ class SendGridService extends NotificationService { date: shipment.shipped_at.toDateString(), email: order.email, fulfillment: shipment, + tracking_links: shipment.tracking_links, tracking_number: shipment.tracking_numbers.join(", "), } } @@ -586,7 +587,9 @@ class SendGridService extends NotificationService { const refundAmount = swap.return_order.refund_amount - const shipment = await this.fulfillmentService_.retrieve(fulfillment_id) + const shipment = await this.fulfillmentService_.retrieve(fulfillment_id, { + relations: ["tracking_links"], + }) return { swap, @@ -602,6 +605,7 @@ class SendGridService extends NotificationService { refund_amount: `${this.humanPrice_(refundAmount)} ${currencyCode}`, additional_total: `${this.humanPrice_(additionalTotal)} ${currencyCode}`, fulfillment: shipment, + tracking_links: shipment.tracking_links, tracking_number: shipment.tracking_numbers.join(", "), } } @@ -611,13 +615,16 @@ class SendGridService extends NotificationService { relations: ["order", "order.items", "order.shipping_address"], }) - const shipment = await this.fulfillmentService_.retrieve(fulfillment_id) + const shipment = await this.fulfillmentService_.retrieve(fulfillment_id, { + relations: ["tracking_links"], + }) return { email: claim.order.email, claim, order: claim.order, fulfillment: shipment, + tracking_links: shipment.tracking_links, tracking_number: shipment.tracking_numbers.join(", "), } } diff --git a/packages/medusa/CHANGELOG.md b/packages/medusa/CHANGELOG.md index 2d5fba459c..890c4e0c76 100644 --- a/packages/medusa/CHANGELOG.md +++ b/packages/medusa/CHANGELOG.md @@ -3,6 +3,47 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.1.11](https://github.com/medusajs/medusa/compare/@medusajs/medusa@1.1.10...@medusajs/medusa@1.1.11) (2021-02-25) + + +### Bug Fixes + +* **medusa:** Add querying func. on customer retrievals ([#181](https://github.com/medusajs/medusa/issues/181)) ([22be418](https://github.com/medusajs/medusa/commit/22be418ec132944afe469106ba4b3b92f634d240)) + + + + + +## [1.1.10](https://github.com/medusajs/medusa/compare/@medusajs/medusa@1.1.10-next.1...@medusajs/medusa@1.1.10) (2021-02-25) + +**Note:** Version bump only for package @medusajs/medusa + + + + + +## [1.1.10-next.1](https://github.com/medusajs/medusa/compare/@medusajs/medusa@1.1.10-next.0...@medusajs/medusa@1.1.10-next.1) (2021-02-25) + + +### Bug Fixes + +* update-product ([0320788](https://github.com/medusajs/medusa/commit/0320788aacf93da8a8951c6a540656da1772dba4)) + + + + + +## [1.1.10-next.0](https://github.com/medusajs/medusa/compare/@medusajs/medusa@1.1.9...@medusajs/medusa@1.1.10-next.0) (2021-02-22) + + +### Features + +* **medusa:** tracking links ([#177](https://github.com/medusajs/medusa/issues/177)) ([99ad43b](https://github.com/medusajs/medusa/commit/99ad43bf47c3922f391d433448b1c4affd88f457)) + + + + + ## [1.1.9](https://github.com/medusajs/medusa/compare/@medusajs/medusa@1.1.8...@medusajs/medusa@1.1.9) (2021-02-18) diff --git a/packages/medusa/package.json b/packages/medusa/package.json index 8ccd34dcdb..7fd7fd6f15 100644 --- a/packages/medusa/package.json +++ b/packages/medusa/package.json @@ -1,6 +1,6 @@ { "name": "@medusajs/medusa", - "version": "1.1.9", + "version": "1.1.11", "description": "E-commerce for JAMstack", "main": "dist/index.js", "repository": { diff --git a/packages/medusa/src/api/routes/admin/customers/list-customers.js b/packages/medusa/src/api/routes/admin/customers/list-customers.js index 6683f80e06..e0bcb9b9c8 100644 --- a/packages/medusa/src/api/routes/admin/customers/list-customers.js +++ b/packages/medusa/src/api/routes/admin/customers/list-customers.js @@ -2,18 +2,32 @@ export default async (req, res) => { try { const customerService = req.scope.resolve("customerService") - const limit = parseInt(req.query.limit) || 10 + const limit = parseInt(req.query.limit) || 50 const offset = parseInt(req.query.offset) || 0 + const selector = {} + + if ("q" in req.query) { + selector.q = req.query.q + } + + let expandFields = [] + if ("expand" in req.query) { + expandFields = req.query.expand.split(",") + } + const listConfig = { - relations: [], + relations: expandFields.length ? expandFields : [], skip: offset, take: limit, } - const customers = await customerService.list({}, listConfig) + const [customers, count] = await customerService.listAndCount( + selector, + listConfig + ) - res.json({ customers, count: customers.length, offset, limit }) + res.json({ customers, count, offset, limit }) } catch (error) { throw error } diff --git a/packages/medusa/src/api/routes/admin/orders/__tests__/get-order.js b/packages/medusa/src/api/routes/admin/orders/__tests__/get-order.js index 743e5c4f40..eaa0fc0136 100644 --- a/packages/medusa/src/api/routes/admin/orders/__tests__/get-order.js +++ b/packages/medusa/src/api/routes/admin/orders/__tests__/get-order.js @@ -10,6 +10,8 @@ const defaultRelations = [ "shipping_methods", "payments", "fulfillments", + "fulfillments.tracking_links", + "fulfillments.items", "returns", "gift_cards", "gift_card_transactions", diff --git a/packages/medusa/src/api/routes/admin/orders/create-claim-shipment.js b/packages/medusa/src/api/routes/admin/orders/create-claim-shipment.js index 840270e91a..d4c6c36a0c 100644 --- a/packages/medusa/src/api/routes/admin/orders/create-claim-shipment.js +++ b/packages/medusa/src/api/routes/admin/orders/create-claim-shipment.js @@ -23,7 +23,7 @@ export default async (req, res) => { await claimService.createShipment( claim_id, value.fulfillment_id, - value.tracking_numbers + value.tracking_numbers.map(n => ({ tracking_number: n })) ) const order = await orderService.retrieve(id, { diff --git a/packages/medusa/src/api/routes/admin/orders/create-shipment.js b/packages/medusa/src/api/routes/admin/orders/create-shipment.js index 24e0ebe199..2a5a097641 100644 --- a/packages/medusa/src/api/routes/admin/orders/create-shipment.js +++ b/packages/medusa/src/api/routes/admin/orders/create-shipment.js @@ -22,7 +22,7 @@ export default async (req, res) => { await orderService.createShipment( id, value.fulfillment_id, - value.tracking_numbers + value.tracking_numbers.map(n => ({ tracking_number: n })) ) const order = await orderService.retrieve(id, { diff --git a/packages/medusa/src/api/routes/admin/orders/create-swap-shipment.js b/packages/medusa/src/api/routes/admin/orders/create-swap-shipment.js index 700e1599e0..a407570f81 100644 --- a/packages/medusa/src/api/routes/admin/orders/create-swap-shipment.js +++ b/packages/medusa/src/api/routes/admin/orders/create-swap-shipment.js @@ -23,7 +23,7 @@ export default async (req, res) => { await swapService.createShipment( swap_id, value.fulfillment_id, - value.tracking_numbers + value.tracking_numbers.map(n => ({ tracking_number: n })) ) const order = await orderService.retrieve(id, { diff --git a/packages/medusa/src/api/routes/admin/orders/index.js b/packages/medusa/src/api/routes/admin/orders/index.js index ffbc5023d9..89278a310a 100644 --- a/packages/medusa/src/api/routes/admin/orders/index.js +++ b/packages/medusa/src/api/routes/admin/orders/index.js @@ -188,6 +188,8 @@ export const defaultRelations = [ "shipping_methods", "payments", "fulfillments", + "fulfillments.tracking_links", + "fulfillments.items", "returns", "gift_cards", "gift_card_transactions", @@ -271,6 +273,7 @@ export const allowedRelations = [ "shipping_methods", "payments", "fulfillments", + "fulfillments.tracking_links", "returns", "claims", "swaps", diff --git a/packages/medusa/src/api/routes/admin/products/update-product.js b/packages/medusa/src/api/routes/admin/products/update-product.js index d017e36951..a90d759868 100644 --- a/packages/medusa/src/api/routes/admin/products/update-product.js +++ b/packages/medusa/src/api/routes/admin/products/update-product.js @@ -6,6 +6,9 @@ export default async (req, res) => { const schema = Validator.object().keys({ title: Validator.string().optional(), + subtitle: Validator.string() + .optional() + .allow(null, ""), description: Validator.string().optional(), type: Validator.object() .keys({ diff --git a/packages/medusa/src/migrations/1613656135167-tracking_links.ts b/packages/medusa/src/migrations/1613656135167-tracking_links.ts new file mode 100644 index 0000000000..4755a24c04 --- /dev/null +++ b/packages/medusa/src/migrations/1613656135167-tracking_links.ts @@ -0,0 +1,16 @@ +import {MigrationInterface, QueryRunner} from "typeorm"; + +export class trackingLinks1613656135167 implements MigrationInterface { + name = 'trackingLinks1613656135167' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`CREATE TABLE "tracking_link" ("id" character varying NOT NULL, "url" character varying, "tracking_number" character varying NOT NULL, "fulfillment_id" 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, "idempotency_key" character varying, CONSTRAINT "PK_fcfd77feb9012ec2126d7c0bfb6" PRIMARY KEY ("id"))`); + await queryRunner.query(`ALTER TABLE "tracking_link" ADD CONSTRAINT "FK_471e9e4c96e02ba209a307db32b" FOREIGN KEY ("fulfillment_id") REFERENCES "fulfillment"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "tracking_link" DROP CONSTRAINT "FK_471e9e4c96e02ba209a307db32b"`); + await queryRunner.query(`DROP TABLE "tracking_link"`); + } + +} diff --git a/packages/medusa/src/models/fulfillment.ts b/packages/medusa/src/models/fulfillment.ts index e071feef3b..be6f4bb8f3 100644 --- a/packages/medusa/src/models/fulfillment.ts +++ b/packages/medusa/src/models/fulfillment.ts @@ -22,6 +22,7 @@ import { FulfillmentProvider } from "./fulfillment-provider" import { FulfillmentItem } from "./fulfillment-item" import { Swap } from "./swap" import { ClaimOrder } from "./claim-order" +import { TrackingLink } from "./tracking-link" @Entity() export class Fulfillment { @@ -76,6 +77,13 @@ export class Fulfillment { ) items: FulfillmentItem[] + @OneToMany( + () => TrackingLink, + tl => tl.fulfillment, + { cascade: ["insert"] } + ) + tracking_links: TrackingLink[] + @Column({ type: "jsonb", default: [] }) tracking_numbers: string[] diff --git a/packages/medusa/src/models/tracking-link.ts b/packages/medusa/src/models/tracking-link.ts new file mode 100644 index 0000000000..ded8e9bc1e --- /dev/null +++ b/packages/medusa/src/models/tracking-link.ts @@ -0,0 +1,63 @@ +import { + Entity, + Index, + BeforeInsert, + Column, + DeleteDateColumn, + CreateDateColumn, + UpdateDateColumn, + PrimaryColumn, + OneToOne, + OneToMany, + ManyToOne, + ManyToMany, + JoinColumn, + JoinTable, +} from "typeorm" +import { ulid } from "ulid" + +import { Fulfillment } from "./fulfillment" + +@Entity() +export class TrackingLink { + @PrimaryColumn() + id: string + + @Column({ nullable: true }) + url: string + + @Column() + tracking_number: string + + @Column() + fulfillment_id: string + + @ManyToOne( + () => Fulfillment, + ful => ful.tracking_links + ) + @JoinColumn({ name: "fulfillment_id" }) + fulfillment: Fulfillment + + @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 = `tlink_${id}` + } +} diff --git a/packages/medusa/src/repositories/tracking-link.ts b/packages/medusa/src/repositories/tracking-link.ts new file mode 100644 index 0000000000..785321a045 --- /dev/null +++ b/packages/medusa/src/repositories/tracking-link.ts @@ -0,0 +1,5 @@ +import { EntityRepository, Repository } from "typeorm" +import { TrackingLink } from "../models/tracking-link" + +@EntityRepository(TrackingLink) +export class TrackingLinkRepository extends Repository {} diff --git a/packages/medusa/src/services/__tests__/fulfillment.js b/packages/medusa/src/services/__tests__/fulfillment.js index 2d20deadf0..2c178ad705 100644 --- a/packages/medusa/src/services/__tests__/fulfillment.js +++ b/packages/medusa/src/services/__tests__/fulfillment.js @@ -95,6 +95,7 @@ describe("FulfillmentService", () => { }) describe("createShipment", () => { + const trackingLinkRepository = MockRepository({ create: c => c }) const fulfillmentRepository = MockRepository({ findOne: () => Promise.resolve({ id: IdMap.getId("fulfillment") }), }) @@ -102,6 +103,7 @@ describe("FulfillmentService", () => { const fulfillmentService = new FulfillmentService({ manager: MockManager, fulfillmentRepository, + trackingLinkRepository, }) const now = new Date() @@ -113,14 +115,17 @@ describe("FulfillmentService", () => { it("calls order model functions", async () => { await fulfillmentService.createShipment( IdMap.getId("fulfillment"), - ["1234", "2345"], + [{ tracking_number: "1234" }, { tracking_number: "2345" }], {} ) expect(fulfillmentRepository.save).toHaveBeenCalledTimes(1) expect(fulfillmentRepository.save).toHaveBeenCalledWith({ id: IdMap.getId("fulfillment"), - tracking_numbers: ["1234", "2345"], + tracking_links: [ + { tracking_number: "1234" }, + { tracking_number: "2345" }, + ], metadata: {}, shipped_at: now, }) diff --git a/packages/medusa/src/services/__tests__/order.js b/packages/medusa/src/services/__tests__/order.js index d2c270fdb0..262504ea33 100644 --- a/packages/medusa/src/services/__tests__/order.js +++ b/packages/medusa/src/services/__tests__/order.js @@ -1182,14 +1182,14 @@ describe("OrderService", () => { await orderService.createShipment( IdMap.getId("test"), IdMap.getId("fulfillment"), - ["1234", "2345"], + [{ tracking_number: "1234" }, { tracking_number: "2345" }], {} ) expect(fulfillmentService.createShipment).toHaveBeenCalledTimes(1) expect(fulfillmentService.createShipment).toHaveBeenCalledWith( IdMap.getId("fulfillment"), - ["1234", "2345"], + [{ tracking_number: "1234" }, { tracking_number: "2345" }], {} ) diff --git a/packages/medusa/src/services/claim.js b/packages/medusa/src/services/claim.js index f8aa1db3ae..ccc2b663e5 100644 --- a/packages/medusa/src/services/claim.js +++ b/packages/medusa/src/services/claim.js @@ -418,7 +418,7 @@ class ClaimService extends BaseService { }) } - async createShipment(id, fulfillmentId, trackingNumbers, metadata = []) { + async createShipment(id, fulfillmentId, trackingLinks, metadata = []) { return this.atomicPhase_(async manager => { const claim = await this.retrieve(id, { relations: ["additional_items"], @@ -426,7 +426,7 @@ class ClaimService extends BaseService { const shipment = await this.fulfillmentService_ .withTransaction(manager) - .createShipment(fulfillmentId, trackingNumbers, metadata) + .createShipment(fulfillmentId, trackingLinks, metadata) claim.fulfillment_status = "shipped" diff --git a/packages/medusa/src/services/customer.js b/packages/medusa/src/services/customer.js index 6db5092f03..486e89c210 100644 --- a/packages/medusa/src/services/customer.js +++ b/packages/medusa/src/services/customer.js @@ -3,6 +3,7 @@ import Scrypt from "scrypt-kdf" import _ from "lodash" import { Validator, MedusaError } from "medusa-core-utils" import { BaseService } from "medusa-interfaces" +import { Brackets } from "typeorm" /** * Provides layer to manipulate customers. @@ -132,6 +133,50 @@ class CustomerService extends BaseService { return customerRepo.find(query) } + async listAndCount( + selector, + config = { relations: [], skip: 0, take: 50, order: { created_at: "DESC" } } + ) { + const customerRepo = this.manager_.getCustomRepository( + this.customerRepository_ + ) + + let q + if ("q" in selector) { + q = selector.q + delete selector.q + } + + const query = this.buildQuery_(selector, config) + + if (q) { + const where = query.where + + delete where.email + delete where.first_name + delete where.last_name + + query.join = { + alias: "customer", + } + + query.where = qb => { + qb.where(where) + + qb.andWhere( + new Brackets(qb => { + qb.where(`customer.first_name ILIKE :q`, { q: `%${q}%` }) + .orWhere(`customer.last_name ILIKE :q`, { q: `%${q}%` }) + .orWhere(`customer.email ILIKE :q`, { q: `%${q}%` }) + }) + ) + } + } + + const [customers, count] = await customerRepo.findAndCount(query) + return [customers, count] + } + /** * Return the total number of documents in database * @return {Promise} the result of the count operation diff --git a/packages/medusa/src/services/fulfillment.js b/packages/medusa/src/services/fulfillment.js index 0548ff2510..b05363978d 100644 --- a/packages/medusa/src/services/fulfillment.js +++ b/packages/medusa/src/services/fulfillment.js @@ -11,6 +11,7 @@ class FulfillmentService extends BaseService { manager, totalsService, fulfillmentRepository, + trackingLinkRepository, shippingProfileService, lineItemService, fulfillmentProviderService, @@ -26,6 +27,9 @@ class FulfillmentService extends BaseService { /** @private @const {FulfillmentRepository} */ this.fulfillmentRepository_ = fulfillmentRepository + /** @private @const {TrackingLinkRepository} */ + this.trackingLinkRepository_ = trackingLinkRepository + /** @private @const {ShippingProfileService} */ this.shippingProfileService_ = shippingProfileService @@ -44,6 +48,7 @@ class FulfillmentService extends BaseService { const cloned = new FulfillmentService({ manager: transactionManager, totalsService: this.totalsService_, + trackingLinkRepository: this.trackingLinkRepository_, fulfillmentRepository: this.fulfillmentRepository_, shippingProfileService: this.shippingProfileService_, lineItemService: this.lineItemService_, @@ -235,15 +240,18 @@ class FulfillmentService extends BaseService { * Creates a shipment by marking a fulfillment as shipped. Adds * tracking numbers and potentially more metadata. * @param {Order} fulfillmentId - the fulfillment to ship - * @param {string[]} trackingNumbers - tracking numbers for the shipment + * @param {TrackingLink[]} trackingNumbers - tracking numbers for the shipment * @param {object} metadata - potential metadata to add * @return {Fulfillment} the shipped fulfillment */ - async createShipment(fulfillmentId, trackingNumbers, metadata) { + async createShipment(fulfillmentId, trackingLinks, metadata) { return this.atomicPhase_(async manager => { const fulfillmentRepository = manager.getCustomRepository( this.fulfillmentRepository_ ) + const trackingLinkRepo = manager.getCustomRepository( + this.trackingLinkRepository_ + ) const fulfillment = await this.retrieve(fulfillmentId, { relations: ["items"], @@ -251,7 +259,11 @@ class FulfillmentService extends BaseService { const now = new Date() fulfillment.shipped_at = now - fulfillment.tracking_numbers = trackingNumbers + + fulfillment.tracking_links = trackingLinks.map(tl => + trackingLinkRepo.create(tl) + ) + fulfillment.metadata = { ...fulfillment.metadata, ...metadata, diff --git a/packages/medusa/src/services/order.js b/packages/medusa/src/services/order.js index 4f37c5a030..aee1438081 100644 --- a/packages/medusa/src/services/order.js +++ b/packages/medusa/src/services/order.js @@ -553,13 +553,13 @@ class OrderService extends BaseService { * have been created in regards to the shipment. * @param {string} orderId - the id of the order that has been shipped * @param {string} fulfillmentId - the fulfillment that has now been shipped - * @param {Array} trackingNumbers - array of tracking numebers + * @param {TrackingLink[]} trackingLinks - array of tracking numebers * associated with the shipment * @param {Dictionary} metadata - optional metadata to add to * the fulfillment * @return {order} the resulting order following the update. */ - async createShipment(orderId, fulfillmentId, trackingNumbers, metadata = {}) { + async createShipment(orderId, fulfillmentId, trackingLinks, metadata = {}) { return this.atomicPhase_(async manager => { const order = await this.retrieve(orderId, { relations: ["items"] }) const shipment = await this.fulfillmentService_.retrieve(fulfillmentId) @@ -573,7 +573,7 @@ class OrderService extends BaseService { const shipmentRes = await this.fulfillmentService_ .withTransaction(manager) - .createShipment(fulfillmentId, trackingNumbers, metadata) + .createShipment(fulfillmentId, trackingLinks, metadata) order.fulfillment_status = "shipped" for (const item of order.items) { diff --git a/packages/medusa/src/services/swap.js b/packages/medusa/src/services/swap.js index bc97b3aff4..5d06abfd1e 100644 --- a/packages/medusa/src/services/swap.js +++ b/packages/medusa/src/services/swap.js @@ -671,12 +671,12 @@ class SwapService extends BaseService { * @param {string} swapId - the id of the swap that has been shipped. * @param {string} fulfillmentId - the id of the specific fulfillment that * has been shipped - * @param {Array} trackingNumbers - the tracking numbers associated + * @param {TrackingLink[]} trackingLinks - the tracking numbers associated * with the shipment * @param {object} metadata - optional metadata to attach to the shipment. * @returns {Promise} the updated swap with new fulfillments and status. */ - async createShipment(swapId, fulfillmentId, trackingNumbers, metadata = {}) { + async createShipment(swapId, fulfillmentId, trackingLinks, metadata = {}) { return this.atomicPhase_(async manager => { const swap = await this.retrieve(swapId, { relations: ["additional_items"], @@ -685,7 +685,7 @@ class SwapService extends BaseService { // Update the fulfillment to register const shipment = await this.fulfillmentService_ .withTransaction(manager) - .createShipment(fulfillmentId, trackingNumbers, metadata) + .createShipment(fulfillmentId, trackingLinks, metadata) swap.fulfillment_status = "shipped"