feat: sales channel module (#5923)
This commit is contained in:
9
.changeset/flat-bees-laugh.md
Normal file
9
.changeset/flat-bees-laugh.md
Normal file
@@ -0,0 +1,9 @@
|
||||
---
|
||||
"@medusajs/core-flows": patch
|
||||
"@medusajs/link-modules": patch
|
||||
"@medusajs/medusa": patch
|
||||
"@medusajs/modules-sdk": patch
|
||||
"@medusajs/types": patch
|
||||
---
|
||||
|
||||
feat: Sales Channel module
|
||||
@@ -71,5 +71,10 @@ module.exports = {
|
||||
resources: "shared",
|
||||
resolve: "@medusajs/promotion",
|
||||
},
|
||||
[Modules.SALES_CHANNEL]: {
|
||||
scope: "internal",
|
||||
resources: "shared",
|
||||
resolve: "@medusajs/sales-channel",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { MedusaV2Flag } from "@medusajs/utils"
|
||||
import { WorkflowArguments } from "@medusajs/workflows-sdk"
|
||||
import { Modules } from "@medusajs/modules-sdk"
|
||||
|
||||
type HandlerInputData = {
|
||||
cart: {
|
||||
@@ -30,10 +31,10 @@ export async function attachCartToSalesChannel({
|
||||
const salesChannel = data[Aliases.SalesChannel]
|
||||
|
||||
await remoteLink.create({
|
||||
cartService: {
|
||||
[Modules.CART]: {
|
||||
cart_id: cart.id,
|
||||
},
|
||||
salesChannelService: {
|
||||
[Modules.SALES_CHANNEL]: {
|
||||
sales_channel_id: salesChannel.sales_channel_id,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { MedusaV2Flag } from "@medusajs/utils"
|
||||
import { WorkflowArguments } from "@medusajs/workflows-sdk"
|
||||
import { Modules } from "@medusajs/modules-sdk"
|
||||
|
||||
type HandlerInputData = {
|
||||
cart: {
|
||||
@@ -30,10 +31,10 @@ export async function detachCartFromSalesChannel({
|
||||
const salesChannel = data[Aliases.SalesChannel]
|
||||
|
||||
await remoteLink.dismiss({
|
||||
cartService: {
|
||||
[Modules.CART]: {
|
||||
cart_id: cart.id,
|
||||
},
|
||||
salesChannelService: {
|
||||
[Modules.SALES_CHANNEL]: {
|
||||
sales_channel_id: salesChannel.sales_channel_id,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -51,7 +51,7 @@ export async function attachSalesChannelToProducts({
|
||||
[Modules.PRODUCT]: {
|
||||
product_id: id,
|
||||
},
|
||||
salesChannelService: {
|
||||
[Modules.SALES_CHANNEL]: {
|
||||
sales_channel_id: salesChannelId,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -49,7 +49,7 @@ export async function detachSalesChannelFromProducts({
|
||||
[Modules.PRODUCT]: {
|
||||
product_id: id,
|
||||
},
|
||||
salesChannelService: {
|
||||
[Modules.SALES_CHANNEL]: {
|
||||
sales_channel_id: salesChannelId,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ModuleJoinerConfig } from "@medusajs/types"
|
||||
import { LINKS } from "../links"
|
||||
import { Modules } from "@medusajs/modules-sdk"
|
||||
|
||||
export const CartSalesChannel: ModuleJoinerConfig = {
|
||||
serviceName: LINKS.CartSalesChannel,
|
||||
@@ -19,14 +20,14 @@ export const CartSalesChannel: ModuleJoinerConfig = {
|
||||
primaryKeys: ["id", "cart_id", "sales_channel_id"],
|
||||
relationships: [
|
||||
{
|
||||
serviceName: "cartService",
|
||||
serviceName: Modules.CART,
|
||||
isInternalService: true,
|
||||
primaryKey: "id",
|
||||
foreignKey: "cart_id",
|
||||
alias: "cart",
|
||||
},
|
||||
{
|
||||
serviceName: "salesChannelService",
|
||||
serviceName: Modules.SALES_CHANNEL,
|
||||
isInternalService: true,
|
||||
primaryKey: "id",
|
||||
foreignKey: "sales_channel_id",
|
||||
@@ -35,7 +36,7 @@ export const CartSalesChannel: ModuleJoinerConfig = {
|
||||
],
|
||||
extends: [
|
||||
{
|
||||
serviceName: "cartService",
|
||||
serviceName: Modules.CART,
|
||||
fieldAlias: {
|
||||
sales_channel: "sales_channel_link.sales_channel",
|
||||
},
|
||||
@@ -48,7 +49,7 @@ export const CartSalesChannel: ModuleJoinerConfig = {
|
||||
},
|
||||
},
|
||||
{
|
||||
serviceName: "salesChannelService",
|
||||
serviceName: Modules.SALES_CHANNEL,
|
||||
fieldAlias: {
|
||||
carts: "cart_link.cart",
|
||||
},
|
||||
|
||||
@@ -26,7 +26,7 @@ export const ProductSalesChannel: ModuleJoinerConfig = {
|
||||
alias: "product",
|
||||
},
|
||||
{
|
||||
serviceName: "salesChannelService",
|
||||
serviceName: Modules.SALES_CHANNEL,
|
||||
isInternalService: true,
|
||||
primaryKey: "id",
|
||||
foreignKey: "sales_channel_id",
|
||||
@@ -48,7 +48,7 @@ export const ProductSalesChannel: ModuleJoinerConfig = {
|
||||
},
|
||||
},
|
||||
{
|
||||
serviceName: "salesChannelService",
|
||||
serviceName: Modules.SALES_CHANNEL,
|
||||
relationship: {
|
||||
serviceName: LINKS.ProductSalesChannel,
|
||||
isInternalService: true,
|
||||
|
||||
@@ -25,25 +25,25 @@ export const LINKS = {
|
||||
ProductSalesChannel: composeLinkName(
|
||||
Modules.PRODUCT,
|
||||
"product_id",
|
||||
"salesChannelService",
|
||||
Modules.SALES_CHANNEL,
|
||||
"sales_channel_id"
|
||||
),
|
||||
CartSalesChannel: composeLinkName(
|
||||
"cartService",
|
||||
Modules.CART,
|
||||
"cart_id",
|
||||
"salesChannelService",
|
||||
Modules.SALES_CHANNEL,
|
||||
"sales_channel_id"
|
||||
),
|
||||
OrderSalesChannel: composeLinkName(
|
||||
"orderService",
|
||||
"order_id",
|
||||
"salesChannelService",
|
||||
Modules.SALES_CHANNEL,
|
||||
"sales_channel_id"
|
||||
),
|
||||
PublishableApiKeySalesChannel: composeLinkName(
|
||||
"publishableApiKeyService",
|
||||
"publishable_key_id",
|
||||
"salesChannelService",
|
||||
Modules.SALES_CHANNEL,
|
||||
"sales_channel_id"
|
||||
),
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { ModuleJoinerConfig } from "@medusajs/types"
|
||||
import { Cart } from "../models"
|
||||
|
||||
export default {
|
||||
serviceName: "cartService",
|
||||
serviceName: Modules.CART,
|
||||
primaryKeys: ["id"],
|
||||
linkableKeys: { cart_id: "Cart" },
|
||||
alias: {
|
||||
|
||||
@@ -1,6 +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"
|
||||
export * as publishableApiKey from "./publishable-api-key-service"
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
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
|
||||
@@ -69,7 +69,7 @@ import { ShippingMethodRepository } from "../repositories/shipping-method"
|
||||
import { PaymentSessionInput } from "../types/payment"
|
||||
import { validateEmail } from "../utils/is-email"
|
||||
import { RemoteQueryFunction } from "@medusajs/types"
|
||||
import { RemoteLink } from "@medusajs/modules-sdk"
|
||||
import { Modules, RemoteLink } from "@medusajs/modules-sdk"
|
||||
|
||||
type InjectedDependencies = {
|
||||
manager: EntityManager
|
||||
@@ -501,10 +501,10 @@ class CartService extends TransactionBaseService {
|
||||
)
|
||||
|
||||
await this.remoteLink_.create({
|
||||
cartService: {
|
||||
[Modules.CART]: {
|
||||
cart_id: cart.id,
|
||||
},
|
||||
salesChannelService: {
|
||||
[Modules.SALES_CHANNEL]: {
|
||||
sales_channel_id: salesChannel.id,
|
||||
},
|
||||
})
|
||||
@@ -1294,20 +1294,20 @@ class CartService extends TransactionBaseService {
|
||||
if (this.featureFlagRouter_.isFeatureEnabled(MedusaV2Flag.key)) {
|
||||
if (cart.sales_channel_id) {
|
||||
await this.remoteLink_.dismiss({
|
||||
cartService: {
|
||||
[Modules.CART]: {
|
||||
cart_id: cart.id,
|
||||
},
|
||||
salesChannelService: {
|
||||
[Modules.SALES_CHANNEL]: {
|
||||
sales_channel_id: cart.sales_channel_id,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
await this.remoteLink_.create({
|
||||
cartService: {
|
||||
[Modules.CART]: {
|
||||
cart_id: cart.id,
|
||||
},
|
||||
salesChannelService: {
|
||||
[Modules.SALES_CHANNEL]: {
|
||||
sales_channel_id: salesChannel.id,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -17,6 +17,7 @@ export enum Modules {
|
||||
PROMOTION = "promotion",
|
||||
AUTHENTICATION = "authentication",
|
||||
WORKFLOW_ENGINE = "workflows",
|
||||
SALES_CHANNEL = "salesChannel",
|
||||
CART = "cart",
|
||||
CUSTOMER = "customer",
|
||||
PAYMENT = "payment",
|
||||
@@ -32,6 +33,7 @@ export enum ModuleRegistrationName {
|
||||
PROMOTION = "promotionModuleService",
|
||||
AUTHENTICATION = "authenticationModuleService",
|
||||
WORKFLOW_ENGINE = "workflowsModuleService",
|
||||
SALES_CHANNEL = "salesChannelModuleService",
|
||||
CART = "cartModuleService",
|
||||
CUSTOMER = "customerModuleService",
|
||||
PAYMENT = "paymentModuleService",
|
||||
@@ -48,6 +50,7 @@ export const MODULE_PACKAGE_NAMES = {
|
||||
[Modules.PROMOTION]: "@medusajs/promotion",
|
||||
[Modules.AUTHENTICATION]: "@medusajs/authentication",
|
||||
[Modules.WORKFLOW_ENGINE]: "@medusajs/workflow-engine-inmemory",
|
||||
[Modules.SALES_CHANNEL]: "@medusajs/sales-channel",
|
||||
[Modules.CART]: "@medusajs/cart",
|
||||
[Modules.CUSTOMER]: "@medusajs/customer",
|
||||
[Modules.PAYMENT]: "@medusajs/payment",
|
||||
@@ -182,6 +185,20 @@ export const ModulesDefinition: { [key: string | Modules]: ModuleDefinition } =
|
||||
resources: MODULE_RESOURCE_TYPE.SHARED,
|
||||
},
|
||||
},
|
||||
[Modules.SALES_CHANNEL]: {
|
||||
key: Modules.SALES_CHANNEL,
|
||||
registrationName: ModuleRegistrationName.SALES_CHANNEL,
|
||||
defaultPackage: false,
|
||||
label: upperCaseFirst(ModuleRegistrationName.SALES_CHANNEL),
|
||||
isRequired: false,
|
||||
canOverride: true,
|
||||
isQueryable: true,
|
||||
dependencies: ["logger"],
|
||||
defaultModuleDeclaration: {
|
||||
scope: MODULE_SCOPE.INTERNAL,
|
||||
resources: MODULE_RESOURCE_TYPE.SHARED,
|
||||
},
|
||||
},
|
||||
[Modules.CART]: {
|
||||
key: Modules.CART,
|
||||
registrationName: ModuleRegistrationName.CART,
|
||||
|
||||
6
packages/sales-channel/.gitignore
vendored
Normal file
6
packages/sales-channel/.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
/dist
|
||||
node_modules
|
||||
.DS_store
|
||||
.env*
|
||||
.env
|
||||
*.sql
|
||||
3
packages/sales-channel/README.md
Normal file
3
packages/sales-channel/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Sales Channel Module
|
||||
|
||||
Sales Channel module enables management of sales channels that are used for grouping Products/Carts/Orders etc.
|
||||
@@ -0,0 +1,40 @@
|
||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||
import { SalesChannel } from "@models"
|
||||
|
||||
const salesChannelData = [
|
||||
{
|
||||
id: "channel-1",
|
||||
name: "Channel 1",
|
||||
description: "Channel description 1",
|
||||
is_disabled: false,
|
||||
},
|
||||
{
|
||||
id: "channel-2",
|
||||
name: "Channel 2",
|
||||
description: "Channel description 2",
|
||||
is_disabled: false,
|
||||
},
|
||||
{
|
||||
id: "channel-3",
|
||||
name: "Channel 3",
|
||||
description: "Channel description 3",
|
||||
is_disabled: true,
|
||||
},
|
||||
]
|
||||
|
||||
export async function createSalesChannels(
|
||||
manager: SqlEntityManager,
|
||||
channelData: any[] = salesChannelData
|
||||
): Promise<SalesChannel[]> {
|
||||
const channels: SalesChannel[] = []
|
||||
|
||||
for (let data of channelData) {
|
||||
const sc = manager.create(SalesChannel, data)
|
||||
|
||||
channels.push(sc)
|
||||
}
|
||||
|
||||
await manager.persistAndFlush(channels)
|
||||
|
||||
return channels
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||
|
||||
import { ISalesChannelModuleService } from "@medusajs/types"
|
||||
|
||||
import { initialize } from "../../../src"
|
||||
|
||||
import { DB_URL, MikroOrmWrapper } from "../../utils"
|
||||
import { createSalesChannels } from "../../__fixtures__"
|
||||
|
||||
jest.setTimeout(30000)
|
||||
|
||||
describe("Sales Channel Service", () => {
|
||||
let service: ISalesChannelModuleService
|
||||
let testManager: SqlEntityManager
|
||||
let repositoryManager: SqlEntityManager
|
||||
|
||||
beforeEach(async () => {
|
||||
await MikroOrmWrapper.setupDatabase()
|
||||
repositoryManager = await MikroOrmWrapper.forkManager()
|
||||
|
||||
service = await initialize({
|
||||
database: {
|
||||
clientUrl: DB_URL,
|
||||
schema: process.env.MEDUSA_SALES_CHANNEL_DB_SCHEMA,
|
||||
},
|
||||
})
|
||||
|
||||
testManager = await MikroOrmWrapper.forkManager()
|
||||
|
||||
await createSalesChannels(testManager)
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await MikroOrmWrapper.clearDatabase()
|
||||
})
|
||||
|
||||
describe("create", () => {
|
||||
it("should create a SalesChannel successfully", async () => {
|
||||
const [created] = await service.create([
|
||||
{
|
||||
name: "test",
|
||||
description: "test",
|
||||
},
|
||||
])
|
||||
|
||||
const [channel] = await service.list({
|
||||
name: [created.name],
|
||||
})
|
||||
|
||||
expect(channel.name).toEqual("test")
|
||||
expect(channel.description).toEqual("test")
|
||||
})
|
||||
})
|
||||
|
||||
describe("retrieve", () => {
|
||||
const id = "channel-1"
|
||||
|
||||
it("should return SalesChannel for the given id", async () => {
|
||||
const result = await service.retrieve(id)
|
||||
|
||||
expect(result).toEqual(
|
||||
expect.objectContaining({
|
||||
id,
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("should throw an error when SalesChannelId with id does not exist", async () => {
|
||||
let error
|
||||
|
||||
try {
|
||||
await service.retrieve("does-not-exist")
|
||||
} catch (e) {
|
||||
error = e
|
||||
}
|
||||
|
||||
expect(error.message).toEqual(
|
||||
"SalesChannel with id: does-not-exist was not found"
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("update", () => {
|
||||
const id = "channel-2"
|
||||
|
||||
it("should update the name of the SalesChannel successfully", async () => {
|
||||
await service.update([
|
||||
{
|
||||
id,
|
||||
name: "Update name 2",
|
||||
is_disabled: true,
|
||||
},
|
||||
])
|
||||
|
||||
const channel = await service.retrieve(id)
|
||||
|
||||
expect(channel.name).toEqual("Update name 2")
|
||||
expect(channel.is_disabled).toEqual(true)
|
||||
})
|
||||
|
||||
it("should throw an error when a id does not exist", async () => {
|
||||
let error
|
||||
|
||||
try {
|
||||
await service.update([
|
||||
{
|
||||
id: "does-not-exist",
|
||||
},
|
||||
])
|
||||
} catch (e) {
|
||||
error = e
|
||||
}
|
||||
|
||||
expect(error.message).toEqual(
|
||||
'SalesChannel with id "does-not-exist" not found'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("list", () => {
|
||||
it("should return a list of SalesChannels", async () => {
|
||||
const result = await service.list()
|
||||
|
||||
expect(result).toEqual([
|
||||
expect.objectContaining({
|
||||
id: "channel-1",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: "channel-2",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: "channel-3",
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("should list SalesChannels by name", async () => {
|
||||
const result = await service.list({
|
||||
name: ["Channel 2", "Channel 3"],
|
||||
})
|
||||
|
||||
expect(result).toEqual([
|
||||
expect.objectContaining({
|
||||
id: "channel-2",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: "channel-3",
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("listAndCount", () => {
|
||||
it("should return sales channels and count", async () => {
|
||||
const [result, count] = await service.listAndCount()
|
||||
|
||||
expect(count).toEqual(3)
|
||||
expect(result).toEqual([
|
||||
expect.objectContaining({
|
||||
id: "channel-1",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: "channel-2",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: "channel-3",
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("should return sales channels and count when filtered", async () => {
|
||||
const [result, count] = await service.listAndCount({
|
||||
id: ["channel-2"],
|
||||
})
|
||||
|
||||
expect(count).toEqual(1)
|
||||
expect(result).toEqual([
|
||||
expect.objectContaining({
|
||||
id: "channel-2",
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("should return sales channels and count when using skip and take", async () => {
|
||||
const [results, count] = await service.listAndCount(
|
||||
{},
|
||||
{ skip: 1, take: 1 }
|
||||
)
|
||||
|
||||
expect(count).toEqual(3)
|
||||
expect(results).toEqual([
|
||||
expect.objectContaining({
|
||||
id: "channel-2",
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("should return requested fields", async () => {
|
||||
const [result, count] = await service.listAndCount(
|
||||
{},
|
||||
{
|
||||
take: 1,
|
||||
select: ["id", "name"],
|
||||
}
|
||||
)
|
||||
|
||||
const serialized = JSON.parse(JSON.stringify(result))
|
||||
|
||||
expect(count).toEqual(3)
|
||||
expect(serialized).toEqual([
|
||||
{
|
||||
id: "channel-1",
|
||||
name: "Channel 1",
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
it("should filter disabled channels", async () => {
|
||||
const [result, count] = await service.listAndCount(
|
||||
{ is_disabled: true },
|
||||
{ select: ["id"] }
|
||||
)
|
||||
|
||||
const serialized = JSON.parse(JSON.stringify(result))
|
||||
|
||||
expect(count).toEqual(1)
|
||||
expect(serialized).toEqual([
|
||||
{
|
||||
id: "channel-3",
|
||||
},
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("delete", () => {
|
||||
const id = "channel-2"
|
||||
|
||||
it("should delete the SalesChannel given an id successfully", async () => {
|
||||
await service.delete([id])
|
||||
|
||||
const result = await service.list({
|
||||
id: [id],
|
||||
})
|
||||
|
||||
expect(result).toHaveLength(0)
|
||||
})
|
||||
})
|
||||
})
|
||||
6
packages/sales-channel/integration-tests/setup-env.js
Normal file
6
packages/sales-channel/integration-tests/setup-env.js
Normal file
@@ -0,0 +1,6 @@
|
||||
if (typeof process.env.DB_TEMP_NAME === "undefined") {
|
||||
const tempName = parseInt(process.env.JEST_WORKER_ID || "1")
|
||||
process.env.DB_TEMP_NAME = `medusa-sales-channel-integration-${tempName}`
|
||||
}
|
||||
|
||||
process.env.MEDUSA_SALES_CHANNEL_DB_SCHEMA = "public"
|
||||
3
packages/sales-channel/integration-tests/setup.js
Normal file
3
packages/sales-channel/integration-tests/setup.js
Normal file
@@ -0,0 +1,3 @@
|
||||
import { JestUtils } from "medusa-test-utils"
|
||||
|
||||
JestUtils.afterAllHookDropDatabase()
|
||||
6
packages/sales-channel/integration-tests/utils/config.ts
Normal file
6
packages/sales-channel/integration-tests/utils/config.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { ModuleServiceInitializeOptions } from "@medusajs/types"
|
||||
|
||||
export const databaseOptions: ModuleServiceInitializeOptions["database"] = {
|
||||
schema: "public",
|
||||
clientUrl: "medusa-sales-channel-test",
|
||||
}
|
||||
18
packages/sales-channel/integration-tests/utils/database.ts
Normal file
18
packages/sales-channel/integration-tests/utils/database.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { TestDatabaseUtils } from "medusa-test-utils"
|
||||
|
||||
import * as SalesChannelModels from "@models"
|
||||
|
||||
const pathToMigrations = "../../src/migrations"
|
||||
const mikroOrmEntities = SalesChannelModels as unknown as any[]
|
||||
|
||||
export const MikroOrmWrapper = TestDatabaseUtils.getMikroOrmWrapper(
|
||||
mikroOrmEntities,
|
||||
pathToMigrations
|
||||
)
|
||||
|
||||
export const MikroOrmConfig = TestDatabaseUtils.getMikroOrmConfig(
|
||||
mikroOrmEntities,
|
||||
pathToMigrations
|
||||
)
|
||||
|
||||
export const DB_URL = TestDatabaseUtils.getDatabaseURL()
|
||||
1
packages/sales-channel/integration-tests/utils/index.ts
Normal file
1
packages/sales-channel/integration-tests/utils/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./database"
|
||||
22
packages/sales-channel/jest.config.js
Normal file
22
packages/sales-channel/jest.config.js
Normal file
@@ -0,0 +1,22 @@
|
||||
module.exports = {
|
||||
moduleNameMapper: {
|
||||
"^@models": "<rootDir>/src/models",
|
||||
"^@services": "<rootDir>/src/services",
|
||||
"^@repositories": "<rootDir>/src/repositories",
|
||||
"^@types": "<rootDir>/src/types",
|
||||
},
|
||||
transform: {
|
||||
"^.+\\.[jt]s?$": [
|
||||
"ts-jest",
|
||||
{
|
||||
tsConfig: "tsconfig.spec.json",
|
||||
isolatedModules: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
testEnvironment: `node`,
|
||||
moduleFileExtensions: [`js`, `ts`],
|
||||
modulePathIgnorePatterns: ["dist/"],
|
||||
setupFiles: ["<rootDir>/integration-tests/setup-env.js"],
|
||||
setupFilesAfterEnv: ["<rootDir>/integration-tests/setup.js"],
|
||||
}
|
||||
8
packages/sales-channel/mikro-orm.config.dev.ts
Normal file
8
packages/sales-channel/mikro-orm.config.dev.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import * as entities from "./src/models"
|
||||
|
||||
module.exports = {
|
||||
entities: Object.values(entities),
|
||||
schema: "public",
|
||||
clientUrl: "postgres://postgres@localhost/medusa-sales-channel",
|
||||
type: "postgresql",
|
||||
}
|
||||
61
packages/sales-channel/package.json
Normal file
61
packages/sales-channel/package.json
Normal file
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"name": "@medusajs/sales-channel",
|
||||
"version": "0.1.0",
|
||||
"description": "Medusa Sales Channel module",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
},
|
||||
"bin": {
|
||||
"medusa-sales-channel-seed": "dist/scripts/bin/run-seed.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/medusajs/medusa",
|
||||
"directory": "packages/sales-channel"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"author": "Medusa",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"watch": "tsc --build --watch",
|
||||
"watch:test": "tsc --build tsconfig.spec.json --watch",
|
||||
"prepublishOnly": "cross-env NODE_ENV=production tsc --build && tsc-alias -p tsconfig.json",
|
||||
"build": "rimraf dist && tsc --build && tsc-alias -p tsconfig.json",
|
||||
"test": "jest --runInBand --bail --forceExit -- src/**/__tests__/**/*.ts",
|
||||
"test:integration": "jest --runInBand --forceExit -- integration-tests/**/__tests__/**/*.ts",
|
||||
"migration:generate": " MIKRO_ORM_CLI=./mikro-orm.config.dev.ts mikro-orm migration:generate",
|
||||
"migration:initial": " MIKRO_ORM_CLI=./mikro-orm.config.dev.ts mikro-orm migration:create --initial",
|
||||
"migration:create": " MIKRO_ORM_CLI=./mikro-orm.config.dev.ts mikro-orm migration:create",
|
||||
"migration:up": " MIKRO_ORM_CLI=./mikro-orm.config.dev.ts mikro-orm migration:up",
|
||||
"orm:cache:clear": " MIKRO_ORM_CLI=./mikro-orm.config.dev.ts mikro-orm cache:clear"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@mikro-orm/cli": "5.9.7",
|
||||
"cross-env": "^5.2.1",
|
||||
"jest": "^29.6.3",
|
||||
"medusa-test-utils": "^1.1.40",
|
||||
"rimraf": "^3.0.2",
|
||||
"ts-jest": "^29.1.1",
|
||||
"ts-node": "^10.9.1",
|
||||
"tsc-alias": "^1.8.6",
|
||||
"typescript": "^5.1.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"@medusajs/modules-sdk": "^1.12.4",
|
||||
"@medusajs/types": "^1.11.8",
|
||||
"@medusajs/utils": "^1.11.1",
|
||||
"@mikro-orm/core": "5.9.7",
|
||||
"@mikro-orm/migrations": "5.9.7",
|
||||
"@mikro-orm/postgresql": "5.9.7",
|
||||
"awilix": "^8.0.0",
|
||||
"dotenv": "^16.1.4",
|
||||
"knex": "2.4.2"
|
||||
}
|
||||
}
|
||||
27
packages/sales-channel/src/index.ts
Normal file
27
packages/sales-channel/src/index.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { Modules } from "@medusajs/modules-sdk"
|
||||
import { ModulesSdkUtils } from "@medusajs/utils"
|
||||
|
||||
import * as SalesChannelModels from "@models"
|
||||
|
||||
import { moduleDefinition } from "./module-definition"
|
||||
|
||||
export default moduleDefinition
|
||||
|
||||
const migrationScriptOptions = {
|
||||
moduleName: Modules.SALES_CHANNEL,
|
||||
models: SalesChannelModels,
|
||||
pathToMigrations: __dirname + "/migrations",
|
||||
}
|
||||
|
||||
export const runMigrations = ModulesSdkUtils.buildMigrationScript(
|
||||
migrationScriptOptions
|
||||
)
|
||||
export const revertMigration = ModulesSdkUtils.buildRevertMigrationScript(
|
||||
migrationScriptOptions
|
||||
)
|
||||
|
||||
export * from "./initialize"
|
||||
export * from "./types"
|
||||
export * from "./loaders"
|
||||
export * from "./models"
|
||||
export * from "./services"
|
||||
34
packages/sales-channel/src/initialize/index.ts
Normal file
34
packages/sales-channel/src/initialize/index.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import {
|
||||
ExternalModuleDeclaration,
|
||||
InternalModuleDeclaration,
|
||||
MedusaModule,
|
||||
MODULE_PACKAGE_NAMES,
|
||||
Modules,
|
||||
} from "@medusajs/modules-sdk"
|
||||
import { ModulesSdkTypes, ISalesChannelModuleService } from "@medusajs/types"
|
||||
import { InitializeModuleInjectableDependencies } from "@types"
|
||||
|
||||
import { moduleDefinition } from "../module-definition"
|
||||
|
||||
export const initialize = async (
|
||||
options?:
|
||||
| ModulesSdkTypes.ModuleServiceInitializeOptions
|
||||
| ModulesSdkTypes.ModuleServiceInitializeCustomDataLayerOptions
|
||||
| ExternalModuleDeclaration
|
||||
| InternalModuleDeclaration,
|
||||
injectedDependencies?: InitializeModuleInjectableDependencies
|
||||
): Promise<ISalesChannelModuleService> => {
|
||||
const serviceKey = Modules.SALES_CHANNEL
|
||||
|
||||
const loaded = await MedusaModule.bootstrap<ISalesChannelModuleService>({
|
||||
moduleKey: serviceKey,
|
||||
defaultPath: MODULE_PACKAGE_NAMES[Modules.SALES_CHANNEL],
|
||||
declaration: options as
|
||||
| InternalModuleDeclaration
|
||||
| ExternalModuleDeclaration,
|
||||
injectedDependencies,
|
||||
moduleExports: moduleDefinition,
|
||||
})
|
||||
|
||||
return loaded[serviceKey]
|
||||
}
|
||||
31
packages/sales-channel/src/joiner-config.ts
Normal file
31
packages/sales-channel/src/joiner-config.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { Modules } from "@medusajs/modules-sdk"
|
||||
import { ModuleJoinerConfig } from "@medusajs/types"
|
||||
import { MapToConfig } from "@medusajs/utils"
|
||||
import { SalesChannel } from "@models"
|
||||
|
||||
export const LinkableKeys = {
|
||||
sales_channel_id: SalesChannel.name,
|
||||
}
|
||||
|
||||
const entityLinkableKeysMap: MapToConfig = {}
|
||||
Object.entries(LinkableKeys).forEach(([key, value]) => {
|
||||
entityLinkableKeysMap[value] ??= []
|
||||
entityLinkableKeysMap[value].push({
|
||||
mapTo: key,
|
||||
valueFrom: key.split("_").pop()!,
|
||||
})
|
||||
})
|
||||
|
||||
export const entityNameToLinkableKeysMap: MapToConfig = entityLinkableKeysMap
|
||||
|
||||
export const joinerConfig: ModuleJoinerConfig = {
|
||||
serviceName: Modules.SALES_CHANNEL,
|
||||
primaryKeys: ["id"],
|
||||
linkableKeys: LinkableKeys,
|
||||
alias: [
|
||||
{
|
||||
name: ["sales_channel", "sales_channels"],
|
||||
args: { entity: "SalesChannel" },
|
||||
},
|
||||
],
|
||||
} as ModuleJoinerConfig
|
||||
37
packages/sales-channel/src/loaders/connection.ts
Normal file
37
packages/sales-channel/src/loaders/connection.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import {
|
||||
InternalModuleDeclaration,
|
||||
LoaderOptions,
|
||||
Modules,
|
||||
} from "@medusajs/modules-sdk"
|
||||
import { ModulesSdkTypes } from "@medusajs/types"
|
||||
import { ModulesSdkUtils } from "@medusajs/utils"
|
||||
import { EntitySchema } from "@mikro-orm/core"
|
||||
|
||||
import * as SalesChannelModels from "@models"
|
||||
|
||||
export default async (
|
||||
{
|
||||
options,
|
||||
container,
|
||||
logger,
|
||||
}: LoaderOptions<
|
||||
| ModulesSdkTypes.ModuleServiceInitializeOptions
|
||||
| ModulesSdkTypes.ModuleServiceInitializeCustomDataLayerOptions
|
||||
>,
|
||||
moduleDeclaration?: InternalModuleDeclaration
|
||||
): Promise<void> => {
|
||||
const entities = Object.values(
|
||||
SalesChannelModels
|
||||
) as unknown as EntitySchema[]
|
||||
const pathToMigrations = __dirname + "/../migrations"
|
||||
|
||||
await ModulesSdkUtils.mikroOrmConnectionLoader({
|
||||
moduleName: Modules.SALES_CHANNEL,
|
||||
entities,
|
||||
container,
|
||||
options,
|
||||
moduleDeclaration,
|
||||
logger,
|
||||
pathToMigrations,
|
||||
})
|
||||
}
|
||||
10
packages/sales-channel/src/loaders/container.ts
Normal file
10
packages/sales-channel/src/loaders/container.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { ModulesSdkUtils } from "@medusajs/utils"
|
||||
import * as ModuleModels from "@models"
|
||||
import * as ModuleRepositories from "@repositories"
|
||||
import * as ModuleServices from "@services"
|
||||
|
||||
export default ModulesSdkUtils.moduleContainerLoaderFactory({
|
||||
moduleModels: ModuleModels,
|
||||
moduleRepositories: ModuleRepositories,
|
||||
moduleServices: ModuleServices,
|
||||
})
|
||||
2
packages/sales-channel/src/loaders/index.ts
Normal file
2
packages/sales-channel/src/loaders/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./connection"
|
||||
export * from "./container"
|
||||
@@ -0,0 +1,105 @@
|
||||
{
|
||||
"namespaces": [
|
||||
"public"
|
||||
],
|
||||
"name": "public",
|
||||
"tables": [
|
||||
{
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"description": {
|
||||
"name": "description",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": true,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"is_disabled": {
|
||||
"name": "is_disabled",
|
||||
"type": "boolean",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"default": "false",
|
||||
"mappedType": "boolean"
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamptz",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"length": 6,
|
||||
"default": "now()",
|
||||
"mappedType": "datetime"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamptz",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"length": 6,
|
||||
"default": "now()",
|
||||
"mappedType": "datetime"
|
||||
},
|
||||
"deleted_at": {
|
||||
"name": "deleted_at",
|
||||
"type": "timestamptz",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": true,
|
||||
"length": 6,
|
||||
"mappedType": "datetime"
|
||||
}
|
||||
},
|
||||
"name": "sales_channel",
|
||||
"schema": "public",
|
||||
"indexes": [
|
||||
{
|
||||
"columnNames": [
|
||||
"deleted_at"
|
||||
],
|
||||
"composite": false,
|
||||
"keyName": "IDX_sales_channel_deleted_at",
|
||||
"primary": false,
|
||||
"unique": false
|
||||
},
|
||||
{
|
||||
"keyName": "sales_channel_pkey",
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"composite": false,
|
||||
"primary": true,
|
||||
"unique": true
|
||||
}
|
||||
],
|
||||
"checks": [],
|
||||
"foreignKeys": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Migration } from "@mikro-orm/migrations"
|
||||
|
||||
export class Migration20240115152146 extends Migration {
|
||||
async up(): Promise<void> {
|
||||
this.addSql(
|
||||
'create table if not exists "sales_channel" ("id" text not null, "name" text not null, "description" text null, "is_disabled" boolean not null default false, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "sales_channel_pkey" primary key ("id"));'
|
||||
)
|
||||
this.addSql(
|
||||
'create index "IDX_sales_channel_deleted_at" on "sales_channel" ("deleted_at");'
|
||||
)
|
||||
}
|
||||
}
|
||||
1
packages/sales-channel/src/models/index.ts
Normal file
1
packages/sales-channel/src/models/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default as SalesChannel } from "./sales-channel"
|
||||
61
packages/sales-channel/src/models/sales-channel.ts
Normal file
61
packages/sales-channel/src/models/sales-channel.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { DALUtils, generateEntityId } from "@medusajs/utils"
|
||||
|
||||
import {
|
||||
BeforeCreate,
|
||||
Entity,
|
||||
Filter,
|
||||
Index,
|
||||
OptionalProps,
|
||||
PrimaryKey,
|
||||
Property,
|
||||
} from "@mikro-orm/core"
|
||||
import { DAL } from "@medusajs/types"
|
||||
|
||||
type SalesChannelOptionalProps = "is_disabled" | DAL.EntityDateColumns
|
||||
|
||||
@Entity()
|
||||
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
|
||||
export default class SalesChannel {
|
||||
[OptionalProps]?: SalesChannelOptionalProps
|
||||
|
||||
@PrimaryKey({ columnType: "text" })
|
||||
id!: string
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
name!: string
|
||||
|
||||
@Property({ columnType: "text", nullable: true })
|
||||
description: string | null = null
|
||||
|
||||
@Property({ columnType: "boolean", default: false })
|
||||
is_disabled = false
|
||||
|
||||
@Property({
|
||||
onCreate: () => new Date(),
|
||||
columnType: "timestamptz",
|
||||
defaultRaw: "now()",
|
||||
})
|
||||
created_at: Date
|
||||
|
||||
@Property({
|
||||
onCreate: () => new Date(),
|
||||
onUpdate: () => new Date(),
|
||||
columnType: "timestamptz",
|
||||
defaultRaw: "now()",
|
||||
})
|
||||
updated_at: Date
|
||||
|
||||
@Index({ name: "IDX_sales_channel_deleted_at" })
|
||||
@Property({ columnType: "timestamptz", nullable: true })
|
||||
deleted_at: Date | null = null
|
||||
|
||||
@BeforeCreate()
|
||||
onCreate() {
|
||||
this.id = generateEntityId(this.id, "sc")
|
||||
}
|
||||
|
||||
@BeforeCreate()
|
||||
onInit() {
|
||||
this.id = generateEntityId(this.id, "sc")
|
||||
}
|
||||
}
|
||||
13
packages/sales-channel/src/module-definition.ts
Normal file
13
packages/sales-channel/src/module-definition.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { ModuleExports } from "@medusajs/types"
|
||||
import { SalesChannelModuleService } from "@services"
|
||||
|
||||
import loadConnection from "./loaders/connection"
|
||||
import loadContainer from "./loaders/container"
|
||||
|
||||
const service = SalesChannelModuleService
|
||||
const loaders = [loadContainer, loadConnection] as any
|
||||
|
||||
export const moduleDefinition: ModuleExports = {
|
||||
service,
|
||||
loaders,
|
||||
}
|
||||
1
packages/sales-channel/src/repositories/index.ts
Normal file
1
packages/sales-channel/src/repositories/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { MikroOrmBaseRepository as BaseRepository } from "@medusajs/utils"
|
||||
31
packages/sales-channel/src/scripts/bin/run-seed.ts
Normal file
31
packages/sales-channel/src/scripts/bin/run-seed.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { ModulesSdkUtils } from "@medusajs/utils"
|
||||
import { Modules } from "@medusajs/modules-sdk"
|
||||
import * as ProductModels from "@models"
|
||||
import { createSalesChannels } from "../seed-utils"
|
||||
import { EOL } from "os"
|
||||
|
||||
const args = process.argv
|
||||
const path = args.pop() as string
|
||||
|
||||
export default (async () => {
|
||||
const { config } = await import("dotenv")
|
||||
config()
|
||||
if (!path) {
|
||||
throw new Error(
|
||||
`filePath is required.${EOL}Example: medusa-product-seed <filePath>`
|
||||
)
|
||||
}
|
||||
|
||||
const run = ModulesSdkUtils.buildSeedScript({
|
||||
moduleName: Modules.PRODUCT,
|
||||
models: ProductModels,
|
||||
pathToMigrations: __dirname + "/../../migrations",
|
||||
seedHandler: async ({ manager, data }) => {
|
||||
const { salesChannelData } = data
|
||||
await createSalesChannels(manager, salesChannelData)
|
||||
},
|
||||
})
|
||||
await run({ path })
|
||||
})()
|
||||
16
packages/sales-channel/src/scripts/seed-utils.ts
Normal file
16
packages/sales-channel/src/scripts/seed-utils.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { SalesChannel } from "@models"
|
||||
import { RequiredEntityData } from "@mikro-orm/core"
|
||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||
|
||||
export async function createSalesChannels(
|
||||
manager: SqlEntityManager,
|
||||
data: RequiredEntityData<SalesChannel>[]
|
||||
) {
|
||||
const channels = data.map((channel) => {
|
||||
return manager.create(SalesChannel, channel)
|
||||
})
|
||||
|
||||
await manager.persistAndFlush(channels)
|
||||
|
||||
return channels
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { SalesChannelService, SalesChannelModuleService } from "@services"
|
||||
import { asClass, asValue, createContainer } from "awilix"
|
||||
|
||||
export const mockContainer = createContainer()
|
||||
|
||||
mockContainer.register({
|
||||
transaction: asValue(async (task) => await task()),
|
||||
salesChannelRepository: asValue({
|
||||
find: jest.fn().mockImplementation(async ({ where: { code } }) => {
|
||||
return [{}]
|
||||
}),
|
||||
findAndCount: jest.fn().mockResolvedValue([[], 0]),
|
||||
getFreshManager: jest.fn().mockResolvedValue({}),
|
||||
}),
|
||||
salesChannelService: asClass(SalesChannelService),
|
||||
salesChannelModuleService: asClass(SalesChannelModuleService),
|
||||
})
|
||||
@@ -0,0 +1,34 @@
|
||||
import { mockContainer } from "../__fixtures__/sales-channel"
|
||||
|
||||
describe("Sales channel service", function () {
|
||||
beforeEach(function () {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("should list sales channels with filters and relations", async function () {
|
||||
const salesChannelRepository = mockContainer.resolve(
|
||||
"salesChannelRepository"
|
||||
)
|
||||
const salesChannelService = mockContainer.resolve("salesChannelService")
|
||||
|
||||
const config = {
|
||||
select: ["id", "name"],
|
||||
}
|
||||
|
||||
await salesChannelService.list({}, config)
|
||||
|
||||
expect(salesChannelRepository.find).toHaveBeenCalledWith(
|
||||
{
|
||||
where: {},
|
||||
options: {
|
||||
fields: ["id", "name"],
|
||||
limit: 15,
|
||||
offset: 0,
|
||||
withDeleted: undefined,
|
||||
populate: [],
|
||||
},
|
||||
},
|
||||
expect.any(Object)
|
||||
)
|
||||
})
|
||||
})
|
||||
2
packages/sales-channel/src/services/index.ts
Normal file
2
packages/sales-channel/src/services/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { default as SalesChannelService } from "./sales-channel"
|
||||
export { default as SalesChannelModuleService } from "./sales-channel-module"
|
||||
247
packages/sales-channel/src/services/sales-channel-module.ts
Normal file
247
packages/sales-channel/src/services/sales-channel-module.ts
Normal file
@@ -0,0 +1,247 @@
|
||||
import {
|
||||
Context,
|
||||
DAL,
|
||||
FilterableSalesChannelProps,
|
||||
FindConfig,
|
||||
InternalModuleDeclaration,
|
||||
ISalesChannelModuleService,
|
||||
ModuleJoinerConfig,
|
||||
RestoreReturn,
|
||||
SalesChannelDTO,
|
||||
SoftDeleteReturn,
|
||||
} from "@medusajs/types"
|
||||
import {
|
||||
InjectManager,
|
||||
InjectTransactionManager,
|
||||
mapObjectTo,
|
||||
MedusaContext,
|
||||
} from "@medusajs/utils"
|
||||
import { CreateSalesChannelDTO, UpdateSalesChannelDTO } from "@medusajs/types"
|
||||
|
||||
import { SalesChannel } from "@models"
|
||||
|
||||
import SalesChannelService from "./sales-channel"
|
||||
import {
|
||||
joinerConfig,
|
||||
entityNameToLinkableKeysMap,
|
||||
LinkableKeys,
|
||||
} from "../joiner-config"
|
||||
|
||||
type InjectedDependencies = {
|
||||
baseRepository: DAL.RepositoryService
|
||||
salesChannelService: SalesChannelService<any>
|
||||
}
|
||||
|
||||
export default class SalesChannelModuleService<
|
||||
TEntity extends SalesChannel = SalesChannel
|
||||
> implements ISalesChannelModuleService
|
||||
{
|
||||
protected baseRepository_: DAL.RepositoryService
|
||||
protected readonly salesChannelService_: SalesChannelService<TEntity>
|
||||
|
||||
constructor(
|
||||
{ baseRepository, salesChannelService }: InjectedDependencies,
|
||||
protected readonly moduleDeclaration: InternalModuleDeclaration
|
||||
) {
|
||||
this.baseRepository_ = baseRepository
|
||||
this.salesChannelService_ = salesChannelService
|
||||
}
|
||||
|
||||
__joinerConfig(): ModuleJoinerConfig {
|
||||
return joinerConfig
|
||||
}
|
||||
|
||||
async create(
|
||||
data: CreateSalesChannelDTO[],
|
||||
sharedContext?: Context
|
||||
): Promise<SalesChannelDTO[]>
|
||||
|
||||
async create(
|
||||
data: CreateSalesChannelDTO,
|
||||
sharedContext?: Context
|
||||
): Promise<SalesChannelDTO>
|
||||
|
||||
@InjectTransactionManager("baseRepository_")
|
||||
async create(
|
||||
data: CreateSalesChannelDTO | CreateSalesChannelDTO[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<SalesChannelDTO | SalesChannelDTO[]> {
|
||||
const input = Array.isArray(data) ? data : [data]
|
||||
|
||||
const result = await this.salesChannelService_.create(input, sharedContext)
|
||||
|
||||
return await this.baseRepository_.serialize<SalesChannelDTO[]>(
|
||||
Array.isArray(data) ? result : result[0],
|
||||
{
|
||||
populate: true,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
async delete(ids: string[], sharedContext?: Context): Promise<void>
|
||||
|
||||
async delete(id: string, sharedContext?: Context): Promise<void>
|
||||
|
||||
@InjectTransactionManager("baseRepository_")
|
||||
async delete(
|
||||
ids: string | string[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<void> {
|
||||
const salesChannelIds = Array.isArray(ids) ? ids : [ids]
|
||||
await this.salesChannelService_.delete(salesChannelIds, sharedContext)
|
||||
}
|
||||
|
||||
@InjectTransactionManager("baseRepository_")
|
||||
protected async softDelete_(
|
||||
salesChannelIds: string[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<[TEntity[], Record<string, unknown[]>]> {
|
||||
return await this.salesChannelService_.softDelete(
|
||||
salesChannelIds,
|
||||
sharedContext
|
||||
)
|
||||
}
|
||||
|
||||
@InjectManager("baseRepository_")
|
||||
async softDelete<
|
||||
TReturnableLinkableKeys extends string = Lowercase<
|
||||
keyof typeof LinkableKeys
|
||||
>
|
||||
>(
|
||||
salesChannelIds: string[],
|
||||
{ returnLinkableKeys }: SoftDeleteReturn<TReturnableLinkableKeys> = {},
|
||||
sharedContext: Context = {}
|
||||
): Promise<Record<Lowercase<keyof typeof LinkableKeys>, string[]> | void> {
|
||||
const [_, cascadedEntitiesMap] = await this.softDelete_(
|
||||
salesChannelIds,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
let mappedCascadedEntitiesMap
|
||||
if (returnLinkableKeys) {
|
||||
mappedCascadedEntitiesMap = mapObjectTo<
|
||||
Record<Lowercase<keyof typeof LinkableKeys>, string[]>
|
||||
>(cascadedEntitiesMap, entityNameToLinkableKeysMap, {
|
||||
pick: returnLinkableKeys,
|
||||
})
|
||||
}
|
||||
|
||||
return mappedCascadedEntitiesMap ? mappedCascadedEntitiesMap : void 0
|
||||
}
|
||||
|
||||
@InjectTransactionManager("baseRepository_")
|
||||
async restore_(
|
||||
salesChannelIds: string[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<[TEntity[], Record<string, unknown[]>]> {
|
||||
return await this.salesChannelService_.restore(
|
||||
salesChannelIds,
|
||||
sharedContext
|
||||
)
|
||||
}
|
||||
|
||||
@InjectManager("baseRepository_")
|
||||
async restore<
|
||||
TReturnableLinkableKeys extends string = Lowercase<
|
||||
keyof typeof LinkableKeys
|
||||
>
|
||||
>(
|
||||
salesChannelIds: string[],
|
||||
{ returnLinkableKeys }: RestoreReturn<TReturnableLinkableKeys> = {},
|
||||
sharedContext: Context = {}
|
||||
): Promise<Record<Lowercase<keyof typeof LinkableKeys>, string[]> | void> {
|
||||
const [_, cascadedEntitiesMap] = await this.restore_(
|
||||
salesChannelIds,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
let mappedCascadedEntitiesMap
|
||||
if (returnLinkableKeys) {
|
||||
mappedCascadedEntitiesMap = mapObjectTo<
|
||||
Record<Lowercase<keyof typeof LinkableKeys>, string[]>
|
||||
>(cascadedEntitiesMap, entityNameToLinkableKeysMap, {
|
||||
pick: returnLinkableKeys,
|
||||
})
|
||||
}
|
||||
|
||||
return mappedCascadedEntitiesMap ? mappedCascadedEntitiesMap : void 0
|
||||
}
|
||||
|
||||
async update(
|
||||
data: UpdateSalesChannelDTO[],
|
||||
sharedContext?: Context
|
||||
): Promise<SalesChannelDTO[]>
|
||||
|
||||
async update(
|
||||
data: UpdateSalesChannelDTO,
|
||||
sharedContext?: Context
|
||||
): Promise<SalesChannelDTO>
|
||||
|
||||
@InjectTransactionManager("baseRepository_")
|
||||
async update(
|
||||
data: UpdateSalesChannelDTO | UpdateSalesChannelDTO[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<SalesChannelDTO | SalesChannelDTO[]> {
|
||||
const input = Array.isArray(data) ? data : [data]
|
||||
|
||||
const result = await this.salesChannelService_.update(input, sharedContext)
|
||||
|
||||
return await this.baseRepository_.serialize<SalesChannelDTO[]>(
|
||||
Array.isArray(data) ? result : result[0],
|
||||
{
|
||||
populate: true,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@InjectManager("baseRepository_")
|
||||
async retrieve(
|
||||
salesChannelId: string,
|
||||
config: FindConfig<SalesChannelDTO> = {},
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<SalesChannelDTO> {
|
||||
const salesChannel = await this.salesChannelService_.retrieve(
|
||||
salesChannelId,
|
||||
config
|
||||
)
|
||||
|
||||
return await this.baseRepository_.serialize<SalesChannelDTO>(salesChannel, {
|
||||
populate: true,
|
||||
})
|
||||
}
|
||||
|
||||
@InjectManager("baseRepository_")
|
||||
async list(
|
||||
filters: {} = {},
|
||||
config: FindConfig<SalesChannelDTO> = {},
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<SalesChannelDTO[]> {
|
||||
const salesChannels = await this.salesChannelService_.list(filters, config)
|
||||
|
||||
return await this.baseRepository_.serialize<SalesChannelDTO[]>(
|
||||
salesChannels,
|
||||
{
|
||||
populate: true,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@InjectManager("baseRepository_")
|
||||
async listAndCount(
|
||||
filters: FilterableSalesChannelProps = {},
|
||||
config: FindConfig<SalesChannelDTO> = {},
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<[SalesChannelDTO[], number]> {
|
||||
const [salesChannels, count] = await this.salesChannelService_.listAndCount(
|
||||
filters,
|
||||
config
|
||||
)
|
||||
|
||||
return [
|
||||
await this.baseRepository_.serialize<SalesChannelDTO[]>(salesChannels, {
|
||||
populate: true,
|
||||
}),
|
||||
count,
|
||||
]
|
||||
}
|
||||
}
|
||||
24
packages/sales-channel/src/services/sales-channel.ts
Normal file
24
packages/sales-channel/src/services/sales-channel.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { DAL } from "@medusajs/types"
|
||||
import { ModulesSdkUtils } from "@medusajs/utils"
|
||||
import { CreateSalesChannelDTO, UpdateSalesChannelDTO } from "@medusajs/types"
|
||||
|
||||
import { SalesChannel } from "@models"
|
||||
|
||||
type InjectedDependencies = {
|
||||
salesChannelRepository: DAL.RepositoryService
|
||||
}
|
||||
|
||||
export default class SalesChannelService<
|
||||
TEntity extends SalesChannel = SalesChannel
|
||||
> extends ModulesSdkUtils.abstractServiceFactory<
|
||||
InjectedDependencies,
|
||||
{
|
||||
create: CreateSalesChannelDTO
|
||||
update: UpdateSalesChannelDTO
|
||||
}
|
||||
>(SalesChannel)<TEntity> {
|
||||
constructor(container: InjectedDependencies) {
|
||||
// @ts-ignore
|
||||
super(...arguments)
|
||||
}
|
||||
}
|
||||
5
packages/sales-channel/src/types/index.ts
Normal file
5
packages/sales-channel/src/types/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { Logger } from "@medusajs/types"
|
||||
|
||||
export type InitializeModuleInjectableDependencies = {
|
||||
logger?: Logger
|
||||
}
|
||||
15
packages/sales-channel/src/types/repositories.ts
Normal file
15
packages/sales-channel/src/types/repositories.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { DAL } from "@medusajs/types"
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
import { SalesChannel } from "@models"
|
||||
import { CreateSalesChannelDTO, UpdateSalesChannelDTO } from "@medusajs/types"
|
||||
|
||||
export interface ISalesChannelRepository<
|
||||
TEntity extends SalesChannel = SalesChannel
|
||||
> extends DAL.RepositoryService<
|
||||
TEntity,
|
||||
{
|
||||
create: CreateSalesChannelDTO
|
||||
update: UpdateSalesChannelDTO
|
||||
}
|
||||
> {}
|
||||
37
packages/sales-channel/tsconfig.json
Normal file
37
packages/sales-channel/tsconfig.json
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["es2020"],
|
||||
"target": "es2020",
|
||||
"outDir": "./dist",
|
||||
"esModuleInterop": true,
|
||||
"declaration": true,
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"sourceMap": false,
|
||||
"noImplicitReturns": true,
|
||||
"strictNullChecks": true,
|
||||
"strictFunctionTypes": true,
|
||||
"noImplicitThis": true,
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"downlevelIteration": true, // to use ES5 specific tooling
|
||||
"baseUrl": ".",
|
||||
"resolveJsonModule": true,
|
||||
"paths": {
|
||||
"@models": ["./src/models"],
|
||||
"@services": ["./src/services"],
|
||||
"@repositories": ["./src/repositories"],
|
||||
"@types": ["./src/types"]
|
||||
}
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": [
|
||||
"dist",
|
||||
"./src/**/__tests__",
|
||||
"./src/**/__mocks__",
|
||||
"./src/**/__fixtures__",
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
8
packages/sales-channel/tsconfig.spec.json
Normal file
8
packages/sales-channel/tsconfig.spec.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"include": ["src", "integration-tests"],
|
||||
"exclude": ["node_modules", "dist"],
|
||||
"compilerOptions": {
|
||||
"sourceMap": true
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import { BaseFilterable } from "../dal"
|
||||
|
||||
export interface SalesChannelLocationDTO {
|
||||
sales_channel_id: string
|
||||
location_id: string
|
||||
@@ -6,8 +8,16 @@ export interface SalesChannelLocationDTO {
|
||||
|
||||
export interface SalesChannelDTO {
|
||||
id: string
|
||||
name: string
|
||||
description: string | null
|
||||
is_disabled: boolean
|
||||
metadata: Record<string, unknown> | null
|
||||
locations?: SalesChannelLocationDTO[]
|
||||
}
|
||||
|
||||
export interface FilterableSalesChannelProps
|
||||
extends BaseFilterable<FilterableSalesChannelProps> {
|
||||
id?: string[]
|
||||
name?: string[]
|
||||
is_disabled?: boolean
|
||||
}
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
export * from "./common"
|
||||
export * from "./mutations"
|
||||
export * from "./service"
|
||||
|
||||
12
packages/types/src/sales-channel/mutations.ts
Normal file
12
packages/types/src/sales-channel/mutations.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
export interface CreateSalesChannelDTO {
|
||||
name: string
|
||||
description?: string
|
||||
is_disabled?: boolean
|
||||
}
|
||||
|
||||
export interface UpdateSalesChannelDTO {
|
||||
id: string
|
||||
name?: string
|
||||
description?: string
|
||||
is_disabled?: boolean
|
||||
}
|
||||
59
packages/types/src/sales-channel/service.ts
Normal file
59
packages/types/src/sales-channel/service.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { IModuleService } from "../modules-sdk"
|
||||
import { FilterableSalesChannelProps, SalesChannelDTO } from "./common"
|
||||
import { FindConfig } from "../common"
|
||||
import { Context } from "../shared-context"
|
||||
import { RestoreReturn, SoftDeleteReturn } from "../dal"
|
||||
import { CreateSalesChannelDTO, UpdateSalesChannelDTO } from "./mutations"
|
||||
|
||||
export interface ISalesChannelModuleService extends IModuleService {
|
||||
create(
|
||||
data: CreateSalesChannelDTO[],
|
||||
sharedContext?: Context
|
||||
): Promise<SalesChannelDTO[]>
|
||||
create(
|
||||
data: CreateSalesChannelDTO,
|
||||
sharedContext?: Context
|
||||
): Promise<SalesChannelDTO>
|
||||
|
||||
update(
|
||||
data: UpdateSalesChannelDTO[],
|
||||
sharedContext?: Context
|
||||
): Promise<SalesChannelDTO[]>
|
||||
update(
|
||||
data: UpdateSalesChannelDTO,
|
||||
sharedContext?: Context
|
||||
): Promise<SalesChannelDTO>
|
||||
|
||||
delete(ids: string[], sharedContext?: Context): Promise<void>
|
||||
delete(id: string, sharedContext?: Context): Promise<void>
|
||||
|
||||
retrieve(
|
||||
id: string,
|
||||
config?: FindConfig<SalesChannelDTO>,
|
||||
sharedContext?: Context
|
||||
): Promise<SalesChannelDTO>
|
||||
|
||||
list(
|
||||
filters?: FilterableSalesChannelProps,
|
||||
config?: FindConfig<SalesChannelDTO>,
|
||||
sharedContext?: Context
|
||||
): Promise<SalesChannelDTO[]>
|
||||
|
||||
listAndCount(
|
||||
filters?: FilterableSalesChannelProps,
|
||||
config?: FindConfig<SalesChannelDTO>,
|
||||
sharedContext?: Context
|
||||
): Promise<[SalesChannelDTO[], number]>
|
||||
|
||||
softDelete<TReturnableLinkableKeys extends string = string>(
|
||||
salesChannelIds: string[],
|
||||
config?: SoftDeleteReturn<TReturnableLinkableKeys>,
|
||||
sharedContext?: Context
|
||||
): Promise<Record<string, string[]> | void>
|
||||
|
||||
restore<TReturnableLinkableKeys extends string = string>(
|
||||
salesChannelIds: string[],
|
||||
config?: RestoreReturn<TReturnableLinkableKeys>,
|
||||
sharedContext?: Context
|
||||
): Promise<Record<string, string[]> | void>
|
||||
}
|
||||
33
yarn.lock
33
yarn.lock
@@ -8370,7 +8370,7 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@medusajs/modules-sdk@^1.12.2, @medusajs/modules-sdk@^1.12.3, @medusajs/modules-sdk@^1.12.5, @medusajs/modules-sdk@^1.12.6, @medusajs/modules-sdk@^1.12.7, @medusajs/modules-sdk@^1.8.8, @medusajs/modules-sdk@workspace:^, @medusajs/modules-sdk@workspace:packages/modules-sdk":
|
||||
"@medusajs/modules-sdk@^1.12.2, @medusajs/modules-sdk@^1.12.3, @medusajs/modules-sdk@^1.12.4, @medusajs/modules-sdk@^1.12.5, @medusajs/modules-sdk@^1.12.6, @medusajs/modules-sdk@^1.12.7, @medusajs/modules-sdk@^1.8.8, @medusajs/modules-sdk@workspace:^, @medusajs/modules-sdk@workspace:packages/modules-sdk":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@medusajs/modules-sdk@workspace:packages/modules-sdk"
|
||||
dependencies:
|
||||
@@ -8565,6 +8565,33 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@medusajs/sales-channel@workspace:packages/sales-channel":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@medusajs/sales-channel@workspace:packages/sales-channel"
|
||||
dependencies:
|
||||
"@medusajs/modules-sdk": ^1.12.4
|
||||
"@medusajs/types": ^1.11.8
|
||||
"@medusajs/utils": ^1.11.1
|
||||
"@mikro-orm/cli": 5.9.7
|
||||
"@mikro-orm/core": 5.9.7
|
||||
"@mikro-orm/migrations": 5.9.7
|
||||
"@mikro-orm/postgresql": 5.9.7
|
||||
awilix: ^8.0.0
|
||||
cross-env: ^5.2.1
|
||||
dotenv: ^16.1.4
|
||||
jest: ^29.6.3
|
||||
knex: 2.4.2
|
||||
medusa-test-utils: ^1.1.40
|
||||
rimraf: ^3.0.2
|
||||
ts-jest: ^29.1.1
|
||||
ts-node: ^10.9.1
|
||||
tsc-alias: ^1.8.6
|
||||
typescript: ^5.1.6
|
||||
bin:
|
||||
medusa-sales-channel-seed: dist/scripts/bin/run-seed.js
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@medusajs/stock-location@workspace:packages/stock-location":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@medusajs/stock-location@workspace:packages/stock-location"
|
||||
@@ -8608,7 +8635,7 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@medusajs/types@^1.10.0, @medusajs/types@^1.11.10, @medusajs/types@^1.11.11, @medusajs/types@^1.11.5, @medusajs/types@^1.11.6, @medusajs/types@^1.11.9, @medusajs/types@^1.8.10, @medusajs/types@workspace:^, @medusajs/types@workspace:packages/types":
|
||||
"@medusajs/types@^1.10.0, @medusajs/types@^1.11.10, @medusajs/types@^1.11.11, @medusajs/types@^1.11.5, @medusajs/types@^1.11.6, @medusajs/types@^1.11.8, @medusajs/types@^1.11.9, @medusajs/types@^1.8.10, @medusajs/types@workspace:^, @medusajs/types@workspace:packages/types":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@medusajs/types@workspace:packages/types"
|
||||
dependencies:
|
||||
@@ -8711,7 +8738,7 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@medusajs/utils@^1.1.41, @medusajs/utils@^1.10.5, @medusajs/utils@^1.11.2, @medusajs/utils@^1.11.3, @medusajs/utils@^1.11.4, @medusajs/utils@^1.9.2, @medusajs/utils@^1.9.4, @medusajs/utils@workspace:^, @medusajs/utils@workspace:packages/utils":
|
||||
"@medusajs/utils@^1.1.41, @medusajs/utils@^1.10.5, @medusajs/utils@^1.11.1, @medusajs/utils@^1.11.2, @medusajs/utils@^1.11.3, @medusajs/utils@^1.11.4, @medusajs/utils@^1.9.2, @medusajs/utils@^1.9.4, @medusajs/utils@workspace:^, @medusajs/utils@workspace:packages/utils":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@medusajs/utils@workspace:packages/utils"
|
||||
dependencies:
|
||||
|
||||
Reference in New Issue
Block a user