fix: Accept filters in softDelete + fulfillment location clean-up (#7198)

This commit is contained in:
Oli Juhl
2024-05-03 18:32:07 +02:00
committed by GitHub
parent 1366e2efad
commit 2f7b53488d
20 changed files with 232 additions and 114 deletions

View File

@@ -245,6 +245,14 @@ medusaIntegrationTestRunner({
object: "stock_location",
deleted: true,
})
const stockLocations = await api.get(
`/admin/stock-locations`,
adminHeaders
)
expect(stockLocations.status).toEqual(200)
expect(stockLocations.data.stock_locations).toEqual([])
})
it("should successfully delete stock location associations", async () => {
@@ -390,7 +398,7 @@ medusaIntegrationTestRunner({
stockLocationId = createResponse.data.stock_location.id
})
it("should create a fulfillment set for the location", async () => {
it("should create a fulfillment set for the location and then delete it and its associations", async () => {
const response = await api.post(
`/admin/stock-locations/${stockLocationId}/fulfillment-sets?fields=id,*fulfillment_sets`,
{
@@ -407,6 +415,19 @@ medusaIntegrationTestRunner({
id: expect.any(String),
}),
])
await api.delete(
`/admin/stock-locations/${stockLocationId}`,
adminHeaders
)
const fulfillmentModule = appContainer.resolve(
ModuleRegistrationName.FULFILLMENT
)
const sets = await fulfillmentModule.list()
expect(sets).toHaveLength(0)
})
// This is really just to test the new Zod middleware. We don't need more of these.

View File

@@ -1634,12 +1634,12 @@ medusaIntegrationTestRunner({
},
},
{
[Modules.FULFILLMENT]: {
fulfillment_set_id: fulfillmentSet.id,
},
[Modules.STOCK_LOCATION]: {
stock_location_id: location.id,
},
[Modules.FULFILLMENT]: {
fulfillment_set_id: fulfillmentSet.id,
},
},
{
[Modules.FULFILLMENT]: {
@@ -1745,12 +1745,12 @@ medusaIntegrationTestRunner({
await remoteLink.create([
{
[Modules.FULFILLMENT]: {
fulfillment_set_id: fulfillmentSet.id,
},
[Modules.STOCK_LOCATION]: {
stock_location_id: location.id,
},
[Modules.FULFILLMENT]: {
fulfillment_set_id: fulfillmentSet.id,
},
},
{
[Modules.FULFILLMENT]: {
@@ -1849,12 +1849,12 @@ medusaIntegrationTestRunner({
},
},
{
[Modules.FULFILLMENT]: {
fulfillment_set_id: fulfillmentSet.id,
},
[Modules.STOCK_LOCATION]: {
stock_location_id: location.id,
},
[Modules.FULFILLMENT]: {
fulfillment_set_id: fulfillmentSet.id,
},
},
])

View File

@@ -1485,8 +1485,8 @@ medusaIntegrationTestRunner({
await remoteLinkService.create([
{
[Modules.FULFILLMENT]: { fulfillment_set_id: fulfillmentSet.id },
[Modules.STOCK_LOCATION]: { stock_location_id: stockLocation.id },
[Modules.FULFILLMENT]: { fulfillment_set_id: fulfillmentSet.id },
},
])

View File

@@ -52,18 +52,18 @@ medusaIntegrationTestRunner({
await remoteLink.create([
{
[Modules.FULFILLMENT]: {
fulfillment_set_id: fulfillmentSet.id,
},
[Modules.STOCK_LOCATION]: {
stock_location_id: euWarehouse.id,
},
[Modules.FULFILLMENT]: {
fulfillment_set_id: fulfillmentSet.id,
},
},
])
const linkQuery = remoteQueryObjectFromString({
entryPoint: "fulfillment_sets",
fields: ["id", "stock_locations.id"],
entryPoint: "stock_locations",
fields: ["id", "fulfillment_sets.id"],
})
const link = await remoteQuery(linkQuery)
@@ -72,10 +72,10 @@ medusaIntegrationTestRunner({
expect(link).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: fulfillmentSet.id,
stock_locations: expect.arrayContaining([
id: euWarehouse.id,
fulfillment_sets: expect.arrayContaining([
expect.objectContaining({
id: euWarehouse.id,
id: fulfillmentSet.id,
}),
]),
}),

View File

@@ -127,12 +127,12 @@ medusaIntegrationTestRunner({
await remoteLinkService.create([
{
[Modules.FULFILLMENT]: {
fulfillment_set_id: fulfillmentSet.id,
},
[Modules.STOCK_LOCATION]: {
stock_location_id: stockLocation.id,
},
[Modules.FULFILLMENT]: {
fulfillment_set_id: fulfillmentSet.id,
},
},
])

View File

@@ -24,12 +24,12 @@ export const associateFulfillmentSetsWithLocationStep = createStep(
.map((link) => {
return link.fulfillment_set_ids.map((id) => {
return {
[Modules.FULFILLMENT]: {
fulfillment_set_id: id,
},
[Modules.STOCK_LOCATION]: {
stock_location_id: link.location_id,
},
[Modules.FULFILLMENT]: {
fulfillment_set_id: id,
},
}
})
})

View File

@@ -1,16 +1,24 @@
import {
DeleteEntityInput,
ModuleRegistrationName,
Modules,
} from "@medusajs/modules-sdk"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
export const deleteStockLocationsStepId = "delete-stock-locations-step"
export const deleteStockLocationsStep = createStep(
deleteStockLocationsStepId,
async (input: string[], { container }) => {
const service = container.resolve(ModuleRegistrationName.STOCK_LOCATION)
await service.softDelete(input)
const softDeletedEntities = await service.softDelete(input)
return new StepResponse(void 0, input)
return new StepResponse(
{
[Modules.STOCK_LOCATION]: softDeletedEntities,
} as DeleteEntityInput,
input
)
},
async (deletedLocationIds, { container }) => {
if (!deletedLocationIds?.length) {

View File

@@ -1,8 +1,7 @@
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
import { Modules } from "@medusajs/modules-sdk"
import { deleteStockLocationsStep } from "../steps"
import { removeRemoteLinkStep } from "../../common/steps/remove-remote-links"
import { deleteStockLocationsStep } from "../steps"
interface WorkflowInput {
ids: string[]
@@ -12,10 +11,8 @@ export const deleteStockLocationsWorkflowId = "delete-stock-locations-workflow"
export const deleteStockLocationsWorkflow = createWorkflow(
deleteStockLocationsWorkflowId,
(input: WorkflowData<WorkflowInput>) => {
deleteStockLocationsStep(input.ids)
const softDeletedEntities = deleteStockLocationsStep(input.ids)
removeRemoteLinkStep({
[Modules.STOCK_LOCATION]: { stock_location_id: input.ids },
})
removeRemoteLinkStep(softDeletedEntities)
}
)

View File

@@ -3,8 +3,8 @@ import { Context } from "../shared-context"
import {
BaseFilterable,
FilterQuery,
FindOptions,
FilterQuery as InternalFilterQuery,
FindOptions,
UpsertWithReplaceConfig,
} from "./index"
@@ -61,7 +61,11 @@ export interface RepositoryService<T = any> extends BaseRepositoryService<T> {
* @returns [T[], Record<string, string[]>] the second value being the map of the entity names and ids that were soft deleted
*/
softDelete(
idsOrFilter: string[] | InternalFilterQuery,
idsOrFilter:
| string
| string[]
| InternalFilterQuery
| InternalFilterQuery[],
context?: Context
): Promise<[T[], Record<string, unknown[]>]>

View File

@@ -68,7 +68,11 @@ export interface InternalModuleService<
): Promise<void>
softDelete(
idsOrFilter: string[] | InternalFilterQuery,
idsOrFilter:
| string
| string[]
| InternalFilterQuery
| InternalFilterQuery[],
sharedContext?: Context
): Promise<[TEntity[], Record<string, unknown[]>]>

View File

@@ -14,7 +14,6 @@ import {
LoadStrategy,
ReferenceType,
RequiredEntityData,
wrap,
} from "@mikro-orm/core"
import { FindOptions as MikroOptions } from "@mikro-orm/core/drivers/IDatabaseDriver"
import {
@@ -25,9 +24,9 @@ import {
} from "@mikro-orm/core/typings"
import { SqlEntityManager } from "@mikro-orm/postgresql"
import {
MedusaError,
arrayDifference,
isString,
MedusaError,
promiseAll,
} from "../../common"
import { buildQuery } from "../../modules-sdk"
@@ -35,9 +34,9 @@ import {
getSoftDeletedCascadedEntitiesIdsMappedBy,
transactionWrapper,
} from "../utils"
import { mikroOrmUpdateDeletedAtRecursively } from "./utils"
import { mikroOrmSerializer } from "./mikro-orm-serializer"
import { dbErrorMapper } from "./db-error-mapper"
import { mikroOrmSerializer } from "./mikro-orm-serializer"
import { mikroOrmUpdateDeletedAtRecursively } from "./utils"
export class MikroOrmBase<T = any> {
readonly manager_: any
@@ -101,6 +100,17 @@ export class MikroOrmBaseRepository<T extends object = object>
super(...arguments)
}
static buildUniqueCompositeKeyValue(keys: string[], data: object) {
return keys.map((k) => data[k]).join("_")
}
static retrievePrimaryKeys(entity: EntityClass<any> | EntitySchema) {
return (
(entity as EntitySchema).meta?.primaryKeys ??
(entity as EntityClass<any>).prototype.__meta.primaryKeys ?? ["id"]
)
}
create(data: unknown[], context?: Context): Promise<T[]> {
throw new Error("Method not implemented.")
}
@@ -142,21 +152,14 @@ export class MikroOrmBaseRepository<T extends object = object>
}
async softDelete(
idsOrFilter: string[] | InternalFilterQuery,
filters:
| string
| string[]
| (FilterQuery<T> & BaseFilterable<FilterQuery<T>>)
| (FilterQuery<T> & BaseFilterable<FilterQuery<T>>)[],
sharedContext: Context = {}
): Promise<[T[], Record<string, unknown[]>]> {
const isArray = Array.isArray(idsOrFilter)
// TODO handle composite keys
const filter =
isArray || isString(idsOrFilter)
? {
id: {
$in: isArray ? idsOrFilter : [idsOrFilter],
},
}
: idsOrFilter
const entities = await this.find({ where: filter as any }, sharedContext)
const entities = await this.find({ where: filters as any }, sharedContext)
const date = new Date()
const manager = this.getActiveManager<SqlEntityManager>(sharedContext)
@@ -286,17 +289,6 @@ export function mikroOrmBaseRepositoryFactory<T extends object = object>(
})
}
static buildUniqueCompositeKeyValue(keys: string[], data: object) {
return keys.map((k) => data[k]).join("_")
}
static retrievePrimaryKeys(entity: EntityClass<T> | EntitySchema<T>) {
return (
(entity as EntitySchema<T>).meta?.primaryKeys ??
(entity as EntityClass<T>).prototype.__meta.primaryKeys ?? ["id"]
)
}
async create(data: any[], context?: Context): Promise<T[]> {
const manager = this.getActiveManager<EntityManager>(context)
@@ -768,6 +760,32 @@ export function mikroOrmBaseRepositoryFactory<T extends object = object>(
return orderedEntities
}
async softDelete(
filters:
| string
| string[]
| (FilterQuery<T> & BaseFilterable<FilterQuery<T>>)
| (FilterQuery<T> & BaseFilterable<FilterQuery<T>>)[],
sharedContext: Context = {}
): Promise<[T[], Record<string, unknown[]>]> {
const primaryKeys =
MikroOrmAbstractBaseRepository_.retrievePrimaryKeys(entity)
const filterArray = Array.isArray(filters) ? filters : [filters]
const normalizedFilters: FilterQuery = {
$or: filterArray.map((filter) => {
// TODO: add support for composite keys
if (isString(filter)) {
return { [primaryKeys[0]]: filter }
}
return filter
}),
}
return await super.softDelete(normalizedFilters, sharedContext)
}
}
return MikroOrmAbstractBaseRepository_ as unknown as {

View File

@@ -44,11 +44,11 @@ export const LINKS = {
Modules.STOCK_LOCATION,
"location_id"
),
FulfillmentSetLocation: composeLinkName(
Modules.FULFILLMENT,
"fulfillment_set_id",
LocationFulfillmentSet: composeLinkName(
Modules.STOCK_LOCATION,
"location_id"
"stock_location_id",
Modules.FULFILLMENT,
"fulfillment_set_id"
),
OrderPromotion: composeLinkName(
Modules.ORDER,

View File

@@ -12,11 +12,11 @@ import {
SoftDeleteReturn,
} from "@medusajs/types"
import {
MapToConfig,
isString,
kebabCase,
lowerCaseFirst,
mapObjectTo,
MapToConfig,
pluralize,
upperCaseFirst,
} from "../common"

View File

@@ -19,13 +19,13 @@ import {
MedusaError,
shouldForceTransaction,
} from "../common"
import { FreeTextSearchFilterKey } from "../dal"
import { buildQuery } from "./build-query"
import {
InjectManager,
InjectTransactionManager,
MedusaContext,
} from "./decorators"
import { FreeTextSearchFilterKey } from "../dal"
type SelectorAndData = {
selector: FilterQuery<any> | BaseFilterable<FilterQuery<any>>
@@ -440,10 +440,17 @@ export function internalModuleServiceFactory<
@InjectTransactionManager(propertyRepositoryName)
async softDelete(
idsOrFilter: string[] | InternalFilterQuery,
idsOrFilter:
| string
| string[]
| InternalFilterQuery
| InternalFilterQuery[],
@MedusaContext() sharedContext: Context = {}
): Promise<[TEntity[], Record<string, unknown[]>]> {
if (Array.isArray(idsOrFilter) && !idsOrFilter.length) {
if (
(Array.isArray(idsOrFilter) && !idsOrFilter.length) ||
(!Array.isArray(idsOrFilter) && !idsOrFilter)
) {
return [[], {}]
}

View File

@@ -14,17 +14,17 @@ import {
UpdateServiceZoneDTO,
} from "@medusajs/types"
import {
arrayDifference,
EmitEvents,
FulfillmentUtils,
getSetDifference,
InjectManager,
InjectTransactionManager,
isString,
MedusaContext,
MedusaError,
Modules,
ModulesSdkUtils,
arrayDifference,
getSetDifference,
isString,
promiseAll,
} from "@medusajs/utils"
import {

View File

@@ -2,54 +2,42 @@ import { Modules } from "@medusajs/modules-sdk"
import { ModuleJoinerConfig } from "@medusajs/types"
import { LINKS } from "@medusajs/utils"
export const FulfillmentSetLocation: ModuleJoinerConfig = {
serviceName: LINKS.FulfillmentSetLocation,
export const LocationFulfillmentSet: ModuleJoinerConfig = {
serviceName: LINKS.LocationFulfillmentSet,
isLink: true,
databaseConfig: {
tableName: "fulfillment_set_location",
idPrefix: "fsloc",
tableName: "location_fulfillment_set",
idPrefix: "locfs",
},
alias: [
{
name: ["fulfillment_set_location", "fulfillment_set_locations"],
name: ["location_fulfillment_set", "location_fulfillment_sets"],
args: {
entity: "LinkFulfillmentSetLocation",
entity: "LinkLocationFulfillmentSet",
},
},
],
primaryKeys: ["id", "fulfillment_set_id", "stock_location_id"],
primaryKeys: ["id", "stock_location_id", "fulfillment_set_id"],
relationships: [
{
serviceName: Modules.FULFILLMENT,
primaryKey: "id",
foreignKey: "fulfillment_set_id",
alias: "fulfillment_set",
},
{
serviceName: Modules.STOCK_LOCATION,
primaryKey: "id",
foreignKey: "stock_location_id",
alias: "location",
},
{
serviceName: Modules.FULFILLMENT,
primaryKey: "id",
foreignKey: "fulfillment_set_id",
alias: "fulfillment_set",
deleteCascade: true,
},
],
extends: [
{
serviceName: Modules.FULFILLMENT,
fieldAlias: {
stock_locations: "locations_link.location",
},
relationship: {
serviceName: LINKS.FulfillmentSetLocation,
primaryKey: "fulfillment_set_id",
foreignKey: "id",
alias: "locations_link",
isList: true,
},
},
{
serviceName: Modules.STOCK_LOCATION,
relationship: {
serviceName: LINKS.FulfillmentSetLocation,
serviceName: LINKS.LocationFulfillmentSet,
primaryKey: "stock_location_id",
foreignKey: "id",
alias: "fulfillment_set_link",
@@ -59,5 +47,14 @@ export const FulfillmentSetLocation: ModuleJoinerConfig = {
fulfillment_sets: "fulfillment_set_link.fulfillment_set",
},
},
{
serviceName: Modules.FULFILLMENT,
relationship: {
serviceName: LINKS.LocationFulfillmentSet,
primaryKey: "fulfillment_set_id",
foreignKey: "id",
alias: "locations_link",
},
},
],
}

View File

@@ -3,8 +3,8 @@ export * from "./cart-payment-collection"
export * from "./cart-promotion"
export * from "./cart-region"
export * from "./cart-sales-channel"
export * from "./fulfillment-set-location"
export * from "./inventory-level-stock-location"
export * from "./location-fulfillment-set"
export * from "./order-customer"
export * from "./order-promotion"
export * from "./order-region"
@@ -18,3 +18,4 @@ export * from "./region-payment-provider"
export * from "./sales-channel-location"
export * from "./shipping-option-price-set"
export * from "./store-default-currency"

View File

@@ -0,0 +1,60 @@
import { Modules } from "@medusajs/modules-sdk"
import { ModuleJoinerConfig } from "@medusajs/types"
import { LINKS } from "@medusajs/utils"
export const LocationFulfillmentSet: ModuleJoinerConfig = {
serviceName: LINKS.LocationFulfillmentSet,
isLink: true,
databaseConfig: {
tableName: "location_fulfillment_set",
idPrefix: "locfs",
},
alias: [
{
name: ["location_fulfillment_set", "location_fulfillment_sets"],
args: {
entity: "LinkLocationFulfillmentSet",
},
},
],
primaryKeys: ["id", "stock_location_id", "fulfillment_set_id"],
relationships: [
{
serviceName: Modules.STOCK_LOCATION,
primaryKey: "id",
foreignKey: "stock_location_id",
alias: "location",
},
{
serviceName: Modules.FULFILLMENT,
primaryKey: "id",
foreignKey: "fulfillment_set_id",
alias: "fulfillment_set",
deleteCascade: true,
},
],
extends: [
{
serviceName: Modules.STOCK_LOCATION,
relationship: {
serviceName: LINKS.LocationFulfillmentSet,
primaryKey: "stock_location_id",
foreignKey: "id",
alias: "fulfillment_set_link",
isList: true,
},
fieldAlias: {
fulfillment_sets: "fulfillment_set_link.fulfillment_set",
},
},
{
serviceName: Modules.FULFILLMENT,
relationship: {
serviceName: LINKS.LocationFulfillmentSet,
primaryKey: "fulfillment_set_id",
foreignKey: "id",
alias: "locations_link",
},
},
],
}

View File

@@ -6,6 +6,7 @@ import moduleSchema from "./schema"
export const LinkableKeys = {
stock_location_id: StockLocation.name,
location_id: StockLocation.name,
}
const entityLinkableKeysMap: MapToConfig = {}
@@ -21,10 +22,7 @@ export const entityNameToLinkableKeysMap: MapToConfig = entityLinkableKeysMap
export const joinerConfig: ModuleJoinerConfig = {
serviceName: Modules.STOCK_LOCATION,
primaryKeys: ["id"],
linkableKeys: {
stock_location_id: StockLocation.name,
location_id: StockLocation.name,
},
linkableKeys: LinkableKeys,
schema: moduleSchema,
alias: [
{

View File

@@ -1,16 +1,18 @@
import {
DALUtils,
Searchable,
createPsqlIndexStatementHelper,
generateEntityId,
} from "@medusajs/utils"
import {
BeforeCreate,
Entity,
Filter,
ManyToOne,
OnInit,
PrimaryKey,
Property,
} from "@mikro-orm/core"
import {
createPsqlIndexStatementHelper,
generateEntityId,
Searchable,
} from "@medusajs/utils"
import { StockLocationAddress } from "./stock-location-address"
@@ -21,6 +23,7 @@ const StockLocationDeletedAtIndex = createPsqlIndexStatementHelper({
})
@Entity()
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
export class StockLocation {
@PrimaryKey({ columnType: "text" })
id: string