diff --git a/packages/medusa/src/loaders/feature-flags/sales-channels.ts b/packages/medusa/src/loaders/feature-flags/sales-channels.ts new file mode 100644 index 0000000000..908a693c60 --- /dev/null +++ b/packages/medusa/src/loaders/feature-flags/sales-channels.ts @@ -0,0 +1,10 @@ +import { FlagSettings } from "../../types/feature-flags" + +const SalesChannelFeatureFlag: FlagSettings = { + key: "sales_channels", + default_val: false, + env_key: "MEDUSA_FF_SALES_CHANNELS", + description: "[WIP] Enable the sales channels feature", +} + +export default SalesChannelFeatureFlag diff --git a/packages/medusa/src/migrations/1656949291839-sales_channel.ts b/packages/medusa/src/migrations/1656949291839-sales_channel.ts new file mode 100644 index 0000000000..2179e5bcdc --- /dev/null +++ b/packages/medusa/src/migrations/1656949291839-sales_channel.ts @@ -0,0 +1,73 @@ +import { MigrationInterface, QueryRunner } from "typeorm" + +export const featureFlag = "sales_channels" + +export class salesChannel1656949291839 implements MigrationInterface { + name = "salesChannel1656949291839" + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TABLE "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, "name" character varying NOT NULL, "description" character varying, "is_disabled" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_d1eb0b923ea5a0eb1e0916191f1" PRIMARY KEY ("id"))` + ) + await queryRunner.query( + `CREATE TABLE "product_sales_channel" ("product_id" character varying NOT NULL, "sales_channel_id" character varying NOT NULL, CONSTRAINT "PK_fd29b6a8bd641052628dee19583" PRIMARY KEY ("product_id", "sales_channel_id"))` + ) + + await queryRunner.query( + `CREATE INDEX "IDX_5a4d5e1e60f97633547821ec8d" ON "product_sales_channel" ("product_id") ` + ) + await queryRunner.query( + `CREATE INDEX "IDX_37341bad297fe5cca91f921032" ON "product_sales_channel" ("sales_channel_id") ` + ) + + await queryRunner.query( + `ALTER TABLE "cart" ADD "sales_channel_id" character varying` + ) + await queryRunner.query( + `ALTER TABLE "order" ADD "sales_channel_id" character varying` + ) + + await queryRunner.query( + `ALTER TABLE "store" ADD "default_sales_channel_id" character varying` + ) + await queryRunner.query( + `ALTER TABLE "store" ADD CONSTRAINT "UQ_61b0f48cccbb5f41c750bac7286" UNIQUE ("default_sales_channel_id")` + ) + + await queryRunner.query( + `ALTER TABLE "cart" ADD CONSTRAINT "FK_a2bd3c26f42e754b9249ba78fd6" FOREIGN KEY ("sales_channel_id") REFERENCES "sales_channel"("id") ON DELETE NO ACTION ON UPDATE NO ACTION` + ) + await queryRunner.query( + `ALTER TABLE "order" ADD CONSTRAINT "FK_6ff7e874f01b478c115fdd462eb" FOREIGN KEY ("sales_channel_id") REFERENCES "sales_channel"("id") ON DELETE NO ACTION ON UPDATE NO ACTION` + ) + await queryRunner.query( + `ALTER TABLE "store" ADD CONSTRAINT "FK_61b0f48cccbb5f41c750bac7286" FOREIGN KEY ("default_sales_channel_id") REFERENCES "sales_channel"("id") ON DELETE NO ACTION ON UPDATE NO ACTION` + ) + + await queryRunner.query( + `ALTER TABLE "product_sales_channel" ADD CONSTRAINT "FK_5a4d5e1e60f97633547821ec8d6" FOREIGN KEY ("product_id") REFERENCES "product"("id") ON DELETE CASCADE ON UPDATE CASCADE` + ) + await queryRunner.query( + `ALTER TABLE "product_sales_channel" ADD CONSTRAINT "FK_37341bad297fe5cca91f921032b" FOREIGN KEY ("sales_channel_id") REFERENCES "sales_channel"("id") ON DELETE CASCADE ON UPDATE CASCADE` + ) + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "product_sales_channel" DROP CONSTRAINT "FK_37341bad297fe5cca91f921032b"` + ) + await queryRunner.query( + `ALTER TABLE "product_sales_channel" DROP CONSTRAINT "FK_5a4d5e1e60f97633547821ec8d6"` + ) + + await queryRunner.query( + `ALTER TABLE "store" DROP COLUMN "default_sales_channel_id"` + ) + await queryRunner.query( + `ALTER TABLE "order" DROP COLUMN "sales_channel_id"` + ) + await queryRunner.query(`ALTER TABLE "cart" DROP COLUMN "sales_channel_id"`) + await queryRunner.query(`DROP TABLE "product_sales_channel"`) + await queryRunner.query(`DROP TABLE "sales_channel"`) + } +} diff --git a/packages/medusa/src/models/batch-job.ts b/packages/medusa/src/models/batch-job.ts index 921e35f91e..ec5781d7a7 100644 --- a/packages/medusa/src/models/batch-job.ts +++ b/packages/medusa/src/models/batch-job.ts @@ -15,7 +15,6 @@ import { DbAwareColumn, resolveDbType } from "../utils/db-aware-column" import { SoftDeletableEntity } from "../interfaces/models/soft-deletable-entity" import { generateEntityId } from "../utils/generate-entity-id" import { User } from "./user" -import { RequestQueryFields, Selector } from "../types/common" @Entity() export class BatchJob extends SoftDeletableEntity { diff --git a/packages/medusa/src/models/cart.ts b/packages/medusa/src/models/cart.ts index e6c31e8f46..ba4ea47880 100644 --- a/packages/medusa/src/models/cart.ts +++ b/packages/medusa/src/models/cart.ts @@ -107,6 +107,11 @@ import { Region } from "./region" import { ShippingMethod } from "./shipping-method" import { SoftDeletableEntity } from "../interfaces/models/soft-deletable-entity" import { generateEntityId } from "../utils/generate-entity-id" +import { + FeatureFlagColumn, + FeatureFlagDecorators, +} from "../utils/feature-flag-decorators" +import { SalesChannel } from "./sales-channel" export enum CartType { DEFAULT = "default", @@ -230,6 +235,15 @@ export class Cart extends SoftDeletableEntity { @DbAwareColumn({ type: "jsonb", nullable: true }) metadata: Record + @FeatureFlagColumn("sales_channels", { type: "varchar", nullable: true }) + sales_channel_id: string | null + + @FeatureFlagDecorators("sales_channels", [ + ManyToOne(() => SalesChannel), + JoinColumn({ name: "sales_channel_id" }), + ]) + sales_channel: SalesChannel + shipping_total?: number discount_total?: number tax_total?: number | null diff --git a/packages/medusa/src/models/order.ts b/packages/medusa/src/models/order.ts index 3bd27b5c27..7bb16a3242 100644 --- a/packages/medusa/src/models/order.ts +++ b/packages/medusa/src/models/order.ts @@ -36,6 +36,11 @@ import { Return } from "./return" import { ShippingMethod } from "./shipping-method" import { Swap } from "./swap" import { generateEntityId } from "../utils/generate-entity-id" +import { + FeatureFlagColumn, + FeatureFlagDecorators, +} from "../utils/feature-flag-decorators" +import { SalesChannel } from "./sales-channel" export enum OrderStatus { PENDING = "pending", @@ -225,6 +230,15 @@ export class Order extends BaseEntity { @Column({ type: "varchar", nullable: true }) external_id: string | null + @FeatureFlagColumn("sales_channels", { type: "varchar", nullable: true }) + sales_channel_id: string | null + + @FeatureFlagDecorators("sales_channels", [ + ManyToOne(() => SalesChannel), + JoinColumn({ name: "sales_channel_id" }), + ]) + sales_channel: SalesChannel + // Total fields shipping_total: number discount_total: number diff --git a/packages/medusa/src/models/product.ts b/packages/medusa/src/models/product.ts index f3d6f84aa6..85e1248961 100644 --- a/packages/medusa/src/models/product.ts +++ b/packages/medusa/src/models/product.ts @@ -9,6 +9,7 @@ import { ManyToMany, ManyToOne, OneToMany, + OneToOne, } from "typeorm" import { DbAwareColumn } from "../utils/db-aware-column" import { Image } from "./image" @@ -20,6 +21,11 @@ import { ProductVariant } from "./product-variant" import { ShippingProfile } from "./shipping-profile" import { SoftDeletableEntity } from "../interfaces/models/soft-deletable-entity" import { generateEntityId } from "../utils/generate-entity-id" +import { + FeatureFlagColumn, + FeatureFlagDecorators, +} from "../utils/feature-flag-decorators" +import { SalesChannel } from "./sales-channel" export enum ProductStatus { DRAFT = "draft", @@ -66,19 +72,12 @@ export class Product extends SoftDeletableEntity { @Column({ type: "text", nullable: true }) thumbnail: string | null - @OneToMany( - () => ProductOption, - (productOption) => productOption.product - ) + @OneToMany(() => ProductOption, (productOption) => productOption.product) options: ProductOption[] - @OneToMany( - () => ProductVariant, - (variant) => variant.product, - { - cascade: true, - } - ) + @OneToMany(() => ProductVariant, (variant) => variant.product, { + cascade: true, + }) variants: ProductVariant[] @Index() @@ -150,6 +149,22 @@ export class Product extends SoftDeletableEntity { @DbAwareColumn({ type: "jsonb", nullable: true }) metadata: Record | null + @FeatureFlagDecorators("sales_channels", [ + ManyToMany(() => SalesChannel, { cascade: true }), + JoinTable({ + name: "product_sales_channel", + joinColumn: { + name: "product_id", + referencedColumnName: "id", + }, + inverseJoinColumn: { + name: "sales_channel_id", + referencedColumnName: "id", + }, + }), + ]) + sales_channels: SalesChannel[] + @BeforeInsert() private beforeInsert(): void { if (this.id) return diff --git a/packages/medusa/src/models/sales-channel.ts b/packages/medusa/src/models/sales-channel.ts new file mode 100644 index 0000000000..653ca57386 --- /dev/null +++ b/packages/medusa/src/models/sales-channel.ts @@ -0,0 +1,26 @@ +import { BeforeInsert, Column } from "typeorm" + +import { SoftDeletableEntity } from "../interfaces" +import { FeatureFlagEntity } from "../utils/feature-flag-decorators" +import { resolveDbType } from "../utils/db-aware-column" +import { generateEntityId } from "../utils" + +@FeatureFlagEntity("sales_channels") +export class SalesChannel extends SoftDeletableEntity { + @Column() + name: string + + @Column({ type: "varchar", nullable: true }) + description: string | null + + @Column({ default: false }) + is_disabled: boolean + + // @Column({ type: resolveDbType("timestamptz"), nullable: true }) + // disabled_at: Date | null + + @BeforeInsert() + private beforeInsert(): void { + this.id = generateEntityId(this.id, "sc") + } +} diff --git a/packages/medusa/src/models/store.ts b/packages/medusa/src/models/store.ts index 8ee323f6e7..d5c4f0cb04 100644 --- a/packages/medusa/src/models/store.ts +++ b/packages/medusa/src/models/store.ts @@ -6,12 +6,18 @@ import { JoinTable, ManyToMany, ManyToOne, + OneToOne, } from "typeorm" import { BaseEntity } from "../interfaces/models/base-entity" import { DbAwareColumn } from "../utils/db-aware-column" import { Currency } from "./currency" import { generateEntityId } from "../utils/generate-entity-id" +import { SalesChannel } from "./sales-channel" +import { + FeatureFlagColumn, + FeatureFlagDecorators, +} from "../utils/feature-flag-decorators" @Entity() export class Store extends BaseEntity { @@ -51,6 +57,15 @@ export class Store extends BaseEntity { @DbAwareColumn({ type: "jsonb", nullable: true }) metadata: Record + @FeatureFlagColumn("sales_channels", { nullable: true }) + default_sales_channel_id: string + + @FeatureFlagDecorators("sales_channels", [ + OneToOne(() => SalesChannel), + JoinColumn({ name: "default_sales_channel_id" }), + ]) + default_sales_channel: SalesChannel + @BeforeInsert() private beforeInsert(): void { this.id = generateEntityId(this.id, "store") diff --git a/packages/medusa/src/repositories/sales-channel.ts b/packages/medusa/src/repositories/sales-channel.ts new file mode 100644 index 0000000000..701c3fd661 --- /dev/null +++ b/packages/medusa/src/repositories/sales-channel.ts @@ -0,0 +1,5 @@ +import { EntityRepository, Repository } from "typeorm" +import { SalesChannel } from "../models/sales-channel" + +@EntityRepository(SalesChannel) +export class SalesChannelRepository extends Repository {} diff --git a/packages/medusa/src/utils/feature-flag-decorators.ts b/packages/medusa/src/utils/feature-flag-decorators.ts index 0e634634cc..b89a88f8b9 100644 --- a/packages/medusa/src/utils/feature-flag-decorators.ts +++ b/packages/medusa/src/utils/feature-flag-decorators.ts @@ -7,7 +7,7 @@ import { FlagRouter } from "./flag-router" export function FeatureFlagColumn( featureFlag: string, - columnOptions: ColumnOptions + columnOptions: ColumnOptions = {} ): PropertyDecorator { const featureFlagRouter = getFeatureFlagRouter()