Skip to content

Commit

Permalink
fix(stock-location,core-flows,types): updates existing address when u…
Browse files Browse the repository at this point in the history
…pdating stock location address
  • Loading branch information
riqwan committed Jan 6, 2025
1 parent 79c87c0 commit 4b6e6d0
Show file tree
Hide file tree
Showing 7 changed files with 311 additions and 6 deletions.
7 changes: 7 additions & 0 deletions .changeset/five-roses-swim.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@medusajs/stock-location": patch
"@medusajs/core-flows": patch
"@medusajs/types": patch
---

fix(stock-location,core-flows,types): update existing address when updating stock location address
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,58 @@ medusaIntegrationTestRunner({
expect(response.status).toEqual(200)
expect(response.data.stock_location.name).toEqual("new name")
})

it("should update stock location address without creating new addresses", async () => {
const response = await api.post(
`/admin/stock-locations/${location1.id}`,
{
name: "new name",
address: {
address_1: "test",
country_code: "dk",
},
},
adminHeaders
)

const firstAddressId = response.data.stock_location.address.id

expect(response.status).toEqual(200)
expect(response.data.stock_location).toEqual(
expect.objectContaining({
name: "new name",
address: expect.objectContaining({
id: firstAddressId,
address_1: "test",
country_code: "dk",
}),
})
)

const response2 = await api.post(
`/admin/stock-locations/${location1.id}`,
{
name: "new name 2",
address: {
address_1: "test 2",
country_code: "dk",
},
},
adminHeaders
)

expect(response2.status).toEqual(200)
expect(response2.data.stock_location).toEqual(
expect.objectContaining({
name: "new name 2",
address: expect.objectContaining({
id: firstAddressId,
address_1: "test 2",
country_code: "dk",
}),
})
)
})
})

