feat(product): Create (+ workflow), delete, restore (#4459)
* Feat: create product with product module * feat: create product wip * feat: create product wip * feat: update product relation and generate image migration * lint * conitnue implementation * continue implementation and add integration tests for produceService.create * Add integration tests for product creation at the module level for the complete flow * only use persist since write operations are always wrapped in a transaction which will be committed and flushed * simplify the transaction wrapper to make future changes easier * feat: move some utils to the utils package to simplify its usage * tests: fix unit tests * feat: create variants along side the product * Add more integration tests an update migrations * chore: Update actions workflow to include packages integration tests * small types and utils cleanup * chore: Add support for database debug option * chore: Add missing types in package.json from types and util, validate that all the models are sync with medusa * expose retrieve method * fix types issues * fix unit tests and move integration tests workflow with the plugins integration tests * chore: remove migration function export from the definition to prevent them to be ran by the medusa cli just in case * fix package.json script * chore: workflows * feat: start creating the create product workflow * feat: add empty step for prices and sales channel * tests: update scripts and action envs * fix imports * feat: Add proper soft deleted support + add product deletion service public api * chore: update migrations * chore: update migrations * chore: update todo * feat: Add product deletion to the create-product workflow as compensation * chore: cleanup product utils * feat: Add support for cascade soft-remove * feat: refactor repository to take into account withDeleted * fix integration tests * Add support for force delete -> delete, cleanup repositories and improvements * Add support for restoring a product and add integration tests * cleaup + tests * types * fix integration tests * remove unnecessary comments * move specific mikro orm usage to the DAL * Cleanup workflow functions * Make deleted_at optional at the property level and add url index for the images * address feedback + cleanup * fix export * merge migrations into one * feat(product, types): added missing product variant methods (#4475) * chore: added missing product variant methods * chore: address PR feedback * chore: catch undefined case for retrieve + specs for variant service * chore: align TEntity + add changeset * chore: revert changeset, TEntity to ProductVariant * chore: write tests for pagination, unskip the test * Create chilled-mice-deliver.md * update integration fixtuers * update pipeline node version * rename github action * fix pipeline * feat(medusa, types): added missing category tests and service methods (#4499) * chore: added missing category tests and service methods * chore: added type changes to module service * chore: address pr feedback * update repositories manager usage and serialisation from the write public API * move serializisation to the DAL * rename template args * chore: added collection methods for module and collection service (#4505) * chore: added collection methods for module and collection service * Create fresh-islands-teach.md * chore: move retrieve entity to utils package * chore: make products optional in DTO type --------- Co-authored-by: Oliver Windall Juhl <59018053+olivermrbl@users.noreply.github.com> * feat(product): Apply transaction decorators to the services (#4512) --------- Co-authored-by: Riqwan Thamir <rmthamir@gmail.com> Co-authored-by: Oliver Windall Juhl <59018053+olivermrbl@users.noreply.github.com> Co-authored-by: Carlos R. L. Rodrigues <37986729+carlos-r-l-rodrigues@users.noreply.github.com>
This commit is contained in:
co-authored by
Oliver Windall Juhl
Riqwan Thamir
Carlos R. L. Rodrigues
parent
5b91a3503a
commit
befc2f1c80
@@ -0,0 +1,159 @@
|
||||
import { EntityManager } from "typeorm"
|
||||
import {
|
||||
IInventoryService,
|
||||
MedusaContainer,
|
||||
ProductTypes,
|
||||
} from "@medusajs/types"
|
||||
import { ulid } from "ulid"
|
||||
import { MedusaError } from "@medusajs/utils"
|
||||
import {
|
||||
DistributedTransaction,
|
||||
TransactionHandlerType,
|
||||
TransactionOrchestrator,
|
||||
TransactionPayload,
|
||||
TransactionState,
|
||||
TransactionStepsDefinition,
|
||||
} from "../../../utils/transaction"
|
||||
import { CreateProductVariantInput } from "../../../types/product-variant"
|
||||
import {
|
||||
attachInventoryItems,
|
||||
createInventoryItems,
|
||||
createProducts,
|
||||
removeInventoryItems,
|
||||
removeProducts,
|
||||
} from "../../functions"
|
||||
|
||||
enum Actions {
|
||||
createProduct = "createProduct",
|
||||
createPrices = "createPrices",
|
||||
attachToSalesChannel = "attachToSalesChannel",
|
||||
createInventoryItems = "createInventoryItems",
|
||||
attachInventoryItems = "attachInventoryItems",
|
||||
}
|
||||
|
||||
const workflowSteps: TransactionStepsDefinition = {
|
||||
next: {
|
||||
action: Actions.createProduct,
|
||||
saveResponse: true,
|
||||
next: {
|
||||
action: Actions.attachToSalesChannel,
|
||||
saveResponse: true,
|
||||
next: {
|
||||
action: Actions.createPrices,
|
||||
saveResponse: true,
|
||||
next: {
|
||||
action: Actions.createInventoryItems,
|
||||
saveResponse: true,
|
||||
next: {
|
||||
action: Actions.attachInventoryItems,
|
||||
noCompensation: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const createProductOrchestrator = new TransactionOrchestrator(
|
||||
"create-product",
|
||||
workflowSteps
|
||||
)
|
||||
|
||||
type InjectedDependencies = {
|
||||
manager: EntityManager
|
||||
container: MedusaContainer
|
||||
inventoryService?: IInventoryService
|
||||
}
|
||||
|
||||
export async function createProductWorkflow(
|
||||
dependencies: InjectedDependencies,
|
||||
productId: string,
|
||||
input: CreateProductVariantInput[]
|
||||
): Promise<DistributedTransaction> {
|
||||
const { manager, container } = dependencies
|
||||
async function transactionHandler(
|
||||
actionId: string,
|
||||
type: TransactionHandlerType,
|
||||
payload: TransactionPayload
|
||||
) {
|
||||
const command = {
|
||||
[Actions.createProduct]: {
|
||||
[TransactionHandlerType.INVOKE]: async (
|
||||
data: ProductTypes.CreateProductDTO[]
|
||||
) => {
|
||||
return await createProducts({
|
||||
container,
|
||||
data,
|
||||
})
|
||||
},
|
||||
[TransactionHandlerType.COMPENSATE]: async (
|
||||
data: any[],
|
||||
{ invoke }
|
||||
) => {
|
||||
const createdProducts = invoke[Actions.createProduct]
|
||||
return await removeProducts({ container, data: createdProducts })
|
||||
},
|
||||
},
|
||||
[Actions.createInventoryItems]: {
|
||||
[TransactionHandlerType.INVOKE]: async (
|
||||
data: CreateProductVariantInput[],
|
||||
{ invoke }
|
||||
) => {
|
||||
const { [Actions.createProduct]: products } = invoke
|
||||
|
||||
return await createInventoryItems({
|
||||
container,
|
||||
manager,
|
||||
data: products,
|
||||
})
|
||||
},
|
||||
[TransactionHandlerType.COMPENSATE]: async (_, { invoke }) => {
|
||||
const variantInventoryItemsData = invoke[Actions.createInventoryItems]
|
||||
await removeInventoryItems({
|
||||
container,
|
||||
manager,
|
||||
data: variantInventoryItemsData,
|
||||
})
|
||||
},
|
||||
},
|
||||
[Actions.attachInventoryItems]: {
|
||||
[TransactionHandlerType.INVOKE]: async (
|
||||
data: CreateProductVariantInput[],
|
||||
{ invoke }
|
||||
) => {
|
||||
const { [Actions.createInventoryItems]: inventoryItemsResult } =
|
||||
invoke
|
||||
|
||||
return await attachInventoryItems({
|
||||
container,
|
||||
manager,
|
||||
data: inventoryItemsResult,
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
return command[actionId][type](payload.data, payload.context)
|
||||
}
|
||||
|
||||
const orchestrator = createProductOrchestrator
|
||||
|
||||
const transaction = await orchestrator.beginTransaction(
|
||||
ulid(),
|
||||
transactionHandler,
|
||||
input
|
||||
)
|
||||
|
||||
await orchestrator.resume(transaction)
|
||||
|
||||
if (transaction.getState() !== TransactionState.DONE) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
transaction
|
||||
.getErrors()
|
||||
.map((err) => err.error?.message)
|
||||
.join("\n")
|
||||
)
|
||||
}
|
||||
|
||||
return transaction
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import {
|
||||
InventoryItemDTO,
|
||||
MedusaContainer,
|
||||
ProductTypes,
|
||||
} from "@medusajs/types"
|
||||
import { EntityManager } from "typeorm"
|
||||
|
||||
export async function attachInventoryItems({
|
||||
container,
|
||||
manager,
|
||||
data,
|
||||
}: {
|
||||
container: MedusaContainer
|
||||
manager: EntityManager
|
||||
data: {
|
||||
variant: ProductTypes.ProductVariantDTO
|
||||
inventoryItem: InventoryItemDTO
|
||||
}[]
|
||||
}) {
|
||||
const productVariantInventoryService = container
|
||||
.resolve("productVariantInventoryService")
|
||||
.withTransaction(manager)
|
||||
|
||||
return await Promise.all(
|
||||
data
|
||||
.filter((d) => d)
|
||||
.map(async ({ variant, inventoryItem }) => {
|
||||
return await productVariantInventoryService.attachInventoryItem(
|
||||
variant.id,
|
||||
inventoryItem.id
|
||||
)
|
||||
})
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import {
|
||||
IInventoryService,
|
||||
MedusaContainer,
|
||||
ProductTypes,
|
||||
} from "@medusajs/types"
|
||||
import { EntityManager } from "typeorm"
|
||||
|
||||
export async function createInventoryItems({
|
||||
container,
|
||||
manager,
|
||||
data,
|
||||
}: {
|
||||
container: MedusaContainer
|
||||
manager: EntityManager
|
||||
data: ProductTypes.ProductDTO[]
|
||||
}) {
|
||||
const inventoryService: IInventoryService =
|
||||
container.resolve("inventoryService")
|
||||
const context = { transactionManager: manager }
|
||||
|
||||
const variants = data.reduce(
|
||||
(
|
||||
acc: ProductTypes.ProductVariantDTO[],
|
||||
product: ProductTypes.ProductDTO
|
||||
) => {
|
||||
return acc.concat(product.variants)
|
||||
},
|
||||
[]
|
||||
)
|
||||
|
||||
return await Promise.all(
|
||||
variants.map(async (variant) => {
|
||||
if (!variant.manage_inventory) {
|
||||
return
|
||||
}
|
||||
|
||||
const inventoryItem = await inventoryService!.createInventoryItem(
|
||||
{
|
||||
sku: variant.sku!,
|
||||
origin_country: variant.origin_country!,
|
||||
hs_code: variant.hs_code!,
|
||||
mid_code: variant.mid_code!,
|
||||
material: variant.material!,
|
||||
weight: variant.weight!,
|
||||
length: variant.length!,
|
||||
height: variant.height!,
|
||||
width: variant.width!,
|
||||
},
|
||||
context
|
||||
)
|
||||
|
||||
return { variant, inventoryItem }
|
||||
})
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { MedusaContainer, ProductTypes } from "@medusajs/types"
|
||||
|
||||
export async function removeProducts({
|
||||
container,
|
||||
data,
|
||||
}: {
|
||||
container: MedusaContainer
|
||||
data: ProductTypes.ProductDTO[]
|
||||
}): Promise<ProductTypes.ProductDTO[]> {
|
||||
const productModuleService = container.resolve("productModuleService")
|
||||
return await productModuleService.softDelete(data.map((p) => p.id))
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export * from "./create-prducts"
|
||||
export * from "./remove-products"
|
||||
export * from "./create-inventory-items"
|
||||
export * from "./remove-inventory-items"
|
||||
export * from "./attach-inventory-items"
|
||||
@@ -0,0 +1,26 @@
|
||||
import { InventoryItemDTO, MedusaContainer } from "@medusajs/types"
|
||||
import { EntityManager } from "typeorm"
|
||||
|
||||
export async function removeInventoryItems({
|
||||
container,
|
||||
manager,
|
||||
data,
|
||||
}: {
|
||||
container: MedusaContainer
|
||||
manager: EntityManager
|
||||
data: {
|
||||
inventoryItem: InventoryItemDTO
|
||||
}[]
|
||||
}) {
|
||||
const inventoryService = container.resolve("inventoryService")
|
||||
const context = { transactionManager: manager }
|
||||
|
||||
return await Promise.all(
|
||||
data.map(async ({ inventoryItem }) => {
|
||||
return await inventoryService!.deleteInventoryItem(
|
||||
inventoryItem.id,
|
||||
context
|
||||
)
|
||||
})
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { MedusaContainer, ProductTypes } from "@medusajs/types"
|
||||
|
||||
export async function createProducts({
|
||||
container,
|
||||
data,
|
||||
}: {
|
||||
container: MedusaContainer
|
||||
data: ProductTypes.CreateProductDTO[]
|
||||
}) {
|
||||
const productModuleService = container.resolve("productModuleService")
|
||||
return await productModuleService.create(data)
|
||||
}
|
||||
Reference in New Issue
Block a user