feat(medusa, link-modules): sales channel <> order link (#5810)

This commit is contained in:
Frane Polić
2024-01-03 14:07:54 +01:00
committed by GitHub
parent 278b7fb203
commit fe007d01bd
15 changed files with 293 additions and 20 deletions

View File

@@ -0,0 +1,8 @@
---
"@medusajs/link-modules": patch
"@medusajs/core-flows": patch
"@medusajs/medusa": patch
"@medusajs/utils": patch
---
feat: sales channel <> order link

View File

@@ -425,7 +425,9 @@ Object {
],
"locale": null,
"order": Object {
"afterLoad": [Function],
"beforeInsert": [Function],
"beforeUpdate": [Function],
"billing_address_id": null,
"canceled_at": null,
"cart_id": null,
@@ -753,7 +755,9 @@ Object {
exports[`medusa-plugin-sendgrid order canceled data 1`] = `
Object {
"afterLoad": [Function],
"beforeInsert": [Function],
"beforeUpdate": [Function],
"billing_address": null,
"billing_address_id": null,
"canceled_at": Any<Date>,
@@ -982,7 +986,9 @@ Object {
exports[`medusa-plugin-sendgrid order placed data 1`] = `
Object {
"afterLoad": [Function],
"beforeInsert": [Function],
"beforeUpdate": [Function],
"billing_address": null,
"billing_address_id": null,
"canceled_at": null,
@@ -1236,7 +1242,9 @@ Object {
},
"locale": null,
"order": Object {
"afterLoad": [Function],
"beforeInsert": [Function],
"beforeUpdate": [Function],
"billing_address": null,
"billing_address_id": null,
"canceled_at": null,
@@ -1612,7 +1620,9 @@ Object {
],
"locale": null,
"order": Object {
"afterLoad": [Function],
"beforeInsert": [Function],
"beforeUpdate": [Function],
"billing_address_id": null,
"canceled_at": null,
"cart_id": null,
@@ -2078,7 +2088,9 @@ Object {
],
"locale": null,
"order": Object {
"afterLoad": [Function],
"beforeInsert": [Function],
"beforeUpdate": [Function],
"billing_address_id": null,
"canceled_at": null,
"cart_id": null,
@@ -2533,4 +2545,4 @@ Object {
"tracking_links": Array [],
"tracking_number": "",
}
`;
`;

View File

@@ -4,10 +4,7 @@ const {
startBootstrapApp,
} = require("../../../environment-helpers/bootstrap-app")
const { initDb, useDb } = require("../../../environment-helpers/use-db")
const {
useApi,
useExpressServer,
} = require("../../../environment-helpers/use-api")
const { useApi } = require("../../../environment-helpers/use-api")
const adminSeeder = require("../../../helpers/admin-seeder")

View File

@@ -38,14 +38,13 @@ export async function detachSalesChannelFromProducts({
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(
await promiseAll(
productIds.map((id) =>
remoteLink.dismiss({
[Modules.PRODUCT]: {
product_id: id,
@@ -57,8 +56,6 @@ export async function detachSalesChannelFromProducts({
)
)
}
return
} else {
await promiseAll(
Array.from(salesChannelIdProductIdsMap.entries()).map(

View File

@@ -4,3 +4,4 @@ export * from "./product-variant-price-set"
export * from "./product-shipping-profile"
export * from "./product-sales-channel"
export * from "./cart-sales-channel"
export * from "./order-sales-channel"

View File

@@ -0,0 +1,66 @@
import { ModuleJoinerConfig } from "@medusajs/types"
import { LINKS } from "../links"
export const OrderSalesChannel: ModuleJoinerConfig = {
serviceName: LINKS.OrderSalesChannel,
isLink: true,
databaseConfig: {
tableName: "order_sales_channel",
idPrefix: "ordersc",
},
alias: [
{
name: "order_sales_channel",
},
{
name: "order_sales_channels",
},
],
primaryKeys: ["id", "order_id", "sales_channel_id"],
relationships: [
{
serviceName: "orderService",
isInternalService: true,
primaryKey: "id",
foreignKey: "order_id",
alias: "order",
},
{
serviceName: "salesChannelService",
isInternalService: true,
primaryKey: "id",
foreignKey: "sales_channel_id",
alias: "sales_channel",
},
],
extends: [
{
serviceName: "orderService",
fieldAlias: {
sales_channel: "sales_channel_link.sales_channel",
},
relationship: {
serviceName: LINKS.OrderSalesChannel,
isInternalService: true,
primaryKey: "order_id",
foreignKey: "id",
alias: "sales_channel_link",
},
},
{
serviceName: "salesChannelService",
fieldAlias: {
orders: "order_link.order",
},
relationship: {
serviceName: LINKS.OrderSalesChannel,
isInternalService: true,
primaryKey: "sales_channel_id",
foreignKey: "id",
alias: "order_link",
isList: true,
},
},
],
}

View File

@@ -34,4 +34,10 @@ export const LINKS = {
"salesChannelService",
"sales_channel_id"
),
OrderSalesChannel: composeLinkName(
"orderService",
"order_id",
"salesChannelService",
"sales_channel_id"
),
}

View File

@@ -0,0 +1,15 @@
import { ModuleJoinerConfig } from "@medusajs/types"
export default {
serviceName: "orderService",
primaryKeys: ["id"],
linkableKeys: { order_id: "Order" },
alias: [
{
name: "order",
},
{
name: "orders",
},
],
} as ModuleJoinerConfig

View File

@@ -0,0 +1,43 @@
import { MigrationInterface, QueryRunner } from "typeorm"
import { MedusaV2Flag } from "@medusajs/utils"
import SalesChannelFeatureFlag from "../loaders/feature-flags/sales-channels"
export const featureFlag = [SalesChannelFeatureFlag.key, MedusaV2Flag.key]
export class OrderSalesChannelLink1701860329931 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
CREATE TABLE IF NOT EXISTS "order_sales_channel"
(
"id" character varying NOT NULL,
"order_id" character varying NOT NULL,
"sales_channel_id" character varying NOT NULL,
"created_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
"updated_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
"deleted_at" TIMESTAMP WITH TIME ZONE,
CONSTRAINT "order_sales_channel_pk" PRIMARY KEY ("order_id", "sales_channel_id"),
CONSTRAINT "order_sales_channel_order_id_unique" UNIQUE ("order_id")
);
CREATE INDEX IF NOT EXISTS "IDX_id_order_sales_channel" ON "order_sales_channel" ("id");
insert into "order_sales_channel" (id, order_id, sales_channel_id)
(select 'ordersc_' || substr(md5(random()::text), 0, 27), id, sales_channel_id from "order" WHERE sales_channel_id IS NOT NULL);
ALTER TABLE "order" DROP CONSTRAINT IF EXISTS "FK_6ff7e874f01b478c115fdd462eb";
`)
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
UPDATE "order"
SET "sales_channel_id" = "order_sales_channel"."sales_channel_id"
FROM "order_sales_channel"
WHERE "order"."id" = "order_sales_channel"."order_id";
DROP TABLE IF EXISTS "order_sales_channel";
ALTER TABLE "order" ADD CONSTRAINT "FK_6ff7e874f01b478c115fdd462eb" FOREIGN KEY ("sales_channel_id") REFERENCES "sales_channel"("id") ON DELETE NO ACTION ON UPDATE NO ACTION;
`)
}
}

View File

@@ -0,0 +1,29 @@
import { BeforeInsert, Column, Index, PrimaryColumn } from "typeorm"
import { MedusaV2Flag, SalesChannelFeatureFlag } from "@medusajs/utils"
import { generateEntityId } from "../utils"
import { SoftDeletableEntity } from "../interfaces"
import { FeatureFlagEntity } from "../utils/feature-flag-decorators"
@FeatureFlagEntity([MedusaV2Flag.key, SalesChannelFeatureFlag.key])
export class OrderSalesChannel extends SoftDeletableEntity {
@Column()
id: string
@Index("order_sales_channel_order_id_unique", {
unique: true,
})
@PrimaryColumn()
order_id: string
@PrimaryColumn()
sales_channel_id: string
/**
* @apiIgnore
*/
@BeforeInsert()
private beforeInsert(): void {
this.id = generateEntityId(this.id, "ordersc")
}
}

View File

@@ -1,5 +1,7 @@
import {
AfterLoad,
BeforeInsert,
BeforeUpdate,
Column,
Entity,
Generated,
@@ -12,7 +14,10 @@ import {
OneToOne,
} from "typeorm"
import { DbAwareColumn, resolveDbType } from "../utils/db-aware-column"
import { FeatureFlagColumn, FeatureFlagDecorators, } from "../utils/feature-flag-decorators"
import {
FeatureFlagColumn,
FeatureFlagDecorators,
} from "../utils/feature-flag-decorators"
import { BaseEntity } from "../interfaces/models/base-entity"
import { generateEntityId } from "../utils/generate-entity-id"
@@ -36,10 +41,11 @@ import { Return } from "./return"
import { SalesChannel } from "./sales-channel"
import { ShippingMethod } from "./shipping-method"
import { Swap } from "./swap"
import { MedusaV2Flag } from "@medusajs/utils"
/**
* @enum
*
*
* The order's status.
*/
export enum OrderStatus {
@@ -48,7 +54,7 @@ export enum OrderStatus {
*/
PENDING = "pending",
/**
* The order is completed, meaning that
* The order is completed, meaning that
* the items have been fulfilled and the payment
* has been captured.
*/
@@ -69,7 +75,7 @@ export enum OrderStatus {
/**
* @enum
*
*
* The order's fulfillment status.
*/
export enum FulfillmentStatus {
@@ -78,7 +84,7 @@ export enum FulfillmentStatus {
*/
NOT_FULFILLED = "not_fulfilled",
/**
* Some of the order's items, but not all, are fulfilled.
* Some of the order's items, but not all, are fulfilled.
*/
PARTIALLY_FULFILLED = "partially_fulfilled",
/**
@@ -113,7 +119,7 @@ export enum FulfillmentStatus {
/**
* @enum
*
*
* The order's payment status.
*/
export enum PaymentStatus {
@@ -321,6 +327,25 @@ export class Order extends BaseEntity {
])
sales_channel: SalesChannel
@FeatureFlagDecorators(
[MedusaV2Flag.key, "sales_channels"],
[
ManyToMany(() => SalesChannel, { cascade: ["remove", "soft-remove"] }),
JoinTable({
name: "order_sales_channel",
joinColumn: {
name: "cart_id",
referencedColumnName: "id",
},
inverseJoinColumn: {
name: "sales_channel_id",
referencedColumnName: "id",
},
}),
]
)
sales_channels?: SalesChannel[]
// Total fields
shipping_total: number
shipping_tax_total: number | null
@@ -345,6 +370,12 @@ export class Order extends BaseEntity {
private async beforeInsert(): Promise<void> {
this.id = generateEntityId(this.id, "order")
if (this.sales_channel_id || this.sales_channel) {
this.sales_channels = [
{ id: this.sales_channel_id || this.sales_channel?.id },
] as SalesChannel[]
}
if (process.env.NODE_ENV === "development" && !this.display_id) {
const disId = await manualAutoIncrement("order")
@@ -353,6 +384,30 @@ export class Order extends BaseEntity {
}
}
}
/**
* @apiIgnore
*/
@BeforeUpdate()
private beforeUpdate(): void {
if (this.sales_channel_id || this.sales_channel) {
this.sales_channels = [
{ id: this.sales_channel_id || this.sales_channel?.id },
] as SalesChannel[]
}
}
/**
* @apiIgnore
*/
@AfterLoad()
private afterLoad(): void {
if (this.sales_channels) {
this.sales_channel = this.sales_channels?.[0]
this.sales_channel_id = this.sales_channel?.id
delete this.sales_channels
}
}
}
/**

View File

@@ -1,15 +1,16 @@
import { BeforeInsert, Column, JoinTable, ManyToMany, OneToMany } from "typeorm"
import { MedusaV2Flag } from "@medusajs/utils"
import {
FeatureFlagDecorators,
FeatureFlagEntity,
} from "../utils/feature-flag-decorators"
import { MedusaV2Flag } from "@medusajs/utils"
import { SoftDeletableEntity } from "../interfaces"
import { DbAwareColumn, generateEntityId } from "../utils"
import { SalesChannelLocation } from "./sales-channel-location"
import { Product } from "./product"
import { Cart } from "./cart"
import { Order } from "./order"
@FeatureFlagEntity("sales_channels")
export class SalesChannel extends SoftDeletableEntity {
@@ -55,6 +56,24 @@ export class SalesChannel extends SoftDeletableEntity {
])
carts: Cart[]
@FeatureFlagDecorators(MedusaV2Flag.key,
[
ManyToMany(() => Order),
JoinTable({
name: "order_sales_channel",
joinColumn: {
name: "sales_channel_id",
referencedColumnName: "id",
},
inverseJoinColumn: {
name: "order_id",
referencedColumnName: "id",
},
}),
]
)
orders: Order[]
@OneToMany(
() => SalesChannelLocation,
(scLocation) => scLocation.sales_channel,

View File

@@ -1,4 +1,5 @@
import { IdMap, MockManager, MockRepository } from "medusa-test-utils"
import { FlagRouter } from "@medusajs/utils"
import { LineItemServiceMock } from "../__mocks__/line-item"
import { newTotalsServiceMock } from "../__mocks__/new-totals"
import { ProductVariantInventoryServiceMock } from "../__mocks__/product-variant-inventory"
@@ -151,6 +152,7 @@ describe("OrderService", () => {
eventBusService,
cartService,
productVariantInventoryService,
featureFlagRouter: new FlagRouter({}),
})
beforeEach(async () => {

View File

@@ -5,6 +5,7 @@ import {
FlagRouter,
isDefined,
MedusaError,
MedusaV2Flag,
promiseAll, selectorConstraintsToString,
} from "@medusajs/utils"
import {
@@ -64,6 +65,7 @@ import { TotalsContext, UpdateOrderInput } from "../types/orders"
import { CreateShippingMethodDto } from "../types/shipping-options"
import { buildQuery, isString, setMetadata } from "../utils"
import EventBusService from "./event-bus"
import { RemoteLink } from "@medusajs/modules-sdk"
export const ORDER_CART_ALREADY_EXISTS_ERROR = "Order from cart already exists"
@@ -90,6 +92,7 @@ type InjectedDependencies = {
eventBusService: EventBusService
featureFlagRouter: FlagRouter
productVariantInventoryService: ProductVariantInventoryService
remoteLink: RemoteLink
}
class OrderService extends TransactionBaseService {
@@ -132,6 +135,7 @@ class OrderService extends TransactionBaseService {
protected readonly inventoryService_: IInventoryService
protected readonly eventBus_: EventBusService
protected readonly featureFlagRouter_: FlagRouter
protected remoteLink_: RemoteLink
// eslint-disable-next-line max-len
protected readonly productVariantInventoryService_: ProductVariantInventoryService
@@ -150,6 +154,7 @@ class OrderService extends TransactionBaseService {
taxProviderService,
regionService,
cartService,
remoteLink,
addressRepository,
giftCardService,
draftOrderService,
@@ -180,6 +185,7 @@ class OrderService extends TransactionBaseService {
this.draftOrderService_ = draftOrderService
this.featureFlagRouter_ = featureFlagRouter
this.productVariantInventoryService_ = productVariantInventoryService
this.remoteLink_ = remoteLink
}
/**
@@ -693,7 +699,8 @@ class OrderService extends TransactionBaseService {
if (
cart.sales_channel_id &&
this.featureFlagRouter_.isFeatureEnabled(SalesChannelFeatureFlag.key)
this.featureFlagRouter_.isFeatureEnabled(SalesChannelFeatureFlag.key) &&
!this.featureFlagRouter_.isFeatureEnabled(MedusaV2Flag.key)
) {
toCreate.sales_channel_id = cart.sales_channel_id
}
@@ -710,6 +717,22 @@ class OrderService extends TransactionBaseService {
const rawOrder = orderRepo.create(toCreate)
const order = await orderRepo.save(rawOrder)
if (
this.featureFlagRouter_.isFeatureEnabled([
SalesChannelFeatureFlag.key,
MedusaV2Flag.key,
])
) {
await this.remoteLink_.create({
orderService: {
order_id: order.id,
},
salesChannelService: {
sales_channel_id: cart.sales_channel_id as string,
},
})
}
if (total !== 0 && payment) {
await this.paymentProviderService_
.withTransaction(manager)
@@ -2082,7 +2105,7 @@ class OrderService extends TransactionBaseService {
relationSet.add("shipping_methods.tax_lines")
relationSet.add("region")
relationSet.add("payments")
return Array.from(relationSet.values())
}
}

View File

@@ -51,7 +51,7 @@ export function FeatureFlagDecorators(
}
export function FeatureFlagClassDecorators(
featureFlag: string,
featureFlag: string | string[],
decorators: ClassDecorator[]
): ClassDecorator {
return function (target) {