feat(medusa, link-modules): sales channel <> product module link (#5450)
* feat: sales channel joiner config * feat: product sales channel link config, SC list method * feat: migration * fix: refactor list SC * refactor: SC repo api * chore: changeset * feat: add dedicated FF * feat: product<> sc join entity * fix: update case * fix: add FF on in the repository, fix tests * fix: assign id when FF is on * fix: target table * feat: product service - fetch SC with RQ * feat: admin list products & SC with isolated product domain * feat: get admin product * feat: store endpoints * fix: remove duplicate import * fix: remove "name" prop * feat: refactor * fix: product seeder if FF is on * fix: env * refactor: workflow product handlers to handle remote links * fix: condition * fix: use correct method * fix: build * wip: update FF * fix: update FF in the handlers * chore: migrate to medusav2 FF * chore: uncomment test * fix: product factory * fix: unlinking SC and product * fix: use module name variable * refactor: cleanup query definitions * fix: add constraint * chore: rename prop * fix: add hook * fix: address comments * fix: temp sc filtering * fix: use RQ to filter by SC * fix: add sc to filter to list --------- Co-authored-by: Riqwan Thamir <rmthamir@gmail.com>
This commit is contained in:
6
.changeset/chilled-deers-prove.md
Normal file
6
.changeset/chilled-deers-prove.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"@medusajs/link-modules": patch
|
||||
"@medusajs/medusa": patch
|
||||
---
|
||||
|
||||
feat(medusa, link-module): SalesChannel<>Product joiner config
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
|
||||
import { DataSource } from "typeorm"
|
||||
import faker from "faker"
|
||||
import { generateEntityId } from "@medusajs/utils"
|
||||
|
||||
export type ProductFactoryData = {
|
||||
id?: string
|
||||
@@ -30,6 +31,7 @@ export type ProductFactoryData = {
|
||||
variants?: Omit<ProductVariantFactoryData, "product_id">[]
|
||||
sales_channels?: SalesChannelFactoryData[]
|
||||
metadata?: Record<string, unknown>
|
||||
isMedusaV2Enabled?: boolean
|
||||
}
|
||||
|
||||
export const simpleProductFactory = async (
|
||||
@@ -41,6 +43,9 @@ export const simpleProductFactory = async (
|
||||
faker.seed(seed)
|
||||
}
|
||||
|
||||
data.isMedusaV2Enabled =
|
||||
data.isMedusaV2Enabled ?? process.env.MEDUSA_FF_MEDUSA_V2 == "true"
|
||||
|
||||
const manager = dataSource.manager
|
||||
|
||||
const defaultProfile = await manager.findOne(ShippingProfile, {
|
||||
@@ -121,10 +126,27 @@ export const simpleProductFactory = async (
|
||||
|
||||
const toSave = manager.create(Product, productToCreate)
|
||||
|
||||
toSave.sales_channels = sales_channels
|
||||
if (!data.isMedusaV2Enabled) {
|
||||
toSave.sales_channels = sales_channels
|
||||
}
|
||||
|
||||
const product = await manager.save(toSave)
|
||||
|
||||
if (data.isMedusaV2Enabled) {
|
||||
await manager.query(
|
||||
`INSERT INTO "product_sales_channel" (id, product_id, sales_channel_id)
|
||||
VALUES ${sales_channels
|
||||
.map(
|
||||
(sc) =>
|
||||
`('${generateEntityId(undefined, "prodsc")}', '${toSave.id}', '${
|
||||
sc.id
|
||||
}')`
|
||||
)
|
||||
.join(", ")};
|
||||
`
|
||||
)
|
||||
}
|
||||
|
||||
const optionId = `${prodId}-option`
|
||||
const options = data.options || [{ id: optionId, title: "Size" }]
|
||||
for (const o of options) {
|
||||
|
||||
@@ -572,7 +572,7 @@ describe("/admin/products", () => {
|
||||
|
||||
const response = await api
|
||||
.post(
|
||||
`/admin/products/${toUpdateWithSalesChannels}`,
|
||||
`/admin/products/${toUpdateWithSalesChannels}?expand=sales_channels`,
|
||||
payload,
|
||||
adminHeaders
|
||||
)
|
||||
@@ -584,11 +584,10 @@ describe("/admin/products", () => {
|
||||
expect(response?.data.product).toEqual(
|
||||
expect.objectContaining({
|
||||
id: toUpdateWithSalesChannels,
|
||||
// TODO: Introduce this in the sale channel PR
|
||||
// sales_channels: [
|
||||
// expect.objectContaining({ id: "channel-2" }),
|
||||
// expect.objectContaining({ id: "channel-3" }),
|
||||
// ],
|
||||
sales_channels: [
|
||||
expect.objectContaining({ id: "channel-2" }),
|
||||
expect.objectContaining({ id: "channel-3" }),
|
||||
],
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { WorkflowArguments } from "@medusajs/workflows-sdk"
|
||||
import { promiseAll } from "@medusajs/utils"
|
||||
import { MedusaV2Flag, promiseAll } from "@medusajs/utils"
|
||||
import { Modules } from "@medusajs/modules-sdk"
|
||||
|
||||
type ProductHandle = string
|
||||
type SalesChannelId = string
|
||||
@@ -17,6 +18,8 @@ export async function attachSalesChannelToProducts({
|
||||
data,
|
||||
}: WorkflowArguments<HandlerInput>): Promise<void> {
|
||||
const { manager } = context
|
||||
const featureFlagRouter = container.resolve("featureFlagRouter")
|
||||
|
||||
const productsHandleSalesChannelsMap = data.productsHandleSalesChannelsMap
|
||||
const products = data.products
|
||||
|
||||
@@ -35,16 +38,41 @@ export async function attachSalesChannelToProducts({
|
||||
}
|
||||
})
|
||||
|
||||
await promiseAll(
|
||||
Array.from(salesChannelIdProductIdsMap.entries()).map(
|
||||
async ([salesChannelId, productIds]) => {
|
||||
return await salesChannelServiceTx.addProducts(
|
||||
salesChannelId,
|
||||
productIds
|
||||
)
|
||||
}
|
||||
if (featureFlagRouter.isFeatureEnabled(MedusaV2Flag.key)) {
|
||||
const remoteLink = container.resolve("remoteLink")
|
||||
const links: any[] = []
|
||||
|
||||
for (const [
|
||||
salesChannelId,
|
||||
productIds,
|
||||
] of salesChannelIdProductIdsMap.entries()) {
|
||||
productIds.forEach((id) =>
|
||||
links.push({
|
||||
[Modules.PRODUCT]: {
|
||||
product_id: id,
|
||||
},
|
||||
salesChannelService: {
|
||||
sales_channel_id: salesChannelId,
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
await remoteLink.create(links)
|
||||
}
|
||||
|
||||
return
|
||||
} else {
|
||||
await promiseAll(
|
||||
Array.from(salesChannelIdProductIdsMap.entries()).map(
|
||||
async ([salesChannelId, productIds]) => {
|
||||
return await salesChannelServiceTx.addProducts(
|
||||
salesChannelId,
|
||||
productIds
|
||||
)
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
attachSalesChannelToProducts.aliases = {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { WorkflowArguments } from "@medusajs/workflows-sdk"
|
||||
import { promiseAll } from "@medusajs/utils"
|
||||
import { MedusaV2Flag, promiseAll } from "@medusajs/utils"
|
||||
import { Modules } from "@medusajs/modules-sdk"
|
||||
|
||||
type ProductHandle = string
|
||||
type SalesChannelId = string
|
||||
@@ -15,6 +16,8 @@ export async function detachSalesChannelFromProducts({
|
||||
data,
|
||||
}: WorkflowArguments<HandlerInput>): Promise<void> {
|
||||
const { manager } = context
|
||||
const featureFlagRouter = container.resolve("featureFlagRouter")
|
||||
|
||||
const productsHandleSalesChannelsMap = data.productsHandleSalesChannelsMap
|
||||
const products = data.products
|
||||
|
||||
@@ -33,16 +36,41 @@ export async function detachSalesChannelFromProducts({
|
||||
}
|
||||
})
|
||||
|
||||
await promiseAll(
|
||||
Array.from(salesChannelIdProductIdsMap.entries()).map(
|
||||
async ([salesChannelId, productIds]) => {
|
||||
return await salesChannelServiceTx.removeProducts(
|
||||
salesChannelId,
|
||||
productIds
|
||||
if (featureFlagRouter.isFeatureEnabled(MedusaV2Flag.key)) {
|
||||
const remoteLink = container.resolve("remoteLink")
|
||||
const promises: Promise<unknown>[] = []
|
||||
|
||||
for (const [
|
||||
salesChannelId,
|
||||
productIds,
|
||||
] of salesChannelIdProductIdsMap.entries()) {
|
||||
productIds.forEach((id) =>
|
||||
promises.push(
|
||||
remoteLink.dismiss({
|
||||
[Modules.PRODUCT]: {
|
||||
product_id: id,
|
||||
},
|
||||
salesChannelService: {
|
||||
sales_channel_id: salesChannelId,
|
||||
},
|
||||
})
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
return
|
||||
} else {
|
||||
await promiseAll(
|
||||
Array.from(salesChannelIdProductIdsMap.entries()).map(
|
||||
async ([salesChannelId, productIds]) => {
|
||||
return await salesChannelServiceTx.removeProducts(
|
||||
salesChannelId,
|
||||
productIds
|
||||
)
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
detachSalesChannelFromProducts.aliases = {
|
||||
|
||||
@@ -2,3 +2,4 @@ export * from "./inventory-level-stock-location"
|
||||
export * from "./product-variant-inventory-item"
|
||||
export * from "./product-variant-price-set"
|
||||
export * from "./product-shipping-profile"
|
||||
export * from "./product-sales-channel"
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import { Modules } from "@medusajs/modules-sdk"
|
||||
import { ModuleJoinerConfig } from "@medusajs/types"
|
||||
import { LINKS } from "../links"
|
||||
|
||||
export const ProductSalesChannel: ModuleJoinerConfig = {
|
||||
serviceName: LINKS.ProductSalesChannel,
|
||||
isLink: true,
|
||||
databaseConfig: {
|
||||
tableName: "product_sales_channel",
|
||||
idPrefix: "prodsc",
|
||||
},
|
||||
alias: [
|
||||
{
|
||||
name: "product_sales_channel",
|
||||
},
|
||||
{
|
||||
name: "product_sales_channels",
|
||||
},
|
||||
],
|
||||
primaryKeys: ["id", "product_id", "sales_channel_id"],
|
||||
relationships: [
|
||||
{
|
||||
serviceName: Modules.PRODUCT,
|
||||
primaryKey: "id",
|
||||
foreignKey: "product_id",
|
||||
alias: "product",
|
||||
},
|
||||
{
|
||||
serviceName: "salesChannelService",
|
||||
isInternalService: true,
|
||||
primaryKey: "id",
|
||||
foreignKey: "sales_channel_id",
|
||||
alias: "sales_channel",
|
||||
},
|
||||
],
|
||||
extends: [
|
||||
{
|
||||
serviceName: Modules.PRODUCT,
|
||||
fieldAlias: {
|
||||
sales_channels: "sales_channels_link.sales_channel",
|
||||
},
|
||||
relationship: {
|
||||
serviceName: LINKS.ProductSalesChannel,
|
||||
primaryKey: "product_id",
|
||||
foreignKey: "id",
|
||||
alias: "sales_channels_link",
|
||||
isList: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
serviceName: "salesChannelService",
|
||||
relationship: {
|
||||
serviceName: LINKS.ProductSalesChannel,
|
||||
isInternalService: true,
|
||||
primaryKey: "sales_channel_id",
|
||||
foreignKey: "id",
|
||||
alias: "products_link",
|
||||
isList: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -22,4 +22,10 @@ export const LINKS = {
|
||||
"shippingProfileService",
|
||||
"profile_id"
|
||||
),
|
||||
ProductSalesChannel: composeLinkName(
|
||||
Modules.PRODUCT,
|
||||
"product_id",
|
||||
"salesChannelService",
|
||||
"sales_channel_id"
|
||||
),
|
||||
}
|
||||
|
||||
@@ -68,15 +68,13 @@ export default async (req, res) => {
|
||||
const productService: ProductService = req.scope.resolve("productService")
|
||||
const pricingService: PricingService = req.scope.resolve("pricingService")
|
||||
const featureFlagRouter = req.scope.resolve("featureFlagRouter")
|
||||
const isMedusaV2FlagOn = featureFlagRouter.isFeatureEnabled(MedusaV2Flag.key)
|
||||
|
||||
const productVariantInventoryService: ProductVariantInventoryService =
|
||||
req.scope.resolve("productVariantInventoryService")
|
||||
const salesChannelService: SalesChannelService = req.scope.resolve(
|
||||
"salesChannelService"
|
||||
)
|
||||
|
||||
let rawProduct
|
||||
if (featureFlagRouter.isFeatureEnabled(MedusaV2Flag.key)) {
|
||||
if (isMedusaV2FlagOn) {
|
||||
rawProduct = await retrieveProduct(
|
||||
req.scope,
|
||||
id,
|
||||
@@ -102,15 +100,30 @@ export default async (req, res) => {
|
||||
req.retrieveConfig.relations?.includes("variants")
|
||||
|
||||
if (shouldSetAvailability) {
|
||||
const [salesChannelsIds] = await salesChannelService.listAndCount(
|
||||
{},
|
||||
{ select: ["id"] }
|
||||
)
|
||||
let salesChannels
|
||||
|
||||
if (isMedusaV2FlagOn) {
|
||||
const remoteQuery = req.scope.resolve("remoteQuery")
|
||||
const query = {
|
||||
sales_channel: {
|
||||
fields: ["id"],
|
||||
},
|
||||
}
|
||||
salesChannels = await remoteQuery(query)
|
||||
} else {
|
||||
const salesChannelService: SalesChannelService = req.scope.resolve(
|
||||
"salesChannelService"
|
||||
)
|
||||
;[salesChannels] = await salesChannelService.listAndCount(
|
||||
{},
|
||||
{ select: ["id"] }
|
||||
)
|
||||
}
|
||||
|
||||
decoratePromises.push(
|
||||
productVariantInventoryService.setProductAvailability(
|
||||
[product],
|
||||
salesChannelsIds.map((salesChannel) => salesChannel.id)
|
||||
salesChannels.map((salesChannel) => salesChannel.id)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -238,6 +238,18 @@ export const defaultAdminProductRemoteQueryObject = {
|
||||
profile: {
|
||||
fields: ["id", "created_at", "updated_at", "deleted_at", "name", "type"],
|
||||
},
|
||||
sales_channels: {
|
||||
fields: [
|
||||
"id",
|
||||
"name",
|
||||
"description",
|
||||
"is_disabled",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"deleted_at",
|
||||
"metadata",
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -198,6 +198,18 @@ export const defaultStoreProductRemoteQueryObject = {
|
||||
profile: {
|
||||
fields: ["id", "created_at", "updated_at", "deleted_at", "name", "type"],
|
||||
},
|
||||
sales_channels: {
|
||||
fields: [
|
||||
"id",
|
||||
"name",
|
||||
"description",
|
||||
"is_disabled",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"deleted_at",
|
||||
"metadata",
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
export * from "./list-products"
|
||||
|
||||
@@ -399,6 +399,10 @@ async function listAndCountProductWithIsolatedProductModule(
|
||||
},
|
||||
}
|
||||
|
||||
if (salesChannelIdFilter) {
|
||||
query.product["sales_channels"]["__args"] = { id: salesChannelIdFilter }
|
||||
}
|
||||
|
||||
const {
|
||||
rows: products,
|
||||
metadata: { count },
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export * as cart from "./cart-service"
|
||||
export * as customer from "./customer-service"
|
||||
export * as region from "./region-service"
|
||||
export * as salesChannel from "./sales-channel-service"
|
||||
export * as shippingProfile from "./shipping-profile-service"
|
||||
|
||||
32
packages/medusa/src/joiner-configs/sales-channel-service.ts
Normal file
32
packages/medusa/src/joiner-configs/sales-channel-service.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { ModuleJoinerConfig } from "@medusajs/types"
|
||||
|
||||
export default {
|
||||
serviceName: "salesChannelService",
|
||||
primaryKeys: ["id"],
|
||||
linkableKeys: { sales_channel_id: "SalesChannel" },
|
||||
schema: `
|
||||
scalar Date
|
||||
scalar JSON
|
||||
|
||||
type SalesChannel {
|
||||
id: ID!
|
||||
name: String!
|
||||
description: String!
|
||||
is_disabled: Boolean
|
||||
created_at: Date!
|
||||
updated_at: Date!
|
||||
deleted_at: Date
|
||||
metadata: JSON
|
||||
}
|
||||
`,
|
||||
alias: [
|
||||
{
|
||||
name: "sales_channel",
|
||||
args: { entity: "SalesChannel" },
|
||||
},
|
||||
{
|
||||
name: "sales_channels",
|
||||
args: { entity: "SalesChannel" },
|
||||
},
|
||||
],
|
||||
} as ModuleJoinerConfig
|
||||
@@ -0,0 +1,38 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm"
|
||||
import { MedusaV2Flag } from "@medusajs/utils"
|
||||
|
||||
export const featureFlag = MedusaV2Flag.key
|
||||
|
||||
export class ProductSalesChannelsLink1698056997411
|
||||
implements MigrationInterface
|
||||
{
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE "product_sales_channel" ADD COLUMN IF NOT EXISTS "id" text;
|
||||
UPDATE "product_sales_channel" SET "id" = 'prodsc_' || substr(md5(random()::text), 0, 27) WHERE id is NULL;
|
||||
ALTER TABLE "product_sales_channel" ALTER COLUMN "id" SET NOT NULL;
|
||||
|
||||
ALTER TABLE "product_sales_channel" DROP CONSTRAINT IF EXISTS "PK_fd29b6a8bd641052628dee19583";
|
||||
ALTER TABLE "product_sales_channel" ADD CONSTRAINT "product_sales_channel_pk" PRIMARY KEY (id);
|
||||
ALTER TABLE "product_sales_channel" ADD CONSTRAINT "product_sales_channel_product_id_sales_channel_id_unique" UNIQUE (product_id, sales_channel_id);
|
||||
|
||||
ALTER TABLE "product_sales_channel" ADD COLUMN IF NOT EXISTS "created_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now();
|
||||
ALTER TABLE "product_sales_channel" ADD COLUMN IF NOT EXISTS "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now();
|
||||
ALTER TABLE "product_sales_channel" ADD COLUMN IF NOT EXISTS "deleted_at" TIMESTAMP WITH TIME ZONE;
|
||||
`)
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE product_sales_channel DROP CONSTRAINT IF EXISTS "product_sales_channel_pk";
|
||||
ALTER TABLE product_sales_channel DROP CONSTRAINT IF EXISTS "product_sales_channel_product_id_sales_channel_id_unique";
|
||||
ALTER TABLE product_sales_channel drop column if exists "id";
|
||||
|
||||
ALTER TABLE "product_sales_channel" DROP COLUMN IF EXISTS "created_at";
|
||||
ALTER TABLE "product_sales_channel" DROP COLUMN IF EXISTS "updated_at";
|
||||
ALTER TABLE "product_sales_channel" DROP COLUMN IF EXISTS "deleted_at";
|
||||
|
||||
ALTER TABLE product_sales_channel ADD CONSTRAINT "PK_product_sales_channel" PRIMARY KEY (product_id, sales_channel_id);
|
||||
`)
|
||||
}
|
||||
}
|
||||
20
packages/medusa/src/models/product-sales-channel.ts
Normal file
20
packages/medusa/src/models/product-sales-channel.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { BeforeInsert, Column, Entity } from "typeorm"
|
||||
import { BaseEntity } from "../interfaces"
|
||||
import { generateEntityId } from "../utils"
|
||||
|
||||
@Entity("product_sales_channel")
|
||||
export class ProductSalesChannel extends BaseEntity {
|
||||
@Column({ type: "text" })
|
||||
sales_channel_id: string
|
||||
|
||||
@Column({ type: "text" })
|
||||
product_id: string
|
||||
|
||||
/**
|
||||
* @apiIgnore
|
||||
*/
|
||||
@BeforeInsert()
|
||||
private beforeInsert(): void {
|
||||
this.id = generateEntityId(this.id, "prodsc")
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
import { BeforeInsert, Column, OneToMany } from "typeorm"
|
||||
import { BeforeInsert, Column, JoinTable, ManyToMany, OneToMany } from "typeorm"
|
||||
|
||||
import { FeatureFlagEntity } from "../utils/feature-flag-decorators"
|
||||
import { SoftDeletableEntity } from "../interfaces"
|
||||
import { DbAwareColumn, generateEntityId } from "../utils"
|
||||
import { SalesChannelLocation } from "./sales-channel-location"
|
||||
import { Product } from "./product"
|
||||
|
||||
@FeatureFlagEntity("sales_channels")
|
||||
export class SalesChannel extends SoftDeletableEntity {
|
||||
@@ -19,6 +20,20 @@ export class SalesChannel extends SoftDeletableEntity {
|
||||
@DbAwareColumn({ type: "jsonb", nullable: true })
|
||||
metadata: Record<string, unknown> | null
|
||||
|
||||
@ManyToMany(() => Product)
|
||||
@JoinTable({
|
||||
name: "product_sales_channel",
|
||||
inverseJoinColumn: {
|
||||
name: "product_id",
|
||||
referencedColumnName: "id",
|
||||
},
|
||||
joinColumn: {
|
||||
name: "sales_channel_id",
|
||||
referencedColumnName: "id",
|
||||
},
|
||||
})
|
||||
products: Product[]
|
||||
|
||||
@OneToMany(
|
||||
() => SalesChannelLocation,
|
||||
(scLocation) => scLocation.sales_channel,
|
||||
|
||||
@@ -2,18 +2,20 @@ import { DeleteResult, FindOptionsWhere, ILike, In } from "typeorm"
|
||||
import { SalesChannel } from "../models"
|
||||
import { ExtendedFindConfig } from "../types/common"
|
||||
import { dataSource } from "../loaders/database"
|
||||
import { generateEntityId } from "../utils"
|
||||
import { ProductSalesChannel } from "../models/product-sales-channel"
|
||||
|
||||
const productSalesChannelTable = "product_sales_channel"
|
||||
|
||||
export const SalesChannelRepository = dataSource
|
||||
.getRepository(SalesChannel)
|
||||
.extend({
|
||||
async getFreeTextSearchResultsAndCount(
|
||||
async getFreeTextSearchResults_(
|
||||
q: string,
|
||||
options: ExtendedFindConfig<SalesChannel> = {
|
||||
options: ExtendedFindConfig<SalesChannel> & { withCount?: boolean } = {
|
||||
where: {},
|
||||
}
|
||||
): Promise<[SalesChannel[], number]> {
|
||||
): Promise<SalesChannel[] | [SalesChannel[], number]> {
|
||||
const options_ = { ...options }
|
||||
|
||||
options_.where = options_.where as FindOptionsWhere<SalesChannel>
|
||||
@@ -41,7 +43,31 @@ export const SalesChannelRepository = dataSource
|
||||
qb = qb.withDeleted()
|
||||
}
|
||||
|
||||
return await qb.getManyAndCount()
|
||||
return await (options_.withCount ? qb.getManyAndCount() : qb.getMany())
|
||||
},
|
||||
|
||||
async getFreeTextSearchResultsAndCount(
|
||||
q: string,
|
||||
options: ExtendedFindConfig<SalesChannel> = {
|
||||
where: {},
|
||||
}
|
||||
): Promise<[SalesChannel[], number]> {
|
||||
return (await this.getFreeTextSearchResults_(q, {
|
||||
...options,
|
||||
withCount: true,
|
||||
})) as [SalesChannel[], number]
|
||||
},
|
||||
|
||||
async getFreeTextSearchResults(
|
||||
q: string,
|
||||
options: ExtendedFindConfig<SalesChannel> = {
|
||||
where: {},
|
||||
}
|
||||
): Promise<SalesChannel[]> {
|
||||
return (await this.getFreeTextSearchResults_(
|
||||
q,
|
||||
options
|
||||
)) as SalesChannel[]
|
||||
},
|
||||
|
||||
async removeProducts(
|
||||
@@ -62,16 +88,26 @@ export const SalesChannelRepository = dataSource
|
||||
|
||||
async addProducts(
|
||||
salesChannelId: string,
|
||||
productIds: string[]
|
||||
productIds: string[],
|
||||
isMedusaV2Enabled?: boolean
|
||||
): Promise<void> {
|
||||
const valuesToInsert = productIds.map((id) => ({
|
||||
let valuesToInsert = productIds.map((id) => ({
|
||||
sales_channel_id: salesChannelId,
|
||||
product_id: id,
|
||||
}))
|
||||
|
||||
if (isMedusaV2Enabled) {
|
||||
valuesToInsert = valuesToInsert.map((v) => ({
|
||||
...v,
|
||||
id: generateEntityId(undefined, "prodsc"),
|
||||
}))
|
||||
}
|
||||
|
||||
await this.createQueryBuilder()
|
||||
.insert()
|
||||
.into(productSalesChannelTable)
|
||||
.into(
|
||||
isMedusaV2Enabled ? ProductSalesChannel : productSalesChannelTable
|
||||
)
|
||||
.values(valuesToInsert)
|
||||
.orIgnore()
|
||||
.execute()
|
||||
|
||||
@@ -3,6 +3,7 @@ import { EventBusService, StoreService } from "../index"
|
||||
import SalesChannelService from "../sales-channel"
|
||||
import { EventBusServiceMock } from "../__mocks__/event-bus"
|
||||
import { store, StoreServiceMock } from "../__mocks__/store"
|
||||
import { FlagRouter } from "@medusajs/utils"
|
||||
|
||||
describe("SalesChannelService", () => {
|
||||
const salesChannelData = {
|
||||
@@ -68,6 +69,7 @@ describe("SalesChannelService", () => {
|
||||
eventBusService: EventBusServiceMock as unknown as EventBusService,
|
||||
salesChannelRepository: salesChannelRepositoryMock,
|
||||
storeService: StoreServiceMock as unknown as StoreService,
|
||||
featureFlagRouter: new FlagRouter({}),
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -90,6 +92,7 @@ describe("SalesChannelService", () => {
|
||||
manager: MockManager,
|
||||
eventBusService: EventBusServiceMock as unknown as EventBusService,
|
||||
salesChannelRepository: salesChannelRepositoryMock,
|
||||
featureFlagRouter: new FlagRouter({}),
|
||||
storeService: {
|
||||
...StoreServiceMock,
|
||||
retrieve: jest.fn().mockImplementation(() => {
|
||||
@@ -119,6 +122,7 @@ describe("SalesChannelService", () => {
|
||||
describe("retrieve", () => {
|
||||
const salesChannelService = new SalesChannelService({
|
||||
manager: MockManager,
|
||||
featureFlagRouter: new FlagRouter({}),
|
||||
eventBusService: EventBusServiceMock as unknown as EventBusService,
|
||||
salesChannelRepository: salesChannelRepositoryMock,
|
||||
storeService: StoreServiceMock as unknown as StoreService,
|
||||
@@ -139,11 +143,9 @@ describe("SalesChannelService", () => {
|
||||
...salesChannelData,
|
||||
})
|
||||
|
||||
expect(
|
||||
salesChannelRepositoryMock.findOne
|
||||
).toHaveBeenLastCalledWith({
|
||||
expect(salesChannelRepositoryMock.findOne).toHaveBeenLastCalledWith({
|
||||
where: { id: IdMap.getId("sales_channel_1") },
|
||||
relationLoadStrategy: "query"
|
||||
relationLoadStrategy: "query",
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -151,6 +153,7 @@ describe("SalesChannelService", () => {
|
||||
describe("update", () => {
|
||||
const salesChannelService = new SalesChannelService({
|
||||
manager: MockManager,
|
||||
featureFlagRouter: new FlagRouter({}),
|
||||
eventBusService: EventBusServiceMock as unknown as EventBusService,
|
||||
salesChannelRepository: salesChannelRepositoryMock,
|
||||
storeService: StoreServiceMock as unknown as StoreService,
|
||||
@@ -186,6 +189,7 @@ describe("SalesChannelService", () => {
|
||||
describe("list", () => {
|
||||
const salesChannelService = new SalesChannelService({
|
||||
manager: MockManager,
|
||||
featureFlagRouter: new FlagRouter({}),
|
||||
eventBusService: EventBusServiceMock as unknown as EventBusService,
|
||||
salesChannelRepository: salesChannelRepositoryMock,
|
||||
storeService: StoreServiceMock as unknown as StoreService,
|
||||
@@ -255,6 +259,7 @@ describe("SalesChannelService", () => {
|
||||
describe("delete", () => {
|
||||
const salesChannelService = new SalesChannelService({
|
||||
manager: MockManager,
|
||||
featureFlagRouter: new FlagRouter({}),
|
||||
eventBusService: EventBusServiceMock as unknown as EventBusService,
|
||||
salesChannelRepository: salesChannelRepositoryMock,
|
||||
storeService: {
|
||||
@@ -310,6 +315,7 @@ describe("SalesChannelService", () => {
|
||||
describe("Remove products", () => {
|
||||
const salesChannelService = new SalesChannelService({
|
||||
manager: MockManager,
|
||||
featureFlagRouter: new FlagRouter({}),
|
||||
eventBusService: EventBusServiceMock as unknown as EventBusService,
|
||||
salesChannelRepository: salesChannelRepositoryMock,
|
||||
storeService: StoreServiceMock as unknown as StoreService,
|
||||
@@ -341,6 +347,7 @@ describe("SalesChannelService", () => {
|
||||
describe("Add products", () => {
|
||||
const salesChannelService = new SalesChannelService({
|
||||
manager: MockManager,
|
||||
featureFlagRouter: new FlagRouter({}),
|
||||
eventBusService: EventBusServiceMock as unknown as EventBusService,
|
||||
salesChannelRepository: salesChannelRepositoryMock,
|
||||
storeService: StoreServiceMock as unknown as StoreService,
|
||||
@@ -359,7 +366,8 @@ describe("SalesChannelService", () => {
|
||||
expect(salesChannelRepositoryMock.addProducts).toHaveBeenCalledTimes(1)
|
||||
expect(salesChannelRepositoryMock.addProducts).toHaveBeenCalledWith(
|
||||
IdMap.getId("sales_channel_1"),
|
||||
[IdMap.getId("sales_channel_1_product_1")]
|
||||
[IdMap.getId("sales_channel_1_product_1")],
|
||||
false
|
||||
)
|
||||
expect(salesChannel).toBeTruthy()
|
||||
expect(salesChannel).toEqual({
|
||||
|
||||
@@ -2,11 +2,14 @@ import {
|
||||
buildRelations,
|
||||
buildSelects,
|
||||
FlagRouter,
|
||||
MedusaV2Flag,
|
||||
objectToStringPath,
|
||||
promiseAll, selectorConstraintsToString,
|
||||
} from "@medusajs/utils"
|
||||
import { RemoteQueryFunction } from "@medusajs/types"
|
||||
import { isDefined, MedusaError } from "medusa-core-utils"
|
||||
import { EntityManager, In } from "typeorm"
|
||||
|
||||
import { ProductVariantService, SearchService } from "."
|
||||
import { TransactionBaseService } from "../interfaces"
|
||||
import SalesChannelFeatureFlag from "../loaders/feature-flags/sales-channels"
|
||||
@@ -42,6 +45,7 @@ import {
|
||||
import { buildQuery, isString, setMetadata } from "../utils"
|
||||
import EventBusService from "./event-bus"
|
||||
import { CreateProductVariantInput } from "../types/product-variant"
|
||||
import SalesChannelService from "./sales-channel"
|
||||
|
||||
type InjectedDependencies = {
|
||||
manager: EntityManager
|
||||
@@ -54,8 +58,10 @@ type InjectedDependencies = {
|
||||
productCategoryRepository: typeof ProductCategoryRepository
|
||||
productVariantService: ProductVariantService
|
||||
searchService: SearchService
|
||||
salesChannelService: SalesChannelService
|
||||
eventBusService: EventBusService
|
||||
featureFlagRouter: FlagRouter
|
||||
remoteQuery: RemoteQueryFunction
|
||||
}
|
||||
|
||||
class ProductService extends TransactionBaseService {
|
||||
@@ -69,8 +75,10 @@ class ProductService extends TransactionBaseService {
|
||||
protected readonly productCategoryRepository_: typeof ProductCategoryRepository
|
||||
protected readonly productVariantService_: ProductVariantService
|
||||
protected readonly searchService_: SearchService
|
||||
protected readonly salesChannelService_: SalesChannelService
|
||||
protected readonly eventBus_: EventBusService
|
||||
protected readonly featureFlagRouter_: FlagRouter
|
||||
protected remoteQuery_: RemoteQueryFunction
|
||||
|
||||
static readonly IndexName = `products`
|
||||
static readonly Events = {
|
||||
@@ -90,6 +98,8 @@ class ProductService extends TransactionBaseService {
|
||||
productCategoryRepository,
|
||||
imageRepository,
|
||||
searchService,
|
||||
remoteQuery,
|
||||
salesChannelService,
|
||||
featureFlagRouter,
|
||||
}: InjectedDependencies) {
|
||||
// eslint-disable-next-line prefer-rest-params
|
||||
@@ -105,7 +115,9 @@ class ProductService extends TransactionBaseService {
|
||||
this.productTagRepository_ = productTagRepository
|
||||
this.imageRepository_ = imageRepository
|
||||
this.searchService_ = searchService
|
||||
this.salesChannelService_ = salesChannelService
|
||||
this.featureFlagRouter_ = featureFlagRouter
|
||||
this.remoteQuery_ = remoteQuery
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -167,17 +179,42 @@ class ProductService extends TransactionBaseService {
|
||||
const manager = this.activeManager_
|
||||
const productRepo = manager.withRepository(this.productRepository_)
|
||||
|
||||
const hasSalesChannelsRelation =
|
||||
config.relations?.includes("sales_channels")
|
||||
|
||||
if (
|
||||
this.featureFlagRouter_.isFeatureEnabled(MedusaV2Flag.key) &&
|
||||
hasSalesChannelsRelation
|
||||
) {
|
||||
config.relations = config.relations?.filter((r) => r !== "sales_channels")
|
||||
}
|
||||
|
||||
const { q, query, relations } = this.prepareListQuery_(selector, config)
|
||||
|
||||
let count: number
|
||||
let products: Product[]
|
||||
|
||||
if (q) {
|
||||
return await productRepo.getFreeTextSearchResultsAndCount(
|
||||
;[products, count] = await productRepo.getFreeTextSearchResultsAndCount(
|
||||
q,
|
||||
query,
|
||||
relations
|
||||
)
|
||||
} else {
|
||||
;[products, count] = await productRepo.findWithRelationsAndCount(
|
||||
relations,
|
||||
query
|
||||
)
|
||||
}
|
||||
|
||||
return await productRepo.findWithRelationsAndCount(relations, query)
|
||||
if (
|
||||
this.featureFlagRouter_.isFeatureEnabled(MedusaV2Flag.key) &&
|
||||
hasSalesChannelsRelation
|
||||
) {
|
||||
await this.decorateProductsWithSalesChannels(products)
|
||||
}
|
||||
|
||||
return [products, count]
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -298,6 +335,16 @@ class ProductService extends TransactionBaseService {
|
||||
const manager = this.activeManager_
|
||||
const productRepo = manager.withRepository(this.productRepository_)
|
||||
|
||||
const hasSalesChannelsRelation =
|
||||
config.relations?.includes("sales_channels")
|
||||
|
||||
if (
|
||||
this.featureFlagRouter_.isFeatureEnabled(MedusaV2Flag.key) &&
|
||||
hasSalesChannelsRelation
|
||||
) {
|
||||
config.relations = config.relations?.filter((r) => r !== "sales_channels")
|
||||
}
|
||||
|
||||
const { relations, ...query } = buildQuery(selector, config)
|
||||
|
||||
const product = await productRepo.findOneWithRelations(
|
||||
@@ -314,6 +361,13 @@ class ProductService extends TransactionBaseService {
|
||||
)
|
||||
}
|
||||
|
||||
if (
|
||||
this.featureFlagRouter_.isFeatureEnabled(MedusaV2Flag.key) &&
|
||||
hasSalesChannelsRelation
|
||||
) {
|
||||
await this.decorateProductsWithSalesChannels([product])
|
||||
}
|
||||
|
||||
return product
|
||||
}
|
||||
|
||||
@@ -465,7 +519,8 @@ class ProductService extends TransactionBaseService {
|
||||
}
|
||||
|
||||
if (
|
||||
this.featureFlagRouter_.isFeatureEnabled(SalesChannelFeatureFlag.key)
|
||||
this.featureFlagRouter_.isFeatureEnabled(SalesChannelFeatureFlag.key) &&
|
||||
!this.featureFlagRouter_.isFeatureEnabled(MedusaV2Flag.key)
|
||||
) {
|
||||
if (isDefined(salesChannels)) {
|
||||
product.sales_channels = []
|
||||
@@ -493,6 +548,20 @@ class ProductService extends TransactionBaseService {
|
||||
|
||||
product = await productRepo.save(product)
|
||||
|
||||
if (
|
||||
isDefined(salesChannels) &&
|
||||
this.featureFlagRouter_.isFeatureEnabled(MedusaV2Flag.key)
|
||||
) {
|
||||
if (salesChannels?.length) {
|
||||
await Promise.all(
|
||||
salesChannels?.map(
|
||||
async (sc) =>
|
||||
await this.salesChannelService_.addProducts(sc.id, [product.id])
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
product.options = await promiseAll(
|
||||
(options ?? []).map(async (option) => {
|
||||
const res = optionRepo.create({
|
||||
@@ -638,7 +707,8 @@ class ProductService extends TransactionBaseService {
|
||||
}
|
||||
|
||||
if (
|
||||
this.featureFlagRouter_.isFeatureEnabled(SalesChannelFeatureFlag.key)
|
||||
this.featureFlagRouter_.isFeatureEnabled(SalesChannelFeatureFlag.key) &&
|
||||
!this.featureFlagRouter_.isFeatureEnabled(MedusaV2Flag.key)
|
||||
) {
|
||||
if (isDefined(salesChannels)) {
|
||||
product.sales_channels = []
|
||||
@@ -661,6 +731,17 @@ class ProductService extends TransactionBaseService {
|
||||
|
||||
const result = await productRepo.save(product)
|
||||
|
||||
if (this.featureFlagRouter_.isFeatureEnabled(MedusaV2Flag.key)) {
|
||||
if (salesChannels?.length) {
|
||||
await promiseAll(
|
||||
salesChannels?.map(
|
||||
async (sc) =>
|
||||
await this.salesChannelService_.addProducts(sc.id, [product.id])
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
await this.eventBus_
|
||||
.withTransaction(manager)
|
||||
.emit(ProductService.Events.UPDATED, {
|
||||
@@ -1025,6 +1106,63 @@ class ProductService extends TransactionBaseService {
|
||||
q,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Temporary method to join sales channels of a product using RemoteQuery while
|
||||
* MedusaV2 FF is on.
|
||||
*
|
||||
* @param products
|
||||
* @private
|
||||
*/
|
||||
private async decorateProductsWithSalesChannels(products: Product[]) {
|
||||
const productIdSalesChannelMapMap =
|
||||
await this.getSalesChannelModuleChannels(products.map((p) => p.id))
|
||||
|
||||
products.forEach(
|
||||
(product) =>
|
||||
(product.sales_channels = productIdSalesChannelMapMap[product.id] ?? [])
|
||||
)
|
||||
|
||||
return products
|
||||
}
|
||||
|
||||
/**
|
||||
* Temporary method to fetch sales channels of a product using RemoteQuery while
|
||||
* MedusaV2 FF is on.
|
||||
*
|
||||
* @param productIds
|
||||
* @private
|
||||
*/
|
||||
private async getSalesChannelModuleChannels(
|
||||
productIds: string[]
|
||||
): Promise<Record<string, SalesChannel[]>> {
|
||||
const query = {
|
||||
product: {
|
||||
__args: { filters: { id: productIds } },
|
||||
fields: ["id"],
|
||||
sales_channels: {
|
||||
fields: [
|
||||
"id",
|
||||
"name",
|
||||
"description",
|
||||
"is_disabled",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"deleted_at",
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const ret = {}
|
||||
const data = (await this.remoteQuery_(query)) as {
|
||||
id: string
|
||||
sales_channels: SalesChannel[]
|
||||
}[]
|
||||
data.forEach((record) => (ret[record.id] = record.sales_channels))
|
||||
|
||||
return ret
|
||||
}
|
||||
}
|
||||
|
||||
export default ProductService
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { EntityManager } from "typeorm"
|
||||
import { isDefined, MedusaError } from "medusa-core-utils"
|
||||
import { FlagRouter, MedusaV2Flag } from "@medusajs/utils"
|
||||
|
||||
import { FindConfig, QuerySelector, Selector } from "../types/common"
|
||||
import {
|
||||
CreateSalesChannelInput,
|
||||
UpdateSalesChannelInput,
|
||||
} from "../types/sales-channels"
|
||||
|
||||
import { isDefined, MedusaError } from "medusa-core-utils"
|
||||
import { EntityManager } from "typeorm"
|
||||
import { TransactionBaseService } from "../interfaces"
|
||||
import { SalesChannel } from "../models"
|
||||
import { SalesChannelRepository } from "../repositories/sales-channel"
|
||||
@@ -19,6 +21,7 @@ type InjectedDependencies = {
|
||||
eventBusService: EventBusService
|
||||
manager: EntityManager
|
||||
storeService: StoreService
|
||||
featureFlagRouter: FlagRouter
|
||||
}
|
||||
|
||||
class SalesChannelService extends TransactionBaseService {
|
||||
@@ -31,11 +34,13 @@ class SalesChannelService extends TransactionBaseService {
|
||||
protected readonly salesChannelRepository_: typeof SalesChannelRepository
|
||||
protected readonly eventBusService_: EventBusService
|
||||
protected readonly storeService_: StoreService
|
||||
protected readonly featureFlagRouter_: FlagRouter
|
||||
|
||||
constructor({
|
||||
salesChannelRepository,
|
||||
eventBusService,
|
||||
storeService,
|
||||
featureFlagRouter,
|
||||
}: InjectedDependencies) {
|
||||
// eslint-disable-next-line prefer-rest-params
|
||||
super(arguments[0])
|
||||
@@ -43,6 +48,7 @@ class SalesChannelService extends TransactionBaseService {
|
||||
this.salesChannelRepository_ = salesChannelRepository
|
||||
this.eventBusService_ = eventBusService
|
||||
this.storeService_ = storeService
|
||||
this.featureFlagRouter_ = featureFlagRouter
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -124,8 +130,9 @@ class SalesChannelService extends TransactionBaseService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists sales channels based on the provided parameters and includes the count of
|
||||
* Lists sales channels based on the provided parameters and include the count of
|
||||
* sales channels that match the query.
|
||||
*
|
||||
* @return an array containing the sales channels as
|
||||
* the first element and the total count of sales channels that matches the query
|
||||
* as the second element.
|
||||
@@ -157,6 +164,38 @@ class SalesChannelService extends TransactionBaseService {
|
||||
return await salesChannelRepo.findAndCount(query)
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists sales channels based on the provided parameters.
|
||||
*
|
||||
* @return an array containing the sales channels
|
||||
*/
|
||||
async list(
|
||||
selector: QuerySelector<SalesChannel>,
|
||||
config: FindConfig<SalesChannel> = {
|
||||
skip: 0,
|
||||
take: 20,
|
||||
}
|
||||
): Promise<SalesChannel[]> {
|
||||
const salesChannelRepo = this.activeManager_.withRepository(
|
||||
this.salesChannelRepository_
|
||||
)
|
||||
|
||||
const selector_ = { ...selector }
|
||||
let q: string | undefined
|
||||
if ("q" in selector_) {
|
||||
q = selector_.q
|
||||
delete selector_.q
|
||||
}
|
||||
|
||||
const query = buildQuery(selector_, config)
|
||||
|
||||
if (q) {
|
||||
return await salesChannelRepo.getFreeTextSearchResults(q, query)
|
||||
}
|
||||
|
||||
return await salesChannelRepo.find(query)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a SalesChannel
|
||||
*
|
||||
@@ -353,7 +392,15 @@ class SalesChannelService extends TransactionBaseService {
|
||||
this.salesChannelRepository_
|
||||
)
|
||||
|
||||
await salesChannelRepo.addProducts(salesChannelId, productIds)
|
||||
const isMedusaV2Enabled = this.featureFlagRouter_.isFeatureEnabled(
|
||||
MedusaV2Flag.key
|
||||
)
|
||||
|
||||
await salesChannelRepo.addProducts(
|
||||
salesChannelId,
|
||||
productIds,
|
||||
isMedusaV2Enabled
|
||||
)
|
||||
|
||||
return await this.retrieve(salesChannelId)
|
||||
})
|
||||
|
||||
@@ -5,7 +5,7 @@ import { ulid } from "ulid"
|
||||
* @param idProperty
|
||||
* @param prefix
|
||||
*/
|
||||
export function generateEntityId(idProperty: string, prefix?: string): string {
|
||||
export function generateEntityId(idProperty?: string, prefix?: string): string {
|
||||
if (idProperty) {
|
||||
return idProperty
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { MedusaContainer } from "@medusajs/types"
|
||||
import { MedusaV2Flag, promiseAll } from "@medusajs/utils"
|
||||
|
||||
import { PriceListService, SalesChannelService } from "../../../services"
|
||||
import { PriceListService } from "../../../services"
|
||||
import { getVariantsFromPriceList } from "./get-variants-from-price-list"
|
||||
|
||||
export async function listProducts(
|
||||
@@ -23,35 +23,6 @@ export async function listProducts(
|
||||
const salesChannelIdFilter = filterableFields.sales_channel_id
|
||||
delete filterableFields.sales_channel_id
|
||||
|
||||
if (salesChannelIdFilter) {
|
||||
const salesChannelService = container.resolve(
|
||||
"salesChannelService"
|
||||
) as SalesChannelService
|
||||
|
||||
promises.push(
|
||||
salesChannelService
|
||||
.listProductIdsBySalesChannelIds(salesChannelIdFilter)
|
||||
.then((productIdsInSalesChannel) => {
|
||||
let filteredProductIds =
|
||||
productIdsInSalesChannel[salesChannelIdFilter]
|
||||
|
||||
if (filterableFields.id) {
|
||||
filterableFields.id = Array.isArray(filterableFields.id)
|
||||
? filterableFields.id
|
||||
: [filterableFields.id]
|
||||
|
||||
const salesChannelProductIdsSet = new Set(filteredProductIds)
|
||||
|
||||
filteredProductIds = filterableFields.id.filter((productId) =>
|
||||
salesChannelProductIdsSet.has(productId)
|
||||
)
|
||||
}
|
||||
|
||||
filteredProductIds.map((id) => productIdsFilter.add(id))
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
const priceListId = filterableFields.price_list_id
|
||||
delete filterableFields.price_list_id
|
||||
|
||||
@@ -112,6 +83,10 @@ export async function listProducts(
|
||||
},
|
||||
}
|
||||
|
||||
if (salesChannelIdFilter) {
|
||||
query.product["sales_channels"]["__args"] = { id: salesChannelIdFilter }
|
||||
}
|
||||
|
||||
const {
|
||||
rows: products,
|
||||
metadata: { count },
|
||||
@@ -245,4 +220,16 @@ export const defaultAdminProductRemoteQueryObject = {
|
||||
profile: {
|
||||
fields: ["id", "created_at", "updated_at", "deleted_at", "name", "type"],
|
||||
},
|
||||
sales_channels: {
|
||||
fields: [
|
||||
"id",
|
||||
"name",
|
||||
"description",
|
||||
"is_disabled",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"deleted_at",
|
||||
"metadata",
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { ulid } from "ulid"
|
||||
* @param idProperty
|
||||
* @param prefix
|
||||
*/
|
||||
export function generateEntityId(idProperty: string, prefix?: string): string {
|
||||
export function generateEntityId(idProperty?: string, prefix?: string): string {
|
||||
if (idProperty) {
|
||||
return idProperty
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user