feat(core-flows,types,medusa): validate deleting location level when quantities exist (#9086)

what:

- adds validation in workflow to prevent deleting a location level when reserved or stocked quantity exists
- disabled delete button when quantities exist
- consolidate delete workflows

<img width="1079" alt="Screenshot 2024-09-10 at 16 39 02" src="https://github.com/user-attachments/assets/cf1f4b2e-75ea-4f7c-9b97-24622396c632">

RESOLVES CC-120
This commit is contained in:
Riqwan Thamir
2024-09-10 17:59:03 +00:00
committed by GitHub
parent 4bf42f7889
commit c097931469
8 changed files with 108 additions and 87 deletions
@@ -1,56 +0,0 @@
import { ModuleRegistrationName } from "@medusajs/utils"
import { createStep, StepResponse } from "@medusajs/workflows-sdk"
import { IInventoryService } from "@medusajs/types"
import { MedusaError } from "@medusajs/utils"
export const deleteInventoryLevelsFromItemAndLocationsStepId =
"delete-inventory-levels-from-item-and-location-step"
/**
* This step removes one or more inventory levels by their associated inventory item and location.
*/
export const deleteInventoryLevelsFromItemAndLocationsStep = createStep(
deleteInventoryLevelsFromItemAndLocationsStepId,
async (
input: { inventory_item_id: string; location_id: string }[],
{ container }
) => {
if (!input.length) {
return new StepResponse(void 0, [])
}
const service = container.resolve<IInventoryService>(
ModuleRegistrationName.INVENTORY
)
const items = await service.listInventoryLevels({ $or: input }, {})
if (items.some((i) => i.reserved_quantity > 0)) {
const invalidDeletes = items.filter((i) => i.reserved_quantity > 0)
throw new MedusaError(
MedusaError.Types.NOT_ALLOWED,
`Cannot remove Inventory Levels for ${invalidDeletes
.map((i) => `Inventory Level ${i.id} at Location ${i.location_id}`)
.join(
", "
)} because there are reserved quantities for items at locations`
)
}
const deletedIds = items.map((i) => i.id)
await service.softDeleteInventoryLevels(deletedIds)
return new StepResponse(void 0, deletedIds)
},
async (prevLevelIds, { container }) => {
if (!prevLevelIds?.length) {
return
}
const service = container.resolve<IInventoryService>(
ModuleRegistrationName.INVENTORY
)
await service.restoreInventoryLevels(prevLevelIds)
}
)
@@ -4,7 +4,6 @@ export * from "./create-inventory-items"
export * from "./create-inventory-levels"
export * from "./delete-inventory-items"
export * from "./delete-inventory-levels"
export * from "./delete-levels-by-item-and-location"
export * from "./update-inventory-items"
export * from "./update-inventory-levels"
export * from "./validate-inventory-locations"
@@ -1,13 +1,11 @@
import { InventoryLevelDTO, InventoryTypes } from "@medusajs/types"
import {
createWorkflow,
WorkflowData,
WorkflowResponse,
createWorkflow,
} from "@medusajs/workflows-sdk"
import {
createInventoryLevelsStep,
deleteInventoryLevelsFromItemAndLocationsStep,
} from "../steps"
import { createInventoryLevelsStep } from "../steps"
import { deleteInventoryLevelsWorkflow } from "./delete-inventory-levels"
export interface BulkCreateDeleteLevelsWorkflowInput {
creates: InventoryTypes.CreateInventoryLevelInput[]
@@ -24,7 +22,11 @@ export const bulkCreateDeleteLevelsWorkflow = createWorkflow(
(
input: WorkflowData<BulkCreateDeleteLevelsWorkflowInput>
): WorkflowResponse<InventoryLevelDTO[]> => {
deleteInventoryLevelsFromItemAndLocationsStep(input.deletes)
deleteInventoryLevelsWorkflow.runAsStep({
input: {
$or: input.deletes,
},
})
return new WorkflowResponse(createInventoryLevelsStep(input.creates))
}
@@ -1,14 +1,43 @@
import {
createStep,
createWorkflow,
transform,
WorkflowData,
WorkflowResponse,
createWorkflow,
} from "@medusajs/workflows-sdk"
import { deleteInventoryLevelsStep } from "../steps"
import { FilterableInventoryLevelProps } from "@medusajs/types"
import {
deduplicate,
MedusaError,
ModuleRegistrationName,
} from "@medusajs/utils"
import { useRemoteQueryStep } from "../../common"
import { deleteEntitiesStep } from "../../common/steps/delete-entities"
/**
* This step validates that inventory levels are deletable.
*/
export const validateInventoryLevelsDelete = createStep(
"validate-inventory-levels-delete",
async function ({ inventoryLevels }: { inventoryLevels: any[] }) {
const undeleteableItems = inventoryLevels.filter(
(i) => i.reserved_quantity > 0 || i.stocked_quantity > 0
)
if (undeleteableItems.length) {
const stockLocationIds = deduplicate(
undeleteableItems.map((item) => item.location_id)
)
throw new MedusaError(
MedusaError.Types.NOT_ALLOWED,
`Cannot remove Inventory Levels for ${stockLocationIds} because there are stocked or reserved items at the locations`
)
}
}
)
export interface DeleteInventoryLevelsWorkflowInput {
ids: string[]
}
export const deleteInventoryLevelsWorkflowId =
"delete-inventory-levels-workflow"
/**
@@ -16,7 +45,28 @@ export const deleteInventoryLevelsWorkflowId =
*/
export const deleteInventoryLevelsWorkflow = createWorkflow(
deleteInventoryLevelsWorkflowId,
(input: WorkflowData<DeleteInventoryLevelsWorkflowInput>): WorkflowResponse<string[]> => {
return new WorkflowResponse(deleteInventoryLevelsStep(input.ids))
(input: WorkflowData<FilterableInventoryLevelProps>) => {
const inventoryLevels = useRemoteQueryStep({
entry_point: "inventory_levels",
fields: ["id", "stocked_quantity", "reserved_quantity", "location_id"],
variables: {
filters: input,
},
})
validateInventoryLevelsDelete({ inventoryLevels })
const idsToDelete = transform({ inventoryLevels }, ({ inventoryLevels }) =>
inventoryLevels.map((il) => il.id)
)
deleteEntitiesStep({
moduleRegistrationName: ModuleRegistrationName.INVENTORY,
invokeMethod: "softDeleteInventoryLevels",
compensateMethod: "restoreInventoryLevels",
data: idsToDelete,
})
return new WorkflowResponse(void 0)
}
)