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:
Philip Korsholm
2024-03-27 10:31:17 +01:00
committed by GitHub
parent 0b23e7efb8
commit 4cf71af07d
13 changed files with 378 additions and 27 deletions

View 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

View File

@@ -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 = {

View File

@@ -1,2 +1,3 @@
export * from "./create-stock-locations"
export * from "./update-stock-locations"
export * from "./delete-stock-locations"

View File

@@ -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 } : {}),
}))
)
}
)

View File

@@ -1,2 +1,3 @@
export * from "./create-stock-locations"
export * from "./update-stock-locations"
export * from "./delete-stock-locations"

View File

@@ -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)
}
)

View File

@@ -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

View File

@@ -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",

View File

@@ -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 {}

View File

@@ -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({

View File

@@ -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)
}

View File

@@ -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>

View File

@@ -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.