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

* initial create

* add list for stock locations

* add changeset

* redo changes for stock locatino module'

* add changeset

* naming

* prep for pr

* move integration tests

* fix pr feedback

* add changeset

* update changeset

---------

Co-authored-by: Riqwan Thamir <rmthamir@gmail.com>
This commit is contained in:
Philip Korsholm
2024-03-28 17:33:26 +01:00
committed by GitHub
co-authored by Riqwan Thamir
parent a6562d2a41
commit 18438a695a
10 changed files with 288 additions and 1 deletions
+6
View File
@@ -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
@@ -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
@@ -2,6 +2,7 @@ import * as QueryConfig from "./query-config"
import {
AdminGetStockLocationsLocationParams,
AdminGetStockLocationsParams,
AdminPostStockLocationsLocationParams,
AdminPostStockLocationsLocationReq,
AdminPostStockLocationsParams,
@@ -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[] = [
@@ -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",
@@ -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,
})
}
@@ -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()
}
}
@@ -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.
*/
@@ -1 +1,2 @@
export { MikroOrmBaseRepository as BaseRepository } from "@medusajs/utils"
export { StockLocationRepository } from "./stock-location"
@@ -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}%`,
},
},
]
}
}
@@ -1 +1 @@
export { default as StockLocationModuleService } from "./stock-location"
export { default as StockLocationModuleService } from "./stock-location-module"