feat(core-flows, medusa): add create stock location endpoint for api-v2 (#6787)

This commit is contained in:
Philip Korsholm
2024-03-25 12:53:09 +01:00
committed by GitHub
parent 247ca3c3fa
commit 68b9812aa1
14 changed files with 355 additions and 2 deletions

View File

@@ -0,0 +1,6 @@
---
"@medusajs/core-flows": patch
"@medusajs/medusa": patch
---
feat(core-flows, medusa): add create stock location endpoint for api-v2

View File

@@ -0,0 +1,58 @@
import {
adminHeaders,
createAdminUser,
} from "../../../../helpers/create-admin-user"
import { IStockLocationServiceNext } from "@medusajs/types"
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
const { medusaIntegrationTestRunner } = require("medusa-test-utils")
jest.setTimeout(30000)
medusaIntegrationTestRunner({
env: {
MEDUSA_FF_MEDUSA_V2: true,
},
testSuite: ({ dbConnection, getContainer, api }) => {
let appContainer
let service: IStockLocationServiceNext
beforeEach(async () => {
appContainer = getContainer()
await createAdminUser(dbConnection, adminHeaders, appContainer)
service = appContainer.resolve(ModuleRegistrationName.STOCK_LOCATION)
})
describe("create stock location", () => {
it("should create a stock location with a name and address", async () => {
const address = {
address_1: "Test Address",
country_code: "US",
}
const location = {
name: "Test Location",
}
const response = await api.post(
"/admin/stock-locations",
{
...location,
address,
},
adminHeaders
)
expect(response.status).toEqual(200)
expect(response.data.stock_location).toEqual(
expect.objectContaining({
...location,
address: expect.objectContaining(address),
})
)
})
})
},
})

View File

@@ -67,8 +67,10 @@ module.exports = {
resolve: "@medusajs/cache-inmemory",
options: { ttl: 0 }, // Cache disabled
},
[Modules.STOCK_LOCATION]: true,
[Modules.INVENTORY]: true,
[Modules.STOCK_LOCATION]: {
resolve: "@medusajs/stock-location-next",
options: {},
},
[Modules.PRODUCT]: true,
[Modules.PRICING]: true,
[Modules.PROMOTION]: true,

View File

@@ -17,6 +17,7 @@ export * from "./promotion"
export * from "./region"
export * from "./sales-channel"
export * from "./shipping-options"
export * from "./stock-location"
export * from "./store"
export * from "./tax"
export * from "./user"

View File

@@ -0,0 +1,2 @@
export * from "./steps"
export * from "./workflows"

View File

@@ -0,0 +1,35 @@
import {
CreateStockLocationInput,
IStockLocationServiceNext,
} from "@medusajs/types"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
export const createStockLocationsStepId = "create-stock-locations"
export const createStockLocations = createStep(
createStockLocationsStepId,
async (data: CreateStockLocationInput[], { container }) => {
const stockLocationService = container.resolve<IStockLocationServiceNext>(
ModuleRegistrationName.STOCK_LOCATION
)
const created = await stockLocationService.create(data)
return new StepResponse(
created,
created.map((i) => i.id)
)
},
async (createdStockLocationIds, { container }) => {
if (!createdStockLocationIds?.length) {
return
}
const stockLocationService = container.resolve(
ModuleRegistrationName.STOCK_LOCATION
)
await stockLocationService.delete(createdStockLocationIds)
}
)

View File

@@ -0,0 +1 @@
export * from "./create-stock-locations"

View File

@@ -0,0 +1,18 @@
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
import { CreateStockLocationInput } from "@medusajs/types"
import { createStockLocations } from "../steps"
interface WorkflowInput {
locations: CreateStockLocationInput[]
}
export const createStockLocationsWorkflowId = "create-stock-locations-workflow"
export const createStockLocationsWorkflow = createWorkflow(
createStockLocationsWorkflowId,
(input: WorkflowData<WorkflowInput>) => {
const locations = createStockLocations(input.locations)
return locations
}
)

View File

@@ -0,0 +1 @@
export * from "./create-stock-locations"

View File

@@ -0,0 +1,29 @@
import * as QueryConfig from "./query-config"
import {
AdminPostStockLocationsParams,
AdminPostStockLocationsReq,
} from "./validators"
import { transformBody, transformQuery } from "../../../api/middlewares"
import { MiddlewareRoute } from "../../../types/middlewares"
import { authenticate } from "../../../utils/authenticate-middleware"
export const adminStockLocationRoutesMiddlewares: MiddlewareRoute[] = [
{
method: "ALL",
matcher: "/admin/stock-locations*",
middlewares: [authenticate("admin", ["session", "bearer", "api-key"])],
},
{
method: ["POST"],
matcher: "/admin/stock-locations",
middlewares: [
transformBody(AdminPostStockLocationsReq),
transformQuery(
AdminPostStockLocationsParams,
QueryConfig.retrieveTransformQueryConfig
),
],
},
]

View File

@@ -0,0 +1,27 @@
export const defaultAdminStockLocationFields = [
"id",
"name",
"metadata",
"created_at",
"updated_at",
"address.id",
"address.address_1",
"address.address_2",
"address.city",
"address.country_code",
"address.phone",
"address.province",
"address.postal_code",
"address.metadata",
]
export const retrieveTransformQueryConfig = {
defaults: defaultAdminStockLocationFields,
allowed: defaultAdminStockLocationFields,
isList: false,
}
export const listTransformQueryConfig = {
...retrieveTransformQueryConfig,
isList: true,
}

View File

@@ -0,0 +1,32 @@
import {
ContainerRegistrationKeys,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import { MedusaRequest, MedusaResponse } from "../../../types/routing"
import { AdminPostStockLocationsReq } from "./validators"
import { createStockLocationsWorkflow } from "@medusajs/core-flows"
// Create stock location
export const POST = async (
req: MedusaRequest<AdminPostStockLocationsReq>,
res: MedusaResponse
) => {
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const { result } = await createStockLocationsWorkflow(req.scope).run({
input: { locations: [req.validatedBody] },
})
const [stock_location] = await remoteQuery(
remoteQueryObjectFromString({
entryPoint: "stock_locations",
variables: {
id: result[0].id,
},
fields: req.remoteQueryConfig.fields,
})
)
res.status(200).json({ stock_location })
}

View File

@@ -0,0 +1,139 @@
import {
DateComparisonOperator,
FindParams,
NumericalComparisonOperator,
StringComparisonOperator,
extendedFindParamsMixin,
} from "../../../types/common"
import {
IsBoolean,
IsEmail,
IsNotEmpty,
IsNumber,
IsObject,
IsOptional,
IsString,
ValidateNested,
} from "class-validator"
import { Transform, Type } from "class-transformer"
import { IsType } from "../../../utils"
/**
* @schema AdminPostStockLocationsReqAddress
* type: object
* required:
* - address_1
* - country_code
* properties:
* address_1:
* type: string
* description: Stock location address
* example: 35, Jhon Doe Ave
* address_2:
* type: string
* description: Stock location address' complement
* example: apartment 4432
* company:
* type: string
* description: Stock location address' company
* city:
* type: string
* description: Stock location address' city
* example: Mexico city
* country_code:
* description: "The two character ISO code for the country."
* type: string
* externalDocs:
* url: https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2#Officially_assigned_code_elements
* description: See a list of codes.
* phone:
* type: string
* description: Stock location address' phone number
* example: +1 555 61646
* postal_code:
* type: string
* description: Stock location address' postal code
* example: HD3-1G8
* province:
* type: string
* description: Stock location address' province
* example: Sinaloa
*/
class StockLocationAddress {
@IsString()
address_1: string
@IsOptional()
@IsString()
address_2?: string
@IsOptional()
@IsString()
company?: string
@IsOptional()
@IsString()
city?: string
@IsString()
country_code: string
@IsOptional()
@IsString()
phone?: string
@IsOptional()
@IsString()
postal_code?: string
@IsOptional()
@IsString()
province?: string
}
/**
* @schema AdminPostStockLocationsReq
* type: object
* description: "The details of the stock location to create."
* required:
* - name
* properties:
* name:
* description: the name of the stock location
* type: string
* address_id:
* description: the ID of an existing stock location address to associate with the stock location. Only required if `address` is not provided.
* type: string
* metadata:
* type: object
* description: An optional key-value map with additional details
* example: {car: "white"}
* externalDocs:
* description: "Learn about the metadata attribute, and how to delete and update it."
* url: "https://docs.medusajs.com/development/entities/overview#metadata-attribute"
* address:
* description: A new stock location address to create and associate with the stock location. Only required if `address_id` is not provided.
* $ref: "#/components/schemas/StockLocationAddressInput"
*/
export class AdminPostStockLocationsReq {
@IsString()
@IsNotEmpty()
@Transform(({ value }: { value: string }) => value?.trim())
name: string
@IsOptional()
@ValidateNested()
@Type(() => StockLocationAddress)
address?: StockLocationAddress
@IsOptional()
@IsString()
address_id?: string
@IsObject()
@IsOptional()
metadata?: Record<string, unknown>
}
export class AdminPostStockLocationsParams extends FindParams {}

View File

@@ -15,6 +15,7 @@ import { adminProductRoutesMiddlewares } from "./admin/products/middlewares"
import { adminPromotionRoutesMiddlewares } from "./admin/promotions/middlewares"
import { adminRegionRoutesMiddlewares } from "./admin/regions/middlewares"
import { adminSalesChannelRoutesMiddlewares } from "./admin/sales-channels/middlewares"
import { adminStockLocationRoutesMiddlewares } from "./admin/stock-locations/middlewares"
import { adminStoreRoutesMiddlewares } from "./admin/stores/middlewares"
import { adminTaxRateRoutesMiddlewares } from "./admin/tax-rates/middlewares"
import { adminTaxRegionRoutesMiddlewares } from "./admin/tax-regions/middlewares"
@@ -57,5 +58,6 @@ export const config: MiddlewaresConfig = {
...adminPricingRoutesMiddlewares,
...adminFulfillmentRoutesMiddlewares,
...adminSalesChannelRoutesMiddlewares,
...adminStockLocationRoutesMiddlewares,
],
}