Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(medusa, stock-location-next): add list-stock-locations endpoint to api-v2 #6788

Merged
merged 20 commits into from
Mar 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/wild-houses-warn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@medusajs/stock-location-next": patch
"@medusajs/medusa": patch
---

feat(medusa, stock-location-next): add list-stock-locations endpoint to api-v2
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,101 @@ medusaIntegrationTestRunner({
})
})

describe("list stock locations", () => {
let location1
let location2
beforeEach(async () => {
const location1CreateResponse = await api.post(
`/admin/stock-locations`,
{
name: "Test Location 1",
address: {
address_1: "Test Address",
country_code: "US",
},
},
adminHeaders
)
location1 = location1CreateResponse.data.stock_location
const location2CreateResponse = await api.post(
`/admin/stock-locations`,
{
name: "Test Location 2",
address: {
address_1: "Test Address",
country_code: "US",
},
},
adminHeaders
)
location2 = location2CreateResponse.data.stock_location
})

it("should list stock locations", async () => {
const listLocationsResponse = await api.get(
"/admin/stock-locations",
adminHeaders
)

expect(listLocationsResponse.status).toEqual(200)
expect(listLocationsResponse.data.stock_locations).toEqual([
expect.objectContaining(location1),
expect.objectContaining(location2),
])
})

it("should filter stock locations by name", async () => {
const listLocationsResponse = await api.get(
"/admin/stock-locations?name=Test%20Location%201",
adminHeaders
)

expect(listLocationsResponse.status).toEqual(200)
expect(listLocationsResponse.data.stock_locations).toEqual([
expect.objectContaining(location1),
])
})

it("should filter stock locations by partial name with q parameter", async () => {
const listLocationsResponse = await api.get(
"/admin/stock-locations?q=ation%201",
adminHeaders
)

expect(listLocationsResponse.status).toEqual(200)
expect(listLocationsResponse.data.stock_locations).toEqual([
expect.objectContaining(location1),
])
})

it("should filter stock locations on sales_channel_id", async () => {
const remoteLinkService = appContainer.resolve(
ContainerRegistrationKeys.REMOTE_LINK
)

await remoteLinkService.create([
{
[Modules.SALES_CHANNEL]: {
sales_channel_id: "default",
},
[Modules.STOCK_LOCATION]: {
stock_location_id: location1.id,
},
},
])

const listLocationsResponse = await api.get(
"/admin/stock-locations?sales_channel_id=default",
adminHeaders
)

expect(listLocationsResponse.status).toEqual(200)
expect(listLocationsResponse.data.stock_locations).toEqual([
expect.objectContaining(location1),
])
})
})

