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,20 @@
|
||||
import { asClass, asValue, createContainer } from "awilix"
|
||||
import { ProductService } from "@services"
|
||||
|
||||
export const nonExistingProductId = "non-existing-id"
|
||||
|
||||
export const mockContainer = createContainer()
|
||||
mockContainer.register({
|
||||
transaction: asValue(async (task) => await task()),
|
||||
productRepository: asValue({
|
||||
find: jest.fn().mockImplementation(async ({ where: { id } }) => {
|
||||
if (id === nonExistingProductId) {
|
||||
return []
|
||||
}
|
||||
|
||||
return [{}]
|
||||
}),
|
||||
findAndCount: jest.fn().mockResolvedValue([[], 0]),
|
||||
}),
|
||||
productService: asClass(ProductService),
|
||||
})
|
||||
@@ -1,29 +1,65 @@
|
||||
import { asClass, asValue, createContainer } from "awilix"
|
||||
import { ProductService } from "@services"
|
||||
|
||||
const container = createContainer()
|
||||
container.register({
|
||||
productRepository: asValue({
|
||||
find: jest.fn().mockResolvedValue([]),
|
||||
findAndCount: jest.fn().mockResolvedValue([[], 0]),
|
||||
}),
|
||||
productVariantService: asValue({
|
||||
list: jest.fn().mockResolvedValue([]),
|
||||
}),
|
||||
productTagService: asValue({
|
||||
list: jest.fn().mockResolvedValue([]),
|
||||
}),
|
||||
productService: asClass(ProductService),
|
||||
})
|
||||
import { mockContainer, nonExistingProductId } from "../__fixtures__/product"
|
||||
|
||||
describe("Product service", function () {
|
||||
beforeEach(function () {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("should retrieve a product", async function () {
|
||||
const productService = mockContainer.resolve("productService")
|
||||
const productRepository = mockContainer.resolve("productRepository")
|
||||
|
||||
const productId = "existing-product"
|
||||
await productService.retrieve(productId)
|
||||
expect(productRepository.find).toHaveBeenCalledWith(
|
||||
{
|
||||
where: {
|
||||
id: productId,
|
||||
},
|
||||
options: {
|
||||
fields: undefined,
|
||||
limit: 15,
|
||||
offset: undefined,
|
||||
populate: [],
|
||||
withDeleted: undefined,
|
||||
},
|
||||
},
|
||||
undefined
|
||||
)
|
||||
})
|
||||
|
||||
it("should fail to retrieve a product", async function () {
|
||||
const productService = mockContainer.resolve("productService")
|
||||
const productRepository = mockContainer.resolve("productRepository")
|
||||
|
||||
const err = await productService
|
||||
.retrieve(nonExistingProductId)
|
||||
.catch((e) => e)
|
||||
|
||||
expect(productRepository.find).toHaveBeenCalledWith(
|
||||
{
|
||||
where: {
|
||||
id: nonExistingProductId,
|
||||
},
|
||||
options: {
|
||||
fields: undefined,
|
||||
limit: 15,
|
||||
offset: undefined,
|
||||
populate: [],
|
||||
withDeleted: undefined,
|
||||
},
|
||||
},
|
||||
undefined
|
||||
)
|
||||
|
||||
expect(err.message).toBe(
|
||||
`Product with id: ${nonExistingProductId} was not found`
|
||||
)
|
||||
})
|
||||
|
||||
it("should list products", async function () {
|
||||
const productService = container.resolve("productService")
|
||||
const productRepository = container.resolve("productRepository")
|
||||
const productService = mockContainer.resolve("productService")
|
||||
const productRepository = mockContainer.resolve("productRepository")
|
||||
|
||||
const filters = {}
|
||||
const config = {
|
||||
@@ -32,27 +68,31 @@ describe("Product service", function () {
|
||||
|
||||
await productService.list(filters, config)
|
||||
|
||||
expect(productRepository.find).toHaveBeenCalledWith({
|
||||
where: {},
|
||||
options: {
|
||||
fields: undefined,
|
||||
limit: undefined,
|
||||
offset: undefined,
|
||||
populate: [],
|
||||
expect(productRepository.find).toHaveBeenCalledWith(
|
||||
{
|
||||
where: {},
|
||||
options: {
|
||||
fields: undefined,
|
||||
limit: 15,
|
||||
offset: undefined,
|
||||
populate: [],
|
||||
withDeleted: undefined,
|
||||
},
|
||||
},
|
||||
})
|
||||
undefined
|
||||
)
|
||||
})
|
||||
|
||||
it("should list products with filters", async function () {
|
||||
const productService = container.resolve("productService")
|
||||
const productRepository = container.resolve("productRepository")
|
||||
const productService = mockContainer.resolve("productService")
|
||||
const productRepository = mockContainer.resolve("productRepository")
|
||||
|
||||
const filters = {
|
||||
tags: {
|
||||
value: {
|
||||
$in: ["test"],
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
const config = {
|
||||
relations: [],
|
||||
@@ -60,33 +100,37 @@ describe("Product service", function () {
|
||||
|
||||
await productService.list(filters, config)
|
||||
|
||||
expect(productRepository.find).toHaveBeenCalledWith({
|
||||
where: {
|
||||
tags: {
|
||||
value: {
|
||||
$in: ["test"]
|
||||
}
|
||||
expect(productRepository.find).toHaveBeenCalledWith(
|
||||
{
|
||||
where: {
|
||||
tags: {
|
||||
value: {
|
||||
$in: ["test"],
|
||||
},
|
||||
},
|
||||
},
|
||||
options: {
|
||||
fields: undefined,
|
||||
limit: 15,
|
||||
offset: undefined,
|
||||
populate: [],
|
||||
withDeleted: undefined,
|
||||
},
|
||||
},
|
||||
options: {
|
||||
fields: undefined,
|
||||
limit: undefined,
|
||||
offset: undefined,
|
||||
populate: [],
|
||||
},
|
||||
})
|
||||
undefined
|
||||
)
|
||||
})
|
||||
|
||||
it("should list products with filters and relations", async function () {
|
||||
const productService = container.resolve("productService")
|
||||
const productRepository = container.resolve("productRepository")
|
||||
const productService = mockContainer.resolve("productService")
|
||||
const productRepository = mockContainer.resolve("productRepository")
|
||||
|
||||
const filters = {
|
||||
tags: {
|
||||
value: {
|
||||
$in: ["test"],
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
const config = {
|
||||
relations: ["tags"],
|
||||
@@ -94,20 +138,62 @@ describe("Product service", function () {
|
||||
|
||||
await productService.list(filters, config)
|
||||
|
||||
expect(productRepository.find).toHaveBeenCalledWith({
|
||||
where: {
|
||||
tags: {
|
||||
value: {
|
||||
$in: ["test"]
|
||||
}
|
||||
expect(productRepository.find).toHaveBeenCalledWith(
|
||||
{
|
||||
where: {
|
||||
tags: {
|
||||
value: {
|
||||
$in: ["test"],
|
||||
},
|
||||
},
|
||||
},
|
||||
options: {
|
||||
fields: undefined,
|
||||
limit: 15,
|
||||
offset: undefined,
|
||||
withDeleted: undefined,
|
||||
populate: ["tags"],
|
||||
},
|
||||
},
|
||||
options: {
|
||||
fields: undefined,
|
||||
limit: undefined,
|
||||
offset: undefined,
|
||||
populate: ["tags"],
|
||||
undefined
|
||||
)
|
||||
})
|
||||
|
||||
it("should list and count the products with filters and relations", async function () {
|
||||
const productService = mockContainer.resolve("productService")
|
||||
const productRepository = mockContainer.resolve("productRepository")
|
||||
|
||||
const filters = {
|
||||
tags: {
|
||||
value: {
|
||||
$in: ["test"],
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
const config = {
|
||||
relations: ["tags"],
|
||||
}
|
||||
|
||||
await productService.listAndCount(filters, config)
|
||||
|
||||
expect(productRepository.findAndCount).toHaveBeenCalledWith(
|
||||
{
|
||||
where: {
|
||||
tags: {
|
||||
value: {
|
||||
$in: ["test"],
|
||||
},
|
||||
},
|
||||
},
|
||||
options: {
|
||||
fields: undefined,
|
||||
limit: 15,
|
||||
offset: undefined,
|
||||
withDeleted: undefined,
|
||||
populate: ["tags"],
|
||||
},
|
||||
},
|
||||
undefined
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -4,3 +4,6 @@ export { default as ProductTagService } from "./product-tag"
|
||||
export { default as ProductVariantService } from "./product-variant"
|
||||
export { default as ProductCollectionService } from "./product-collection"
|
||||
export { default as ProductCategoryService } from "./product-category"
|
||||
export { default as ProductTypeService } from "./product-type"
|
||||
export { default as ProductOptionService } from "./product-option"
|
||||
export { default as ProductImageService } from "./product-image"
|
||||
|
||||
@@ -1,34 +1,99 @@
|
||||
import { ProductCategory } from "@models"
|
||||
import { DAL, FindConfig, ProductTypes, SharedContext } from "@medusajs/types"
|
||||
import { buildQuery } from "../utils"
|
||||
import { Context, DAL, FindConfig, ProductTypes } from "@medusajs/types"
|
||||
import { ModulesSdkUtils, MedusaError, isDefined } from "@medusajs/utils"
|
||||
|
||||
type InjectedDependencies = {
|
||||
productCategoryRepository: DAL.RepositoryService
|
||||
productCategoryRepository: DAL.TreeRepositoryService
|
||||
}
|
||||
|
||||
export default class ProductCategoryService<TEntity = ProductCategory> {
|
||||
protected readonly productCategoryRepository_: DAL.RepositoryService
|
||||
export default class ProductCategoryService<
|
||||
TEntity extends ProductCategory = ProductCategory
|
||||
> {
|
||||
protected readonly productCategoryRepository_: DAL.TreeRepositoryService
|
||||
|
||||
constructor({ productCategoryRepository }: InjectedDependencies) {
|
||||
this.productCategoryRepository_ = productCategoryRepository
|
||||
}
|
||||
|
||||
async retrieve(
|
||||
productCategoryId: string,
|
||||
config: FindConfig<ProductTypes.ProductCategoryDTO> = {},
|
||||
sharedContext?: Context
|
||||
): Promise<TEntity> {
|
||||
if (!isDefined(productCategoryId)) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`"productCategoryId" must be defined`
|
||||
)
|
||||
}
|
||||
|
||||
const queryOptions = ModulesSdkUtils.buildQuery<ProductCategory>({
|
||||
id: productCategoryId,
|
||||
}, config)
|
||||
|
||||
const transformOptions = {
|
||||
includeDescendantsTree: true,
|
||||
}
|
||||
|
||||
const productCategories = await this.productCategoryRepository_.find(
|
||||
queryOptions,
|
||||
transformOptions,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
if (!productCategories?.length) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`ProductCategory with id: ${productCategoryId} was not found`
|
||||
)
|
||||
}
|
||||
|
||||
return productCategories[0] as TEntity
|
||||
}
|
||||
|
||||
async list(
|
||||
filters: ProductTypes.FilterableProductCategoryProps = {},
|
||||
config: FindConfig<ProductTypes.ProductCategoryDTO> = {},
|
||||
sharedContext?: SharedContext
|
||||
sharedContext?: Context
|
||||
): Promise<TEntity[]> {
|
||||
const transformOptions = {
|
||||
includeDescendantsTree: filters?.include_descendants_tree || false
|
||||
includeDescendantsTree: filters?.include_descendants_tree || false,
|
||||
}
|
||||
delete filters.include_descendants_tree
|
||||
|
||||
const queryOptions = buildQuery<TEntity>(filters, config)
|
||||
const queryOptions = ModulesSdkUtils.buildQuery<ProductCategory>(
|
||||
filters,
|
||||
config
|
||||
)
|
||||
queryOptions.where ??= {}
|
||||
|
||||
return await this.productCategoryRepository_.find(
|
||||
return (await this.productCategoryRepository_.find(
|
||||
queryOptions,
|
||||
transformOptions,
|
||||
sharedContext
|
||||
)) as TEntity[]
|
||||
}
|
||||
|
||||
async listAndCount(
|
||||
filters: ProductTypes.FilterableProductCategoryProps = {},
|
||||
config: FindConfig<ProductTypes.ProductCategoryDTO> = {},
|
||||
sharedContext?: Context
|
||||
): Promise<[TEntity[], number]> {
|
||||
const transformOptions = {
|
||||
includeDescendantsTree: filters?.include_descendants_tree || false,
|
||||
}
|
||||
delete filters.include_descendants_tree
|
||||
|
||||
const queryOptions = ModulesSdkUtils.buildQuery<ProductCategory>(
|
||||
filters,
|
||||
config
|
||||
)
|
||||
queryOptions.where ??= {}
|
||||
|
||||
return (await this.productCategoryRepository_.findAndCount(
|
||||
queryOptions,
|
||||
transformOptions,
|
||||
sharedContext
|
||||
)) as [TEntity[], number]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +1,74 @@
|
||||
import { ProductCollection } from "@models"
|
||||
import { DAL, FindConfig, ProductTypes, SharedContext } from "@medusajs/types"
|
||||
import { buildQuery } from "../utils"
|
||||
import { Context, DAL, FindConfig, ProductTypes } from "@medusajs/types"
|
||||
import { ModulesSdkUtils, retrieveEntity } from "@medusajs/utils"
|
||||
|
||||
type InjectedDependencies = {
|
||||
productCollectionRepository: DAL.RepositoryService
|
||||
}
|
||||
|
||||
export default class ProductCollectionService<TEntity = ProductCollection> {
|
||||
protected readonly productCollectionRepository_: DAL.RepositoryService<TEntity>
|
||||
export default class ProductCollectionService<
|
||||
TEntity extends ProductCollection = ProductCollection
|
||||
> {
|
||||
protected readonly productCollectionRepository_: DAL.TreeRepositoryService
|
||||
|
||||
constructor({ productCollectionRepository }: InjectedDependencies) {
|
||||
this.productCollectionRepository_ = productCollectionRepository
|
||||
}
|
||||
|
||||
async retrieve(
|
||||
productCollectionId: string,
|
||||
config: FindConfig<ProductTypes.ProductCollectionDTO> = {},
|
||||
sharedContext?: Context
|
||||
): Promise<TEntity> {
|
||||
return (await retrieveEntity<
|
||||
ProductCollection,
|
||||
ProductTypes.ProductCollectionDTO
|
||||
>({
|
||||
id: productCollectionId,
|
||||
entityName: ProductCollection.name,
|
||||
repository: this.productCollectionRepository_,
|
||||
config,
|
||||
sharedContext,
|
||||
})) as TEntity
|
||||
}
|
||||
|
||||
async list(
|
||||
filters: ProductTypes.FilterableProductCollectionProps = {},
|
||||
config: FindConfig<ProductTypes.ProductCollectionDTO> = {},
|
||||
sharedContext?: SharedContext
|
||||
sharedContext?: Context
|
||||
): Promise<TEntity[]> {
|
||||
const queryOptions = buildQuery<TEntity>(filters, config)
|
||||
return (await this.productCollectionRepository_.find(
|
||||
this.buildListQueryOptions(filters, config),
|
||||
sharedContext
|
||||
)) as TEntity[]
|
||||
}
|
||||
|
||||
async listAndCount(
|
||||
filters: ProductTypes.FilterableProductCollectionProps = {},
|
||||
config: FindConfig<ProductTypes.ProductCollectionDTO> = {},
|
||||
sharedContext?: Context
|
||||
): Promise<[TEntity[], number]> {
|
||||
return (await this.productCollectionRepository_.findAndCount(
|
||||
this.buildListQueryOptions(filters, config),
|
||||
sharedContext
|
||||
)) as [TEntity[], number]
|
||||
}
|
||||
|
||||
protected buildListQueryOptions(
|
||||
filters: ProductTypes.FilterableProductCollectionProps = {},
|
||||
config: FindConfig<ProductTypes.ProductCollectionDTO> = {}
|
||||
) {
|
||||
const queryOptions = ModulesSdkUtils.buildQuery<ProductCollection>(
|
||||
filters,
|
||||
config
|
||||
)
|
||||
|
||||
queryOptions.where ??= {}
|
||||
|
||||
if (filters.title) {
|
||||
queryOptions.where["title"] = { $like: filters.title }
|
||||
}
|
||||
|
||||
return await this.productCollectionRepository_.find(queryOptions)
|
||||
return queryOptions
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { Image } from "@models"
|
||||
import { Context, DAL } from "@medusajs/types"
|
||||
import { InjectTransactionManager, MedusaContext } from "@medusajs/utils"
|
||||
import { doNotForceTransaction } from "../utils"
|
||||
import { ProductImageRepository } from "@repositories"
|
||||
|
||||
type InjectedDependencies = {
|
||||
productImageRepository: DAL.RepositoryService
|
||||
}
|
||||
|
||||
export default class ProductImageService<TEntity extends Image = Image> {
|
||||
protected readonly productImageRepository_: DAL.RepositoryService
|
||||
|
||||
constructor({ productImageRepository }: InjectedDependencies) {
|
||||
this.productImageRepository_ = productImageRepository
|
||||
}
|
||||
|
||||
@InjectTransactionManager(doNotForceTransaction, "productImageRepository_")
|
||||
async upsert(
|
||||
urls: string[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<TEntity[]> {
|
||||
return (await (this.productImageRepository_ as ProductImageRepository)
|
||||
.upsert!(urls, sharedContext)) as TEntity[]
|
||||
}
|
||||
}
|
||||
@@ -1,66 +1,105 @@
|
||||
import {
|
||||
ProductCategoryService,
|
||||
ProductCollectionService,
|
||||
ProductOptionService,
|
||||
ProductService,
|
||||
ProductTagService,
|
||||
ProductTypeService,
|
||||
ProductVariantService,
|
||||
} from "@services"
|
||||
import {
|
||||
Image,
|
||||
Product,
|
||||
ProductCategory,
|
||||
ProductCollection,
|
||||
ProductOption,
|
||||
ProductTag,
|
||||
ProductType,
|
||||
ProductVariant,
|
||||
} from "@models"
|
||||
import { FindConfig, ProductTypes, SharedContext } from "@medusajs/types"
|
||||
import {
|
||||
Context,
|
||||
CreateProductOnlyDTO,
|
||||
DAL,
|
||||
FindConfig,
|
||||
InternalModuleDeclaration,
|
||||
ProductTypes,
|
||||
} from "@medusajs/types"
|
||||
import ProductImageService from "./product-image"
|
||||
import {
|
||||
InjectTransactionManager,
|
||||
isDefined,
|
||||
isString,
|
||||
kebabCase,
|
||||
MedusaContext,
|
||||
} from "@medusajs/utils"
|
||||
import { shouldForceTransaction } from "../utils"
|
||||
|
||||
type InjectedDependencies = {
|
||||
baseRepository: DAL.RepositoryService
|
||||
productService: ProductService<any>
|
||||
productVariantService: ProductVariantService<any>
|
||||
productVariantService: ProductVariantService<any, any>
|
||||
productTagService: ProductTagService<any>
|
||||
productCategoryService: ProductCategoryService<any>
|
||||
productCollectionService: ProductCollectionService<any>
|
||||
productImageService: ProductImageService<any>
|
||||
productTypeService: ProductTypeService<any>
|
||||
productOptionService: ProductOptionService<any>
|
||||
}
|
||||
|
||||
export default class ProductModuleService<
|
||||
TProduct = Product,
|
||||
TProductVariant = ProductVariant,
|
||||
TProductTag = ProductTag,
|
||||
TProductCollection = ProductCollection,
|
||||
TProductCategory = ProductCategory
|
||||
> implements
|
||||
ProductTypes.IProductModuleService<
|
||||
TProduct,
|
||||
TProductVariant,
|
||||
TProductTag,
|
||||
TProductCollection,
|
||||
TProductCategory
|
||||
>
|
||||
TProduct extends Product = Product,
|
||||
TProductVariant extends ProductVariant = ProductVariant,
|
||||
TProductTag extends ProductTag = ProductTag,
|
||||
TProductCollection extends ProductCollection = ProductCollection,
|
||||
TProductCategory extends ProductCategory = ProductCategory,
|
||||
TProductImage extends Image = Image,
|
||||
TProductType extends ProductType = ProductType,
|
||||
TProductOption extends ProductOption = ProductOption
|
||||
> implements ProductTypes.IProductModuleService
|
||||
{
|
||||
protected baseRepository_: DAL.RepositoryService
|
||||
protected readonly productService_: ProductService<TProduct>
|
||||
protected readonly productVariantService: ProductVariantService<TProductVariant>
|
||||
protected readonly productCategoryService: ProductCategoryService<TProductCategory>
|
||||
protected readonly productTagService: ProductTagService<TProductTag>
|
||||
protected readonly productCollectionService: ProductCollectionService<TProductCollection>
|
||||
protected readonly productVariantService_: ProductVariantService<
|
||||
TProductVariant,
|
||||
TProduct
|
||||
>
|
||||
protected readonly productCategoryService_: ProductCategoryService<TProductCategory>
|
||||
protected readonly productTagService_: ProductTagService<TProductTag>
|
||||
protected readonly productCollectionService_: ProductCollectionService<TProductCollection>
|
||||
protected readonly productImageService_: ProductImageService<TProductImage>
|
||||
protected readonly productTypeService_: ProductTypeService<TProductType>
|
||||
protected readonly productOptionService_: ProductOptionService<TProductOption>
|
||||
|
||||
constructor({
|
||||
productService,
|
||||
productVariantService,
|
||||
productTagService,
|
||||
productCategoryService,
|
||||
productCollectionService,
|
||||
}: InjectedDependencies) {
|
||||
constructor(
|
||||
{
|
||||
baseRepository,
|
||||
productService,
|
||||
productVariantService,
|
||||
productTagService,
|
||||
productCategoryService,
|
||||
productCollectionService,
|
||||
productImageService,
|
||||
productTypeService,
|
||||
productOptionService,
|
||||
}: InjectedDependencies,
|
||||
protected readonly moduleDeclaration: InternalModuleDeclaration
|
||||
) {
|
||||
this.baseRepository_ = baseRepository
|
||||
this.productService_ = productService
|
||||
this.productVariantService = productVariantService
|
||||
this.productTagService = productTagService
|
||||
this.productCategoryService = productCategoryService
|
||||
this.productCollectionService = productCollectionService
|
||||
this.productVariantService_ = productVariantService
|
||||
this.productTagService_ = productTagService
|
||||
this.productCategoryService_ = productCategoryService
|
||||
this.productCollectionService_ = productCollectionService
|
||||
this.productImageService_ = productImageService
|
||||
this.productTypeService_ = productTypeService
|
||||
this.productOptionService_ = productOptionService
|
||||
}
|
||||
|
||||
async list(
|
||||
filters: ProductTypes.FilterableProductProps = {},
|
||||
config: FindConfig<ProductTypes.ProductDTO> = {},
|
||||
sharedContext?: SharedContext
|
||||
sharedContext?: Context
|
||||
): Promise<ProductTypes.ProductDTO[]> {
|
||||
const products = await this.productService_.list(
|
||||
filters,
|
||||
@@ -71,10 +110,22 @@ export default class ProductModuleService<
|
||||
return JSON.parse(JSON.stringify(products))
|
||||
}
|
||||
|
||||
async retrieve(
|
||||
productId: string,
|
||||
sharedContext?: Context
|
||||
): Promise<ProductTypes.ProductDTO> {
|
||||
const product = await this.productService_.retrieve(
|
||||
productId,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
return JSON.parse(JSON.stringify(product))
|
||||
}
|
||||
|
||||
async listAndCount(
|
||||
filters: ProductTypes.FilterableProductProps = {},
|
||||
config: FindConfig<ProductTypes.ProductDTO> = {},
|
||||
sharedContext?: SharedContext
|
||||
sharedContext?: Context
|
||||
): Promise<[ProductTypes.ProductDTO[], number]> {
|
||||
const [products, count] = await this.productService_.listAndCount(
|
||||
filters,
|
||||
@@ -85,12 +136,26 @@ export default class ProductModuleService<
|
||||
return [JSON.parse(JSON.stringify(products)), count]
|
||||
}
|
||||
|
||||
async retrieveVariant(
|
||||
productVariantId: string,
|
||||
config: FindConfig<ProductTypes.ProductVariantDTO> = {},
|
||||
sharedContext?: Context
|
||||
): Promise<ProductTypes.ProductVariantDTO> {
|
||||
const productVariant = await this.productVariantService_.retrieve(
|
||||
productVariantId,
|
||||
config,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
return JSON.parse(JSON.stringify(productVariant))
|
||||
}
|
||||
|
||||
async listVariants(
|
||||
filters: ProductTypes.FilterableProductVariantProps = {},
|
||||
config: FindConfig<ProductTypes.ProductVariantDTO> = {},
|
||||
sharedContext?: SharedContext
|
||||
sharedContext?: Context
|
||||
): Promise<ProductTypes.ProductVariantDTO[]> {
|
||||
const variants = await this.productVariantService.list(
|
||||
const variants = await this.productVariantService_.list(
|
||||
filters,
|
||||
config,
|
||||
sharedContext
|
||||
@@ -99,12 +164,26 @@ export default class ProductModuleService<
|
||||
return JSON.parse(JSON.stringify(variants))
|
||||
}
|
||||
|
||||
async listAndCountVariants(
|
||||
filters: ProductTypes.FilterableProductVariantProps = {},
|
||||
config: FindConfig<ProductTypes.ProductVariantDTO> = {},
|
||||
sharedContext?: Context
|
||||
): Promise<[ProductTypes.ProductVariantDTO[], number]> {
|
||||
const [variants, count] = await this.productVariantService_.listAndCount(
|
||||
filters,
|
||||
config,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
return [JSON.parse(JSON.stringify(variants)), count]
|
||||
}
|
||||
|
||||
async listTags(
|
||||
filters: ProductTypes.FilterableProductTagProps = {},
|
||||
config: FindConfig<ProductTypes.ProductTagDTO> = {},
|
||||
sharedContext?: SharedContext
|
||||
sharedContext?: Context
|
||||
): Promise<ProductTypes.ProductTagDTO[]> {
|
||||
const tags = await this.productTagService.list(
|
||||
const tags = await this.productTagService_.list(
|
||||
filters,
|
||||
config,
|
||||
sharedContext
|
||||
@@ -113,12 +192,26 @@ export default class ProductModuleService<
|
||||
return JSON.parse(JSON.stringify(tags))
|
||||
}
|
||||
|
||||
async retrieveCollection(
|
||||
productCollectionId: string,
|
||||
config: FindConfig<ProductTypes.ProductCollectionDTO> = {},
|
||||
sharedContext?: Context
|
||||
): Promise<ProductTypes.ProductCollectionDTO> {
|
||||
const productCollection = await this.productCollectionService_.retrieve(
|
||||
productCollectionId,
|
||||
config,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
return JSON.parse(JSON.stringify(productCollection))
|
||||
}
|
||||
|
||||
async listCollections(
|
||||
filters: ProductTypes.FilterableProductCollectionProps = {},
|
||||
config: FindConfig<ProductTypes.ProductCollectionDTO> = {},
|
||||
sharedContext?: SharedContext
|
||||
sharedContext?: Context
|
||||
): Promise<ProductTypes.ProductCollectionDTO[]> {
|
||||
const collections = await this.productCollectionService.list(
|
||||
const collections = await this.productCollectionService_.list(
|
||||
filters,
|
||||
config,
|
||||
sharedContext
|
||||
@@ -127,12 +220,40 @@ export default class ProductModuleService<
|
||||
return JSON.parse(JSON.stringify(collections))
|
||||
}
|
||||
|
||||
async listAndCountCollections(
|
||||
filters: ProductTypes.FilterableProductCollectionProps = {},
|
||||
config: FindConfig<ProductTypes.ProductCollectionDTO> = {},
|
||||
sharedContext?: Context
|
||||
): Promise<[ProductTypes.ProductCollectionDTO[], number]> {
|
||||
const collections = await this.productCollectionService_.listAndCount(
|
||||
filters,
|
||||
config,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
return JSON.parse(JSON.stringify(collections))
|
||||
}
|
||||
|
||||
async retrieveCategory(
|
||||
productCategoryId: string,
|
||||
config: FindConfig<ProductTypes.ProductCategoryDTO> = {},
|
||||
sharedContext?: Context
|
||||
): Promise<ProductTypes.ProductCategoryDTO> {
|
||||
const productCategory = await this.productCategoryService_.retrieve(
|
||||
productCategoryId,
|
||||
config,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
return JSON.parse(JSON.stringify(productCategory))
|
||||
}
|
||||
|
||||
async listCategories(
|
||||
filters: ProductTypes.FilterableProductCategoryProps = {},
|
||||
config: FindConfig<ProductTypes.ProductCategoryDTO> = {},
|
||||
sharedContext?: SharedContext
|
||||
sharedContext?: Context
|
||||
): Promise<ProductTypes.ProductCategoryDTO[]> {
|
||||
const categories = await this.productCategoryService.list(
|
||||
const categories = await this.productCategoryService_.list(
|
||||
filters,
|
||||
config,
|
||||
sharedContext
|
||||
@@ -140,4 +261,199 @@ export default class ProductModuleService<
|
||||
|
||||
return JSON.parse(JSON.stringify(categories))
|
||||
}
|
||||
|
||||
async listAndCountCategories(
|
||||
filters: ProductTypes.FilterableProductCategoryProps = {},
|
||||
config: FindConfig<ProductTypes.ProductCategoryDTO> = {},
|
||||
sharedContext?: Context
|
||||
): Promise<[ProductTypes.ProductCategoryDTO[], number]> {
|
||||
const categories = await this.productCategoryService_.listAndCount(
|
||||
filters,
|
||||
config,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
return JSON.parse(JSON.stringify(categories))
|
||||
}
|
||||
|
||||
async create(data: ProductTypes.CreateProductDTO[], sharedContext?: Context) {
|
||||
const products = await this.create_(data, sharedContext)
|
||||
|
||||
return this.baseRepository_.serialize<
|
||||
TProduct[],
|
||||
ProductTypes.ProductDTO[]
|
||||
>(products, {
|
||||
populate: true,
|
||||
})
|
||||
}
|
||||
|
||||
@InjectTransactionManager(shouldForceTransaction, "baseRepository_")
|
||||
protected async create_(
|
||||
data: ProductTypes.CreateProductDTO[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<TProduct[]> {
|
||||
const productVariantsMap = new Map<
|
||||
string,
|
||||
ProductTypes.CreateProductVariantDTO[]
|
||||
>()
|
||||
const productOptionsMap = new Map<
|
||||
string,
|
||||
ProductTypes.CreateProductOptionDTO[]
|
||||
>()
|
||||
|
||||
const productsData = await Promise.all(
|
||||
data.map(async (product) => {
|
||||
const productData = { ...product }
|
||||
if (!productData.handle) {
|
||||
productData.handle = kebabCase(product.title)
|
||||
}
|
||||
|
||||
const variants = productData.variants
|
||||
const options = productData.options
|
||||
delete productData.options
|
||||
delete productData.variants
|
||||
|
||||
productVariantsMap.set(productData.handle!, variants ?? [])
|
||||
productOptionsMap.set(productData.handle!, options ?? [])
|
||||
|
||||
if (!productData.thumbnail && productData.images?.length) {
|
||||
productData.thumbnail = isString(productData.images[0])
|
||||
? (productData.images[0] as string)
|
||||
: (productData.images[0] as { url: string }).url
|
||||
}
|
||||
|
||||
if (productData.is_giftcard) {
|
||||
productData.discountable = false
|
||||
}
|
||||
|
||||
if (productData.images?.length) {
|
||||
productData.images = await this.productImageService_.upsert(
|
||||
productData.images.map((image) =>
|
||||
isString(image) ? image : image.url
|
||||
),
|
||||
sharedContext
|
||||
)
|
||||
}
|
||||
|
||||
if (productData.tags?.length) {
|
||||
productData.tags = await this.productTagService_.upsert(
|
||||
productData.tags,
|
||||
sharedContext
|
||||
)
|
||||
}
|
||||
|
||||
if (isDefined(productData.type)) {
|
||||
productData.type_id = (
|
||||
await this.productTypeService_.upsert(
|
||||
[productData.type as ProductTypes.CreateProductTypeDTO],
|
||||
sharedContext
|
||||
)
|
||||
)?.[0]!.id
|
||||
}
|
||||
|
||||
return productData as CreateProductOnlyDTO
|
||||
})
|
||||
)
|
||||
|
||||
const products = await this.productService_.create(
|
||||
productsData,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
const productByHandleMap = new Map<string, TProduct>(
|
||||
products.map((product) => [product.handle!, product])
|
||||
)
|
||||
|
||||
const productOptionsData = [...productOptionsMap]
|
||||
.map(([handle, options]) => {
|
||||
return options.map((option) => {
|
||||
return {
|
||||
...option,
|
||||
product: productByHandleMap.get(handle)!,
|
||||
}
|
||||
})
|
||||
})
|
||||
.flat()
|
||||
|
||||
const productOptions = await this.productOptionService_.create(
|
||||
productOptionsData,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
for (const variants of productVariantsMap.values()) {
|
||||
variants.forEach((variant) => {
|
||||
variant.options = variant.options?.map((option, index) => {
|
||||
const productOption = productOptions[index]
|
||||
return {
|
||||
option: productOption,
|
||||
value: option.value,
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
[...productVariantsMap].map(async ([handle, variants]) => {
|
||||
return await this.productVariantService_.create(
|
||||
productByHandleMap.get(handle)!,
|
||||
variants as unknown as ProductTypes.CreateProductVariantOnlyDTO[],
|
||||
sharedContext
|
||||
)
|
||||
})
|
||||
)
|
||||
|
||||
return products
|
||||
}
|
||||
|
||||
@InjectTransactionManager(shouldForceTransaction, "baseRepository_")
|
||||
async delete(
|
||||
productIds: string[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<void> {
|
||||
await this.productService_.delete(productIds, sharedContext)
|
||||
}
|
||||
|
||||
async softDelete(
|
||||
productIds: string[],
|
||||
sharedContext: Context = {}
|
||||
): Promise<ProductTypes.ProductDTO[]> {
|
||||
const products = await this.softDelete_(productIds, sharedContext)
|
||||
|
||||
return this.baseRepository_.serialize<
|
||||
TProduct[],
|
||||
ProductTypes.ProductDTO[]
|
||||
>(products, {
|
||||
populate: true,
|
||||
})
|
||||
}
|
||||
|
||||
@InjectTransactionManager(shouldForceTransaction, "baseRepository_")
|
||||
protected async softDelete_(
|
||||
productIds: string[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<TProduct[]> {
|
||||
return await this.productService_.softDelete(productIds, sharedContext)
|
||||
}
|
||||
|
||||
async restore(
|
||||
productIds: string[],
|
||||
sharedContext: Context = {}
|
||||
): Promise<ProductTypes.ProductDTO[]> {
|
||||
const products = await this.restore_(productIds, sharedContext)
|
||||
|
||||
return this.baseRepository_.serialize<
|
||||
TProduct[],
|
||||
ProductTypes.ProductDTO[]
|
||||
>(products, {
|
||||
populate: true,
|
||||
})
|
||||
}
|
||||
|
||||
@InjectTransactionManager(shouldForceTransaction, "baseRepository_")
|
||||
async restore_(
|
||||
productIds: string[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<TProduct[]> {
|
||||
return await this.productService_.restore(productIds, sharedContext)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import { ProductOption } from "@models"
|
||||
import { Context, DAL, ProductTypes } from "@medusajs/types"
|
||||
import { ProductOptionRepository } from "@repositories"
|
||||
import { InjectTransactionManager, MedusaContext } from "@medusajs/utils"
|
||||
import { doNotForceTransaction } from "../utils"
|
||||
|
||||
type InjectedDependencies = {
|
||||
productOptionRepository: DAL.RepositoryService
|
||||
}
|
||||
|
||||
export default class ProductOptionService<
|
||||
TEntity extends ProductOption = ProductOption
|
||||
> {
|
||||
protected readonly productOptionRepository_: DAL.RepositoryService
|
||||
|
||||
constructor({ productOptionRepository }: InjectedDependencies) {
|
||||
this.productOptionRepository_ =
|
||||
productOptionRepository as ProductOptionRepository
|
||||
}
|
||||
|
||||
@InjectTransactionManager(doNotForceTransaction, "productOptionRepository_")
|
||||
async create(
|
||||
data: ProductTypes.CreateProductOptionOnlyDTO[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<TEntity[]> {
|
||||
return (await (
|
||||
this.productOptionRepository_ as ProductOptionRepository
|
||||
).create(data, {
|
||||
transactionManager: sharedContext.transactionManager,
|
||||
})) as TEntity[]
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,27 @@
|
||||
import { ProductTag } from "@models"
|
||||
import { DAL, FindConfig, ProductTypes, SharedContext } from "@medusajs/types"
|
||||
import { buildQuery } from "../utils"
|
||||
import {
|
||||
Context,
|
||||
CreateProductTagDTO,
|
||||
DAL,
|
||||
FindConfig,
|
||||
ProductTypes,
|
||||
} from "@medusajs/types"
|
||||
import {
|
||||
InjectTransactionManager,
|
||||
MedusaContext,
|
||||
ModulesSdkUtils,
|
||||
} from "@medusajs/utils"
|
||||
import { doNotForceTransaction } from "../utils"
|
||||
import { ProductTagRepository } from "@repositories"
|
||||
|
||||
type InjectedDependencies = {
|
||||
productTagRepository: DAL.RepositoryService
|
||||
}
|
||||
|
||||
export default class ProductTagService<TEntity = ProductTag> {
|
||||
protected readonly productTagRepository_: DAL.RepositoryService<TEntity>
|
||||
export default class ProductTagService<
|
||||
TEntity extends ProductTag = ProductTag
|
||||
> {
|
||||
protected readonly productTagRepository_: DAL.RepositoryService
|
||||
|
||||
constructor({ productTagRepository }: InjectedDependencies) {
|
||||
this.productTagRepository_ = productTagRepository
|
||||
@@ -16,14 +30,28 @@ export default class ProductTagService<TEntity = ProductTag> {
|
||||
async list(
|
||||
filters: ProductTypes.FilterableProductTagProps = {},
|
||||
config: FindConfig<ProductTypes.ProductTagDTO> = {},
|
||||
sharedContext?: SharedContext
|
||||
sharedContext?: Context
|
||||
): Promise<TEntity[]> {
|
||||
const queryOptions = buildQuery<TEntity>(filters, config)
|
||||
const queryOptions = ModulesSdkUtils.buildQuery<ProductTag>(filters, config)
|
||||
|
||||
if (filters.value) {
|
||||
queryOptions.where["value"] = { $ilike: filters.value }
|
||||
}
|
||||
|
||||
return await this.productTagRepository_.find(queryOptions)
|
||||
return (await this.productTagRepository_.find(
|
||||
queryOptions,
|
||||
sharedContext
|
||||
)) as TEntity[]
|
||||
}
|
||||
|
||||
@InjectTransactionManager(doNotForceTransaction, "productTagRepository_")
|
||||
async upsert(
|
||||
tags: CreateProductTagDTO[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<TEntity[]> {
|
||||
return (await (this.productTagRepository_ as ProductTagRepository).upsert!(
|
||||
tags,
|
||||
sharedContext
|
||||
)) as TEntity[]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { ProductType } from "@models"
|
||||
import { Context, CreateProductTypeDTO, DAL } from "@medusajs/types"
|
||||
import { InjectTransactionManager, MedusaContext } from "@medusajs/utils"
|
||||
import { doNotForceTransaction } from "../utils"
|
||||
import { ProductTypeRepository } from "@repositories"
|
||||
|
||||
type InjectedDependencies = {
|
||||
productTypeRepository: DAL.RepositoryService
|
||||
}
|
||||
|
||||
export default class ProductTypeService<
|
||||
TEntity extends ProductType = ProductType
|
||||
> {
|
||||
protected readonly productTypeRepository_: DAL.RepositoryService
|
||||
|
||||
constructor({ productTypeRepository }: InjectedDependencies) {
|
||||
this.productTypeRepository_ = productTypeRepository
|
||||
}
|
||||
|
||||
@InjectTransactionManager(doNotForceTransaction, "productTypeRepository_")
|
||||
async upsert(
|
||||
types: CreateProductTypeDTO[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<TEntity[]> {
|
||||
return (await (this.productTypeRepository_ as ProductTypeRepository)
|
||||
.upsert!(types, sharedContext)) as TEntity[]
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,115 @@
|
||||
import { ProductVariant } from "@models"
|
||||
import { DAL, FindConfig, ProductTypes, SharedContext } from "@medusajs/types"
|
||||
import { buildQuery } from "../utils"
|
||||
import { Product, ProductVariant } from "@models"
|
||||
import { Context, DAL, FindConfig, ProductTypes } from "@medusajs/types"
|
||||
import {
|
||||
InjectTransactionManager,
|
||||
isString,
|
||||
MedusaContext,
|
||||
ModulesSdkUtils,
|
||||
retrieveEntity,
|
||||
} from "@medusajs/utils"
|
||||
|
||||
import ProductService from "./product"
|
||||
import { doNotForceTransaction } from "../utils"
|
||||
import { ProductVariantRepository } from "@repositories"
|
||||
|
||||
type InjectedDependencies = {
|
||||
productVariantRepository: DAL.RepositoryService
|
||||
productService: ProductService<any>
|
||||
}
|
||||
|
||||
export default class ProductVariantService<TEntity = ProductVariant> {
|
||||
protected readonly productVariantRepository_: DAL.RepositoryService<TEntity>
|
||||
export default class ProductVariantService<
|
||||
TEntity extends ProductVariant = ProductVariant,
|
||||
TProduct extends Product = Product
|
||||
> {
|
||||
protected readonly productVariantRepository_: DAL.RepositoryService
|
||||
protected readonly productService_: ProductService<TProduct>
|
||||
|
||||
constructor({ productVariantRepository }: InjectedDependencies) {
|
||||
constructor({
|
||||
productVariantRepository,
|
||||
productService,
|
||||
}: InjectedDependencies) {
|
||||
this.productVariantRepository_ = productVariantRepository
|
||||
this.productService_ = productService
|
||||
}
|
||||
|
||||
async retrieve(
|
||||
productVariantId: string,
|
||||
config: FindConfig<ProductTypes.ProductVariantDTO> = {},
|
||||
sharedContext?: Context
|
||||
): Promise<TEntity> {
|
||||
return (await retrieveEntity<
|
||||
ProductVariant,
|
||||
ProductTypes.ProductVariantDTO
|
||||
>({
|
||||
id: productVariantId,
|
||||
entityName: ProductVariant.name,
|
||||
repository: this.productVariantRepository_,
|
||||
config,
|
||||
sharedContext,
|
||||
})) as TEntity
|
||||
}
|
||||
|
||||
async list(
|
||||
filters: ProductTypes.FilterableProductVariantProps = {},
|
||||
config: FindConfig<ProductTypes.ProductVariantDTO> = {},
|
||||
sharedContext?: SharedContext
|
||||
sharedContext?: Context
|
||||
): Promise<TEntity[]> {
|
||||
const queryOptions = buildQuery<TEntity>(filters, config)
|
||||
return await this.productVariantRepository_.find(queryOptions)
|
||||
const queryOptions = ModulesSdkUtils.buildQuery<ProductVariant>(
|
||||
filters,
|
||||
config
|
||||
)
|
||||
|
||||
return (await this.productVariantRepository_.find(
|
||||
queryOptions,
|
||||
sharedContext
|
||||
)) as TEntity[]
|
||||
}
|
||||
|
||||
async listAndCount(
|
||||
filters: ProductTypes.FilterableProductVariantProps = {},
|
||||
config: FindConfig<ProductTypes.ProductVariantDTO> = {},
|
||||
sharedContext?: Context
|
||||
): Promise<[TEntity[], number]> {
|
||||
const queryOptions = ModulesSdkUtils.buildQuery<ProductVariant>(
|
||||
filters,
|
||||
config
|
||||
)
|
||||
|
||||
return (await this.productVariantRepository_.findAndCount(
|
||||
queryOptions,
|
||||
sharedContext
|
||||
)) as [TEntity[], number]
|
||||
}
|
||||
|
||||
@InjectTransactionManager(doNotForceTransaction, "productVariantRepository_")
|
||||
async create(
|
||||
productOrId: TProduct | string,
|
||||
data: ProductTypes.CreateProductVariantOnlyDTO[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<TEntity[]> {
|
||||
let product = productOrId as unknown as Product
|
||||
|
||||
if (isString(productOrId)) {
|
||||
product = await this.productService_.retrieve(
|
||||
productOrId as string,
|
||||
sharedContext
|
||||
)
|
||||
}
|
||||
|
||||
let computedRank = product.variants.toArray().length
|
||||
|
||||
const data_ = [...data]
|
||||
data_.forEach((variant) => {
|
||||
Object.assign(variant, {
|
||||
variant_rank: computedRank++,
|
||||
product,
|
||||
})
|
||||
})
|
||||
|
||||
return (await (
|
||||
this.productVariantRepository_ as ProductVariantRepository
|
||||
).create(data_, {
|
||||
transactionManager: sharedContext.transactionManager,
|
||||
})) as TEntity[]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,55 @@
|
||||
import { ProductTagService, ProductVariantService } from "@services"
|
||||
import { Product } from "@models"
|
||||
import { DAL, FindConfig, ProductTypes, SharedContext } from "@medusajs/types"
|
||||
import { buildQuery } from "../utils"
|
||||
import {
|
||||
Context,
|
||||
DAL,
|
||||
FindConfig,
|
||||
ProductStatus,
|
||||
ProductTypes,
|
||||
WithRequiredProperty,
|
||||
} from "@medusajs/types"
|
||||
import {
|
||||
InjectTransactionManager,
|
||||
MedusaContext,
|
||||
MedusaError,
|
||||
ModulesSdkUtils,
|
||||
} from "@medusajs/utils"
|
||||
import { ProductRepository } from "@repositories"
|
||||
import { doNotForceTransaction } from "../utils"
|
||||
|
||||
type InjectedDependencies = {
|
||||
productRepository: DAL.RepositoryService
|
||||
productVariantService: ProductVariantService
|
||||
productTagService: ProductTagService
|
||||
}
|
||||
|
||||
export default class ProductService<TEntity = Product> {
|
||||
protected readonly productRepository_: DAL.RepositoryService<TEntity>
|
||||
export default class ProductService<TEntity extends Product = Product> {
|
||||
protected readonly productRepository_: DAL.RepositoryService
|
||||
|
||||
constructor({ productRepository }: InjectedDependencies) {
|
||||
this.productRepository_ = productRepository
|
||||
}
|
||||
|
||||
async retrieve(productId: string, sharedContext?: Context): Promise<TEntity> {
|
||||
const queryOptions = ModulesSdkUtils.buildQuery<Product>({
|
||||
id: productId,
|
||||
})
|
||||
const product = await this.productRepository_.find(
|
||||
queryOptions,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
if (!product?.length) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`Product with id: ${productId} was not found`
|
||||
)
|
||||
}
|
||||
|
||||
return product[0] as TEntity
|
||||
}
|
||||
|
||||
async list(
|
||||
filters: ProductTypes.FilterableProductProps = {},
|
||||
config: FindConfig<ProductTypes.ProductDTO> = {},
|
||||
sharedContext?: SharedContext
|
||||
sharedContext?: Context
|
||||
): Promise<TEntity[]> {
|
||||
if (filters.category_ids) {
|
||||
if (Array.isArray(filters.category_ids)) {
|
||||
@@ -34,14 +64,17 @@ export default class ProductService<TEntity = Product> {
|
||||
delete filters.category_ids
|
||||
}
|
||||
|
||||
const queryOptions = buildQuery<TEntity>(filters, config)
|
||||
return await this.productRepository_.find(queryOptions)
|
||||
const queryOptions = ModulesSdkUtils.buildQuery<Product>(filters, config)
|
||||
return (await this.productRepository_.find(
|
||||
queryOptions,
|
||||
sharedContext
|
||||
)) as TEntity[]
|
||||
}
|
||||
|
||||
async listAndCount(
|
||||
filters: ProductTypes.FilterableProductProps = {},
|
||||
config: FindConfig<ProductTypes.ProductDTO> = {},
|
||||
sharedContext?: SharedContext
|
||||
sharedContext?: Context
|
||||
): Promise<[TEntity[], number]> {
|
||||
if (filters.category_ids) {
|
||||
if (Array.isArray(filters.category_ids)) {
|
||||
@@ -56,7 +89,60 @@ export default class ProductService<TEntity = Product> {
|
||||
delete filters.category_ids
|
||||
}
|
||||
|
||||
const queryOptions = buildQuery<TEntity>(filters, config)
|
||||
return await this.productRepository_.findAndCount(queryOptions)
|
||||
const queryOptions = ModulesSdkUtils.buildQuery<Product>(filters, config)
|
||||
return (await this.productRepository_.findAndCount(
|
||||
queryOptions,
|
||||
sharedContext
|
||||
)) as [TEntity[], number]
|
||||
}
|
||||
|
||||
@InjectTransactionManager(doNotForceTransaction, "productRepository_")
|
||||
async create(
|
||||
data: ProductTypes.CreateProductOnlyDTO[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<TEntity[]> {
|
||||
data.forEach((product) => {
|
||||
product.status ??= ProductStatus.DRAFT
|
||||
})
|
||||
|
||||
return (await (this.productRepository_ as ProductRepository).create(
|
||||
data as WithRequiredProperty<
|
||||
ProductTypes.CreateProductOnlyDTO,
|
||||
"status"
|
||||
>[],
|
||||
{
|
||||
transactionManager: sharedContext.transactionManager,
|
||||
}
|
||||
)) as TEntity[]
|
||||
}
|
||||
|
||||
@InjectTransactionManager(doNotForceTransaction, "productRepository_")
|
||||
async delete(
|
||||
ids: string[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<void> {
|
||||
await this.productRepository_.delete(ids, {
|
||||
transactionManager: sharedContext.transactionManager,
|
||||
})
|
||||
}
|
||||
|
||||
@InjectTransactionManager(doNotForceTransaction, "productRepository_")
|
||||
async softDelete(
|
||||
productIds: string[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<TEntity[]> {
|
||||
return await this.productRepository_.softDelete(productIds, {
|
||||
transactionManager: sharedContext.transactionManager,
|
||||
})
|
||||
}
|
||||
|
||||
@InjectTransactionManager(doNotForceTransaction, "productRepository_")
|
||||
async restore(
|
||||
productIds: string[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<TEntity[]> {
|
||||
return await this.productRepository_.restore(productIds, {
|
||||
transactionManager: sharedContext.transactionManager,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user