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:
co-authored by
Riqwan Thamir
parent
a6562d2a41
commit
18438a695a
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user