describe("Update stock locations", () => {
let stockLocationId

Expand Down
13 changes: 13 additions & 0 deletions packages/medusa/src/api-v2/admin/stock-locations/middlewares.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as QueryConfig from "./query-config"

import {
AdminGetStockLocationsLocationParams,
AdminGetStockLocationsParams,
AdminPostStockLocationsLocationParams,
AdminPostStockLocationsLocationReq,
AdminPostStockLocationsParams,
Expand All @@ -10,6 +11,7 @@ import {
import { transformBody, transformQuery } from "../../../api/middlewares"

import { MiddlewareRoute } from "../../../types/middlewares"
import { applySalesChannelsFilter } from "./utils/apply-sales-channel-filter"
import { authenticate } from "../../../utils/authenticate-middleware"

export const adminStockLocationRoutesMiddlewares: MiddlewareRoute[] = [
Expand All @@ -29,6 +31,17 @@ export const adminStockLocationRoutesMiddlewares: MiddlewareRoute[] = [
),
],
},
{
method: ["GET"],
matcher: "/admin/stock-locations",
middlewares: [
transformQuery(
AdminGetStockLocationsParams,
QueryConfig.listTransformQueryConfig
),
applySalesChannelsFilter(),
],
},
{
method: ["POST"],
matcher: "/admin/stock-locations/:id",
Expand Down
22 changes: 22 additions & 0 deletions packages/medusa/src/api-v2/admin/stock-locations/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,25 @@ export const POST = async (

res.status(200).json({ stock_location })
}

export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)

const { rows: stock_locations } = await remoteQuery(
remoteQueryObjectFromString({
entryPoint: "stock_locations",
variables: {
filters: req.filterableFields,
order: req.listConfig.order,
skip: req.listConfig.skip,
take: req.listConfig.take,
},
fields: req.remoteQueryConfig.fields,
})
)

res.status(200).json({
stock_locations,
...req.remoteQueryConfig.pagination,
})
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import {
ContainerRegistrationKeys,
remoteQueryObjectFromString,
} from "@medusajs/utils"

import { AdminGetStockLocationsParams } from "../validators"
import { MedusaRequest } from "../../../../types/routing"
import { Modules } from "@medusajs/modules-sdk"
import { NextFunction } from "express"

export function applySalesChannelsFilter() {
return async (req: MedusaRequest, _, next: NextFunction) => {
const filterableFields: AdminGetStockLocationsParams = req.filterableFields

if (!filterableFields.sales_channel_id) {
return next()
}

const salesChannelIds = Array.isArray(filterableFields.sales_channel_id)
? filterableFields.sales_channel_id
: [filterableFields.sales_channel_id]

delete filterableFields.sales_channel_id

const remoteLinkService = req.scope.resolve(
ContainerRegistrationKeys.REMOTE_LINK
)

const stockLocationSalesChannelLinkModuleService =
await remoteLinkService.getLinkModule(
Modules.SALES_CHANNEL,
"sales_channel_id",
Modules.STOCK_LOCATION,
"stock_location_id"
)

const stockLocationSalesChannelLinks =
await stockLocationSalesChannelLinkModuleService.list(
{ sales_channel_id: salesChannelIds },
{}
)

filterableFields.id = stockLocationSalesChannelLinks.map(
(link) => link.stock_location_id
)

return next()
}
}
49 changes: 49 additions & 0 deletions packages/medusa/src/api-v2/admin/stock-locations/validators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,55 @@ export class AdminPostStockLocationsReq {

export class AdminPostStockLocationsParams extends FindParams {}

/**
* Parameters used to filter and configure the pagination of the retrieved stock locations.
*/
export class AdminGetStockLocationsParams extends extendedFindParamsMixin({
limit: 20,
offset: 0,
}) {
/**
* Search term to search stock location names.
*/
@IsString()
@IsOptional()
q?: string

/**
* IDs to filter stock locations by.
*/
@IsOptional()
@IsType([String, [String]])
id?: string | string[]

/**
* Names to filter stock locations by.
*/
@IsOptional()
@IsType([String, [String]])
name?: string | string[]

/**
* Filter stock locations by the ID of their associated addresses.
*/
@IsOptional()
@IsType([String, [String]])
address_id?: string | string[]

/**
* Filter stock locations by the ID of their associated sales channels.
*/
@IsOptional()
@IsType([String, [String]])
sales_channel_id?: string | string[]

/**
* The field to sort the data by. By default, the sort order is ascending. To change the order to descending, prefix the field name with `-`.
*/
@IsString()
@IsOptional()
order?: string
}
/**
* The attributes of a stock location address to create or update.
*/
Expand Down
1 change: 1 addition & 0 deletions packages/stock-location-next/src/repositories/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { MikroOrmBaseRepository as BaseRepository } from "@medusajs/utils"
export { StockLocationRepository } from "./stock-location"
52 changes: 52 additions & 0 deletions packages/stock-location-next/src/repositories/stock-location.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { Context, DAL } from "@medusajs/types"

import { StockLocation } from "@models"
import { mikroOrmBaseRepositoryFactory } from "@medusajs/utils"

export class StockLocationRepository extends mikroOrmBaseRepositoryFactory<StockLocation>(
StockLocation
) {
async find(
findOptions: DAL.FindOptions<StockLocation & { q?: string }> = {
where: {},
},
context: Context
): Promise<StockLocation[]> {
const findOptions_ = { ...findOptions }
findOptions_.options ??= {}

this.applyFreeTextSearchFilters<StockLocation>(
findOptions_,
this.getFreeTextSearchConstraints
)

return await super.find(findOptions_, context)
}

async findAndCount(
findOptions: DAL.FindOptions<StockLocation & { q?: string }> = {
where: {},
},
context: Context
): Promise<[StockLocation[], number]> {
const findOptions_ = { ...findOptions }
findOptions_.options ??= {}

this.applyFreeTextSearchFilters<StockLocation>(
findOptions_,
this.getFreeTextSearchConstraints
)

return await super.findAndCount(findOptions_, context)
}

protected getFreeTextSearchConstraints(q: string) {
return [
{
name: {
$ilike: `%${q}%`,
},
},
]
}
}
2 changes: 1 addition & 1 deletion packages/stock-location-next/src/services/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { default as StockLocationModuleService } from "./stock-location"
export { default as StockLocationModuleService } from "./stock-location-module"
Loading