feat(core-flows, medusa): add update stock location endpoint to api-v2 (#6800)
* initial create * add changeset * redo changes for stock locatino module' * initial update * add changeset * move integration tests * update update method * fix integration tests * Update packages/stock-location-next/src/services/stock-location.ts Co-authored-by: Oli Juhl <59018053+olivermrbl@users.noreply.github.com> * update service * initial fixes * pr feedback * update types * expand revert clause for update * update versioning --------- Co-authored-by: Riqwan Thamir <rmthamir@gmail.com> Co-authored-by: Oli Juhl <59018053+olivermrbl@users.noreply.github.com>
This commit is contained in:
7
.changeset/silent-files-kiss.md
Normal file
7
.changeset/silent-files-kiss.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@medusajs/core-flows": patch
|
||||
"@medusajs/medusa": patch
|
||||
"@medusajs/types": patch
|
||||
---
|
||||
|
||||
feat(core-flows, medusa): add update stock location endpoint to api-v2
|
||||
@@ -56,6 +56,35 @@ medusaIntegrationTestRunner({
|
||||
})
|
||||
})
|
||||
|
||||
describe("Update stock locations", () => {
|
||||
let stockLocationId
|
||||
|
||||
beforeEach(async () => {
|
||||
const createResponse = await api.post(
|
||||
`/admin/stock-locations`,
|
||||
{
|
||||
name: "test location",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
stockLocationId = createResponse.data.stock_location.id
|
||||
})
|
||||
|
||||
it("should update stock location name", async () => {
|
||||
const response = await api.post(
|
||||
`/admin/stock-locations/${stockLocationId}`,
|
||||
{
|
||||
name: "new name",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
expect(response.status).toEqual(200)
|
||||
|
||||
expect(response.data.stock_location.name).toEqual("new name")
|
||||
})
|
||||
})
|
||||
|
||||
describe("Get stock location", () => {
|
||||
let locationId
|
||||
const location = {
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from "./create-stock-locations"
|
||||
export * from "./update-stock-locations"
|
||||
export * from "./delete-stock-locations"
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import {
|
||||
FilterableStockLocationProps,
|
||||
IStockLocationServiceNext,
|
||||
UpdateStockLocationNextInput,
|
||||
} from "@medusajs/types"
|
||||
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
|
||||
import {
|
||||
convertItemResponseToUpdateRequest,
|
||||
getSelectsAndRelationsFromObjectArray,
|
||||
} from "@medusajs/utils"
|
||||
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
import { UpdateStockLocationInput } from "@medusajs/types"
|
||||
|
||||
interface StepInput {
|
||||
selector: FilterableStockLocationProps
|
||||
update: UpdateStockLocationInput
|
||||
}
|
||||
|
||||
export const updateStockLocationsStepId = "update-stock-locations-step"
|
||||
export const updateStockLocationsStep = createStep(
|
||||
updateStockLocationsStepId,
|
||||
async (input: StepInput, { container }) => {
|
||||
const stockLocationService = container.resolve<IStockLocationServiceNext>(
|
||||
ModuleRegistrationName.STOCK_LOCATION
|
||||
)
|
||||
const { selects, relations } = getSelectsAndRelationsFromObjectArray([
|
||||
input.update,
|
||||
])
|
||||
|
||||
const dataBeforeUpdate = await stockLocationService.list(input.selector, {
|
||||
select: selects,
|
||||
relations,
|
||||
})
|
||||
|
||||
const updatedStockLocations = await stockLocationService.update(
|
||||
input.selector,
|
||||
input.update
|
||||
)
|
||||
|
||||
return new StepResponse(updatedStockLocations, dataBeforeUpdate)
|
||||
},
|
||||
async (revertInput, { container }) => {
|
||||
if (!revertInput?.length) {
|
||||
return
|
||||
}
|
||||
|
||||
const stockLocationService = container.resolve<IStockLocationServiceNext>(
|
||||
ModuleRegistrationName.STOCK_LOCATION
|
||||
)
|
||||
|
||||
await stockLocationService.upsert(
|
||||
revertInput.map((item) => ({
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
...(item.metadata ? { metadata: item.metadata } : {}),
|
||||
...(item.address ? { address: item.address } : {}),
|
||||
}))
|
||||
)
|
||||
}
|
||||
)
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from "./create-stock-locations"
|
||||
export * from "./update-stock-locations"
|
||||
export * from "./delete-stock-locations"
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import {
|
||||
InventoryNext,
|
||||
StockLocationDTO,
|
||||
UpdateStockLocationNextInput,
|
||||
} from "@medusajs/types"
|
||||
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
|
||||
|
||||
import { FilterableStockLocationProps } from "@medusajs/types"
|
||||
import { UpdateStockLocationInput } from "@medusajs/types"
|
||||
import { updateStockLocationsStep } from "../steps"
|
||||
|
||||
interface WorkflowInput {
|
||||
selector: FilterableStockLocationProps
|
||||
update: UpdateStockLocationInput
|
||||
}
|
||||
export const updateStockLocationsWorkflowId = "update-stock-locations-workflow"
|
||||
export const updateStockLocationsWorkflow = createWorkflow(
|
||||
updateStockLocationsWorkflowId,
|
||||
(input: WorkflowData<WorkflowInput>): WorkflowData<StockLocationDTO[]> => {
|
||||
return updateStockLocationsStep(input)
|
||||
}
|
||||
)
|
||||
@@ -4,8 +4,40 @@ import {
|
||||
} from "@medusajs/utils"
|
||||
import { MedusaRequest, MedusaResponse } from "../../../../types/routing"
|
||||
|
||||
import { AdminPostStockLocationsLocationReq } from "../validators"
|
||||
import { MedusaError } from "@medusajs/utils"
|
||||
import { deleteStockLocationsWorkflow } from "@medusajs/core-flows"
|
||||
import { updateStockLocationsWorkflow } from "@medusajs/core-flows"
|
||||
|
||||
export const POST = async (
|
||||
req: MedusaRequest<AdminPostStockLocationsLocationReq>,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const { id } = req.params
|
||||
|
||||
await updateStockLocationsWorkflow(req.scope).run({
|
||||
input: {
|
||||
selector: { id: req.params.id },
|
||||
update: req.validatedBody,
|
||||
},
|
||||
})
|
||||
|
||||
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
|
||||
|
||||
const [stock_location] = await remoteQuery(
|
||||
remoteQueryObjectFromString({
|
||||
entryPoint: "stock_locations",
|
||||
variables: {
|
||||
id,
|
||||
},
|
||||
fields: req.remoteQueryConfig.fields,
|
||||
})
|
||||
)
|
||||
|
||||
res.status(200).json({
|
||||
stock_location,
|
||||
})
|
||||
}
|
||||
|
||||
export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
|
||||
const { id } = req.params
|
||||
|
||||
@@ -2,6 +2,8 @@ import * as QueryConfig from "./query-config"
|
||||
|
||||
import {
|
||||
AdminGetStockLocationsLocationParams,
|
||||
AdminPostStockLocationsLocationParams,
|
||||
AdminPostStockLocationsLocationReq,
|
||||
AdminPostStockLocationsParams,
|
||||
AdminPostStockLocationsReq,
|
||||
} from "./validators"
|
||||
@@ -27,6 +29,17 @@ export const adminStockLocationRoutesMiddlewares: MiddlewareRoute[] = [
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
method: ["POST"],
|
||||
matcher: "/admin/stock-locations/:id",
|
||||
middlewares: [
|
||||
transformBody(AdminPostStockLocationsLocationReq),
|
||||
transformQuery(
|
||||
AdminPostStockLocationsLocationParams,
|
||||
QueryConfig.retrieveTransformQueryConfig
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
method: ["GET"],
|
||||
matcher: "/admin/stock-locations/:id",
|
||||
|
||||
@@ -60,7 +60,7 @@ import { IsType } from "../../../utils"
|
||||
* description: Stock location address' province
|
||||
* example: Sinaloa
|
||||
*/
|
||||
class StockLocationAddress {
|
||||
class StockLocationCreateAddress {
|
||||
@IsString()
|
||||
address_1: string
|
||||
|
||||
@@ -124,8 +124,8 @@ export class AdminPostStockLocationsReq {
|
||||
|
||||
@IsOptional()
|
||||
@ValidateNested()
|
||||
@Type(() => StockLocationAddress)
|
||||
address?: StockLocationAddress
|
||||
@Type(() => StockLocationCreateAddress)
|
||||
address?: StockLocationCreateAddress
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@@ -138,4 +138,106 @@ export class AdminPostStockLocationsReq {
|
||||
|
||||
export class AdminPostStockLocationsParams extends FindParams {}
|
||||
|
||||
/**
|
||||
* The attributes of a stock location address to create or update.
|
||||
*/
|
||||
class StockLocationUpdateAddress {
|
||||
/**
|
||||
* First line address.
|
||||
*/
|
||||
@IsString()
|
||||
address_1: string
|
||||
|
||||
/**
|
||||
* Second line address.
|
||||
*/
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
address_2?: string
|
||||
|
||||
/**
|
||||
* Company.
|
||||
*/
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
company?: string
|
||||
|
||||
/**
|
||||
* City.
|
||||
*/
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
city?: string
|
||||
|
||||
/**
|
||||
* Country code.
|
||||
*/
|
||||
@IsString()
|
||||
country_code: string
|
||||
|
||||
/**
|
||||
* Phone.
|
||||
*/
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
phone?: string
|
||||
|
||||
/**
|
||||
* Postal code.
|
||||
*/
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
postal_code?: string
|
||||
|
||||
/**
|
||||
* Province.
|
||||
*/
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
province?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* @schema AdminPostStockLocationsLocationReq
|
||||
* type: object
|
||||
* description: "The details to update of the stock location."
|
||||
* properties:
|
||||
* name:
|
||||
* description: the name of the stock location
|
||||
* type: string
|
||||
* address_id:
|
||||
* description: the stock location address ID
|
||||
* 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: The data of an associated address to create or update.
|
||||
* $ref: "#/components/schemas/StockLocationAddressInput"
|
||||
*/
|
||||
export class AdminPostStockLocationsLocationReq {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
name?: string
|
||||
|
||||
@IsOptional()
|
||||
@ValidateNested()
|
||||
@Type(() => StockLocationUpdateAddress)
|
||||
address?: StockLocationUpdateAddress
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
address_id?: string
|
||||
|
||||
@IsObject()
|
||||
@IsOptional()
|
||||
metadata?: Record<string, unknown>
|
||||
}
|
||||
|
||||
export class AdminPostStockLocationsLocationParams extends FindParams {}
|
||||
|
||||
export class AdminGetStockLocationsLocationParams extends FindParams {}
|
||||
|
||||
@@ -107,7 +107,7 @@ moduleIntegrationTestRunner({
|
||||
id: stockLocation.id,
|
||||
name: "updated location",
|
||||
}
|
||||
const location = await service.update(data)
|
||||
const location = await service.upsert(data)
|
||||
|
||||
expect(location).toEqual(expect.objectContaining(data))
|
||||
})
|
||||
@@ -122,7 +122,7 @@ moduleIntegrationTestRunner({
|
||||
},
|
||||
}
|
||||
|
||||
const location = await service.update(data)
|
||||
const location = await service.upsert(data)
|
||||
|
||||
expect(location).toEqual(
|
||||
expect.objectContaining({
|
||||
|
||||
@@ -10,16 +10,20 @@ import {
|
||||
ModulesSdkTypes,
|
||||
DAL,
|
||||
IStockLocationServiceNext,
|
||||
FilterableStockLocationProps,
|
||||
} from "@medusajs/types"
|
||||
import {
|
||||
InjectManager,
|
||||
InjectTransactionManager,
|
||||
MedusaContext,
|
||||
ModulesSdkUtils,
|
||||
isString,
|
||||
} from "@medusajs/utils"
|
||||
import { entityNameToLinkableKeysMap, joinerConfig } from "../joiner-config"
|
||||
import { StockLocation, StockLocationAddress } from "../models"
|
||||
import { UpdateStockLocationNextInput } from "@medusajs/types"
|
||||
import { UpsertStockLocationInput } from "@medusajs/types"
|
||||
import { promiseAll } from "@medusajs/utils"
|
||||
|
||||
type InjectedDependencies = {
|
||||
eventBusService: IEventBusService
|
||||
@@ -117,13 +121,65 @@ export default class StockLocationModuleService<
|
||||
return await this.stockLocationService_.create(data, context)
|
||||
}
|
||||
|
||||
async upsert(
|
||||
data: UpsertStockLocationInput,
|
||||
context?: Context
|
||||
): Promise<StockLocationTypes.StockLocationDTO>
|
||||
async upsert(
|
||||
data: UpsertStockLocationInput[],
|
||||
context?: Context
|
||||
): Promise<StockLocationTypes.StockLocationDTO[]>
|
||||
|
||||
@InjectManager("baseRepository_")
|
||||
async upsert(
|
||||
data: UpsertStockLocationInput | UpsertStockLocationInput[],
|
||||
@MedusaContext() context: Context = {}
|
||||
): Promise<
|
||||
StockLocationTypes.StockLocationDTO | StockLocationTypes.StockLocationDTO[]
|
||||
> {
|
||||
const input = Array.isArray(data) ? data : [data]
|
||||
|
||||
const result = await this.upsert_(input, context)
|
||||
|
||||
return await this.baseRepository_.serialize<
|
||||
| StockLocationTypes.StockLocationDTO[]
|
||||
| StockLocationTypes.StockLocationDTO
|
||||
>(Array.isArray(data) ? result : result[0])
|
||||
}
|
||||
|
||||
@InjectTransactionManager("baseRepository_")
|
||||
async upsert_(
|
||||
input: UpsertStockLocationInput[],
|
||||
@MedusaContext() context: Context = {}
|
||||
) {
|
||||
const toUpdate = input.filter(
|
||||
(location): location is UpdateStockLocationNextInput => !!location.id
|
||||
) as UpdateStockLocationNextInput[]
|
||||
const toCreate = input.filter(
|
||||
(location) => !location.id
|
||||
) as CreateStockLocationInput[]
|
||||
|
||||
const operations: Promise<StockLocation[] | StockLocation>[] = []
|
||||
|
||||
if (toCreate.length) {
|
||||
operations.push(this.create_(toCreate, context))
|
||||
}
|
||||
if (toUpdate.length) {
|
||||
operations.push(this.update_(toUpdate, context))
|
||||
}
|
||||
|
||||
return (await promiseAll(operations)).flat()
|
||||
}
|
||||
|
||||
update(
|
||||
data: UpdateStockLocationNextInput,
|
||||
context: Context
|
||||
id: string,
|
||||
input: UpdateStockLocationNextInput,
|
||||
context?: Context
|
||||
): Promise<StockLocationTypes.StockLocationDTO>
|
||||
update(
|
||||
data: UpdateStockLocationNextInput[],
|
||||
context: Context
|
||||
selector: FilterableStockLocationProps,
|
||||
input: UpdateStockLocationNextInput,
|
||||
context?: Context
|
||||
): Promise<StockLocationTypes.StockLocationDTO[]>
|
||||
/**
|
||||
* Updates an existing stock location.
|
||||
@@ -134,14 +190,21 @@ export default class StockLocationModuleService<
|
||||
*/
|
||||
@InjectManager("baseRepository_")
|
||||
async update(
|
||||
idOrSelector: string | FilterableStockLocationProps,
|
||||
data: UpdateStockLocationNextInput | UpdateStockLocationNextInput[],
|
||||
@MedusaContext() context: Context = {}
|
||||
): Promise<
|
||||
StockLocationTypes.StockLocationDTO | StockLocationTypes.StockLocationDTO[]
|
||||
> {
|
||||
const input = Array.isArray(data) ? data : [data]
|
||||
|
||||
const updated = await this.update_(input, context)
|
||||
let normalizedInput:
|
||||
| (UpdateStockLocationNextInput & { id: string })[]
|
||||
| { data: any; selector: FilterableStockLocationProps } = []
|
||||
if (isString(idOrSelector)) {
|
||||
normalizedInput = [{ id: idOrSelector, ...data }]
|
||||
} else {
|
||||
normalizedInput = { data, selector: idOrSelector }
|
||||
}
|
||||
const updated = await this.update_(normalizedInput, context)
|
||||
|
||||
const serialized = await this.baseRepository_.serialize<
|
||||
| StockLocationTypes.StockLocationDTO
|
||||
@@ -153,9 +216,12 @@ export default class StockLocationModuleService<
|
||||
|
||||
@InjectTransactionManager("baseRepository_")
|
||||
async update_(
|
||||
data: (UpdateStockLocationInput & { id: string })[],
|
||||
data:
|
||||
| UpdateStockLocationNextInput[]
|
||||
| UpdateStockLocationNextInput
|
||||
| { data: any; selector: FilterableStockLocationProps },
|
||||
@MedusaContext() context: Context = {}
|
||||
): Promise<TEntity[]> {
|
||||
): Promise<TEntity[] | TEntity> {
|
||||
return await this.stockLocationService_.update(data, context)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { BaseFilterable, OperatorMap } from "../dal"
|
||||
|
||||
import { StringComparisonOperator } from "../common/common"
|
||||
|
||||
/**
|
||||
@@ -241,7 +243,8 @@ export type StockLocationExpandedDTO = StockLocationDTO & {
|
||||
*
|
||||
* The filters to apply on the retrieved stock locations.
|
||||
*/
|
||||
export type FilterableStockLocationProps = {
|
||||
export interface FilterableStockLocationProps
|
||||
extends BaseFilterable<FilterableStockLocationProps> {
|
||||
/**
|
||||
* Search parameter for stock location names
|
||||
*/
|
||||
@@ -255,7 +258,7 @@ export type FilterableStockLocationProps = {
|
||||
/**
|
||||
* The names to filter stock locations by.
|
||||
*/
|
||||
name?: string | string[] | StringComparisonOperator
|
||||
name?: string | string[] | OperatorMap<string>
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -309,7 +312,7 @@ export type StockLocationAddressInput = {
|
||||
/**
|
||||
* The second line of the stock location address.
|
||||
*/
|
||||
address_2?: string
|
||||
address_2?: string | null
|
||||
|
||||
/**
|
||||
* The country code of the stock location address.
|
||||
@@ -319,27 +322,27 @@ export type StockLocationAddressInput = {
|
||||
/**
|
||||
* The city of the stock location address.
|
||||
*/
|
||||
city?: string
|
||||
city?: string | null
|
||||
|
||||
/**
|
||||
* The phone of the stock location address.
|
||||
*/
|
||||
phone?: string
|
||||
phone?: string | null
|
||||
|
||||
/**
|
||||
* The province of the stock location address.
|
||||
*/
|
||||
province?: string
|
||||
province?: string | null
|
||||
|
||||
/**
|
||||
* The postal code of the stock location address.
|
||||
*/
|
||||
postal_code?: string
|
||||
postal_code?: string | null
|
||||
|
||||
/**
|
||||
* Holds custom data in key-value pairs.
|
||||
*/
|
||||
metadata?: Record<string, unknown>
|
||||
metadata?: Record<string, unknown> | null
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -435,3 +438,5 @@ export type UpdateStockLocationInput = {
|
||||
export type UpdateStockLocationNextInput = UpdateStockLocationInput & {
|
||||
id: string
|
||||
}
|
||||
|
||||
export type UpsertStockLocationInput = Partial<UpdateStockLocationNextInput>
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
StockLocationDTO,
|
||||
UpdateStockLocationInput,
|
||||
UpdateStockLocationNextInput,
|
||||
UpsertStockLocationInput,
|
||||
} from "./common"
|
||||
import { RestoreReturn, SoftDeleteReturn } from "../dal"
|
||||
|
||||
@@ -251,6 +252,15 @@ export interface IStockLocationServiceNext extends IModuleService {
|
||||
context?: Context
|
||||
): Promise<StockLocationDTO[]>
|
||||
|
||||
upsert(
|
||||
data: UpsertStockLocationInput[],
|
||||
sharedContext?: Context
|
||||
): Promise<StockLocationDTO[]>
|
||||
upsert(
|
||||
data: UpsertStockLocationInput,
|
||||
sharedContext?: Context
|
||||
): Promise<StockLocationDTO>
|
||||
|
||||
/**
|
||||
* This method is used to update a stock location.
|
||||
*
|
||||
@@ -275,13 +285,15 @@ export interface IStockLocationServiceNext extends IModuleService {
|
||||
* }
|
||||
*/
|
||||
update(
|
||||
input: UpdateStockLocationNextInput[],
|
||||
context?: Context
|
||||
): Promise<StockLocationDTO[]>
|
||||
update(
|
||||
input: UpdateStockLocationNextInput,
|
||||
id: string,
|
||||
input: UpdateStockLocationInput,
|
||||
context?: Context
|
||||
): Promise<StockLocationDTO>
|
||||
update(
|
||||
selector: FilterableStockLocationProps,
|
||||
input: UpdateStockLocationInput,
|
||||
context?: Context
|
||||
): Promise<StockLocationDTO[]>
|
||||
|
||||
/**
|
||||
* This method is used to delete a stock location.
|
||||
|
||||
Reference in New Issue
Block a user