describe("Get stock location", () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import {
IStockLocationService,
UpsertStockLocationAddressInput,
} from "@medusajs/framework/types"
import {
getSelectsAndRelationsFromObjectArray,
promiseAll,
} from "@medusajs/framework/utils"
import { StepResponse, createStep } from "@medusajs/framework/workflows-sdk"

import { Modules } from "@medusajs/framework/utils"

export const upsertStockLocationAddressesStepId =
"upsert-stock-location-addresses-step"
/**
* This step upserts stock location addresses matching the specified filters.
*/
export const upsertStockLocationAddressesStep = createStep(
upsertStockLocationAddressesStepId,
async (input: UpsertStockLocationAddressInput[], { container }) => {
const stockLocationService = container.resolve<IStockLocationService>(
Modules.STOCK_LOCATION
)

const stockLocationAddressIds = input.map((i) => i.id!).filter(Boolean)
const { selects, relations } = getSelectsAndRelationsFromObjectArray(input)

const dataToUpdate = await stockLocationService.listStockLocationAddresses(
{ id: stockLocationAddressIds },
{ select: selects, relations }
)

const updateIds = dataToUpdate.map((du) => du.id)

const updatedAddresses =
await stockLocationService.upsertStockLocationAddresses(input)

const dataToDelete = updatedAddresses.filter(
(address) => !updateIds.includes(address.id)
)

return new StepResponse(updatedAddresses, { dataToUpdate, dataToDelete })
},
async (revertData, { container }) => {
if (!revertData) {
return
}

const stockLocationService = container.resolve<IStockLocationService>(
Modules.STOCK_LOCATION
)

const promises: any[] = []

if (revertData.dataToDelete) {
promises.push(
stockLocationService.deleteStockLocationAddresses(
revertData.dataToDelete.map((d) => d.id!)
)
)
}

if (revertData.dataToUpdate) {
promises.push(
stockLocationService.upsertStockLocationAddresses(
revertData.dataToUpdate
)
)
}

await promiseAll(promises)
}
)
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import {
FilterableStockLocationProps,
StockLocationDTO,
UpdateStockLocationInput,
FilterableStockLocationProps,
UpsertStockLocationAddressInput,
} from "@medusajs/framework/types"
import {
WorkflowData,
WorkflowResponse,
createWorkflow,
transform,
} from "@medusajs/framework/workflows-sdk"

import { useQueryGraphStep } from "../../common"
import { updateStockLocationsStep } from "../steps"
import { upsertStockLocationAddressesStep } from "../steps/upsert-stock-location-addresses"

export interface UpdateStockLocationsWorkflowInput {
selector: FilterableStockLocationProps
Expand All @@ -24,6 +28,50 @@ export const updateStockLocationsWorkflow = createWorkflow(
(
input: WorkflowData<UpdateStockLocationsWorkflowInput>
): WorkflowResponse<StockLocationDTO[]> => {
return new WorkflowResponse(updateStockLocationsStep(input))
const stockLocationsQuery = useQueryGraphStep({
entity: "stock_location",
filters: input.selector,
fields: ["id", "address.id"],
}).config({ name: "get-stock-location" })

const stockLocations = transform(
{ stockLocationsQuery },
({ stockLocationsQuery }) => stockLocationsQuery.data
)

const normalizedData = transform(
{ input, stockLocations },
({ input, stockLocations }) => {
const { address, address_id, ...stockLocationInput } = input.update
const addressesInput: UpsertStockLocationAddressInput[] = []

if (address) {
for (const stockLocation of stockLocations) {
if (stockLocation.address?.id) {
addressesInput.push({
id: stockLocation.address?.id!,
...address,
})
} else {
addressesInput.push(address)
}
}
}

return {
stockLocationInput: {
selector: input.selector,
update: stockLocationInput,
},
addressesInput,
}
}
)

upsertStockLocationAddressesStep(normalizedData.addressesInput)

return new WorkflowResponse(
updateStockLocationsStep(normalizedData.stockLocationInput)
)
}
)
16 changes: 16 additions & 0 deletions packages/core/types/src/stock-location/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -452,3 +452,19 @@ export type UpsertStockLocationInput = Partial<UpdateStockLocationInput> & {
*/
id?: string
}

export type UpdateStockLocationAddressInput = StockLocationAddressInput & {
id: string
}

export type UpsertStockLocationAddressInput = StockLocationAddressInput & {
id?: string
}

export interface FilterableStockLocationAddressProps
extends BaseFilterable<FilterableStockLocationAddressProps> {
/**
* The IDs to filter stock location's address by.
*/
id?: string | string[]
}
57 changes: 53 additions & 4 deletions packages/core/types/src/stock-location/service.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import { FindConfig } from "../common/common"
import { RestoreReturn, SoftDeleteReturn } from "../dal"
import { IModuleService } from "../modules-sdk"
import { Context } from "../shared-context"
import {
CreateStockLocationInput,
FilterableStockLocationAddressProps,
FilterableStockLocationProps,
StockLocationAddressDTO,
StockLocationDTO,
UpdateStockLocationInput,
UpsertStockLocationAddressInput,
UpsertStockLocationInput,
} from "./common"
import { RestoreReturn, SoftDeleteReturn } from "../dal"
import { Context } from "../shared-context"
import { FindConfig } from "../common/common"
import { IModuleService } from "../modules-sdk"

/**
* The main service interface for the Stock Location Module.
Expand Down Expand Up @@ -333,4 +336,50 @@ export interface IStockLocationService extends IModuleService {
config?: RestoreReturn<TReturnableLinkableKeys>,
sharedContext?: Context
): Promise<Record<string, string[]> | void>

/**
* This method retrieves a paginated list of stock location addresses based on optional filters and configuration.
*
* @param {FilterableStockLocationAddressProps} selector - The filters to apply on the retrieved stock location address.
* @param {FindConfig<StockLocationAddressDTO>} config - The configurations determining how the stock location address is retrieved. Its properties, such as `select` or `relations`, accept the
* attributes or relations associated with a stock location address.
* @param {Context} context - A context used to share resources, such as transaction manager, between the application and the module.
* @returns {Promise<StockLocationAddressDTO[]>} The list of stock location addressess.
*
*/
listStockLocationAddresses(
selector: FilterableStockLocationAddressProps,
config?: FindConfig<StockLocationAddressDTO>,
context?: Context
): Promise<StockLocationAddressDTO[]>

/**
* This method updates or creates stock location addresses
*
* @param {Partial<UpsertStockLocationAddressInput>[]} data - The list of Make all properties in t optional
* @param {Context} sharedContext - A context used to share resources, such as transaction manager, between the application and the module.
* @returns {Promise<StockLocationAddressDTO[]>} The created or updated stock location address
*
* @example
* {example-code}
*/
upsertStockLocationAddresses(
data: UpsertStockLocationAddressInput[],
sharedContext?: Context
): Promise<StockLocationAddressDTO[]>

/**
* This method deletes a stock location address by its ID.
*
* @param {string} id - The ID of the stock location address.
* @param {Context} context - A context used to share resources, such as transaction manager, between the application and the module.
* @returns {Promise<void>} Resolves when the stock location address is deleted successfully.
*
* @example
* await stockLocationModuleService.deleteStockLocationAddresses("sla_123")
*/
deleteStockLocationAddresses(
id: string | string[],
context?: Context
): Promise<void>
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ import {
ModulesSdkTypes,
StockLocationAddressInput,
StockLocationTypes,
UpdateStockLocationAddressInput,
UpdateStockLocationInput,
UpsertStockLocationAddressInput,
UpsertStockLocationInput,
} from "@medusajs/framework/types"
import {
Expand Down Expand Up @@ -258,4 +260,62 @@ export default class StockLocationModuleService
) {
return await this.stockLocationAddressService_.update(input, context)
}

async upsertStockLocationAddresses(
data: UpsertStockLocationAddressInput,
context?: Context
): Promise<StockLocationTypes.StockLocationAddressDTO>
async upsertStockLocationAddresses(
data: UpsertStockLocationAddressInput[],
context?: Context
): Promise<StockLocationTypes.StockLocationAddressDTO[]>

@InjectManager()
async upsertStockLocationAddresses(
data: UpsertStockLocationAddressInput | UpsertStockLocationAddressInput[],
@MedusaContext() context: Context = {}
): Promise<
| StockLocationTypes.StockLocationAddressDTO
| StockLocationTypes.StockLocationAddressDTO[]
> {
const input = Array.isArray(data) ? data : [data]

const result = await this.upsertStockLocationAddresses_(input, context)

return await this.baseRepository_.serialize<
| StockLocationTypes.StockLocationAddressDTO[]
| StockLocationTypes.StockLocationAddressDTO
>(Array.isArray(data) ? result : result[0])
}

@InjectTransactionManager()
async upsertStockLocationAddresses_(
input: UpsertStockLocationAddressInput[],
@MedusaContext() context: Context = {}
) {
const toUpdate = input.filter(
(location): location is UpdateStockLocationAddressInput => !!location.id
) as UpdateStockLocationAddressInput[]
const toCreate = input.filter(
(location) => !location.id
) as StockLocationAddressInput[]

const operations: Promise<
| InferEntityType<typeof StockLocationAddress>[]
| InferEntityType<typeof StockLocationAddress>
>[] = []

if (toCreate.length) {
operations.push(
this.stockLocationAddressService_.create(toCreate, context)
)
}
if (toUpdate.length) {
operations.push(
this.stockLocationAddressService_.update(toUpdate, context)
)
}

return (await promiseAll(operations)).flat()
}
}

0 comments on commit 4b6e6d0

Please sign in to comment.