chore(inventory): convert to dml (#10569)
Fixes: FRMW-2848 Co-authored-by: Harminder Virk <1706381+thetutlage@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
ae1d875fcf
commit
729eb5da7b
7
.changeset/big-days-train.md
Normal file
7
.changeset/big-days-train.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@medusajs/inventory": patch
|
||||
"@medusajs/types": patch
|
||||
"@medusajs/utils": patch
|
||||
---
|
||||
|
||||
chore: Inventory DML
|
||||
@@ -1353,7 +1353,7 @@ medusaIntegrationTestRunner({
|
||||
{ title: "variant two", options: { color: "blue" } },
|
||||
],
|
||||
})
|
||||
console.log(product)
|
||||
|
||||
const [variantOne, variantTwo] = product.variants
|
||||
|
||||
const [itemOne, itemTwo, itemThree] =
|
||||
|
||||
@@ -72,6 +72,7 @@ export type PropertyMetadata = {
|
||||
fieldName: string
|
||||
defaultValue?: any
|
||||
nullable: boolean
|
||||
computed: boolean
|
||||
dataType: {
|
||||
name: KnownDataTypes
|
||||
options?: Record<string, any>
|
||||
|
||||
@@ -12,6 +12,7 @@ describe("Array property", () => {
|
||||
name: "array",
|
||||
},
|
||||
nullable: false,
|
||||
computed: false,
|
||||
indexes: [],
|
||||
relationships: [],
|
||||
})
|
||||
|
||||
@@ -13,6 +13,7 @@ describe("Autoincrement property", () => {
|
||||
options: {},
|
||||
},
|
||||
nullable: false,
|
||||
computed: false,
|
||||
indexes: [],
|
||||
relationships: [],
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { PropertyMetadata } from "@medusajs/types"
|
||||
import { expectTypeOf } from "expect-type"
|
||||
import { BaseProperty } from "../properties/base"
|
||||
import { PropertyMetadata } from "@medusajs/types"
|
||||
import { TextProperty } from "../properties/text"
|
||||
|
||||
describe("Base property", () => {
|
||||
@@ -20,6 +20,7 @@ describe("Base property", () => {
|
||||
name: "text",
|
||||
},
|
||||
nullable: false,
|
||||
computed: false,
|
||||
indexes: [],
|
||||
relationships: [],
|
||||
})
|
||||
@@ -38,6 +39,7 @@ describe("Base property", () => {
|
||||
},
|
||||
},
|
||||
nullable: false,
|
||||
computed: false,
|
||||
indexes: [],
|
||||
relationships: [],
|
||||
})
|
||||
@@ -59,6 +61,42 @@ describe("Base property", () => {
|
||||
name: "text",
|
||||
},
|
||||
nullable: true,
|
||||
computed: false,
|
||||
indexes: [],
|
||||
relationships: [],
|
||||
})
|
||||
})
|
||||
|
||||
test("apply computed property", () => {
|
||||
class StringProperty extends BaseProperty<string> {
|
||||
protected dataType: PropertyMetadata["dataType"] = {
|
||||
name: "text",
|
||||
}
|
||||
}
|
||||
|
||||
const property = new StringProperty().computed()
|
||||
const property2 = new StringProperty().nullable().computed()
|
||||
|
||||
expectTypeOf(property["$dataType"]).toEqualTypeOf<string | null>()
|
||||
expect(property.parse("username")).toEqual({
|
||||
fieldName: "username",
|
||||
dataType: {
|
||||
name: "text",
|
||||
},
|
||||
nullable: false,
|
||||
computed: true,
|
||||
indexes: [],
|
||||
relationships: [],
|
||||
})
|
||||
|
||||
expectTypeOf(property2["$dataType"]).toEqualTypeOf<string | null>()
|
||||
expect(property2.parse("username")).toEqual({
|
||||
fieldName: "username",
|
||||
dataType: {
|
||||
name: "text",
|
||||
},
|
||||
nullable: true,
|
||||
computed: true,
|
||||
indexes: [],
|
||||
relationships: [],
|
||||
})
|
||||
@@ -81,6 +119,7 @@ describe("Base property", () => {
|
||||
},
|
||||
defaultValue: "foo",
|
||||
nullable: false,
|
||||
computed: false,
|
||||
indexes: [],
|
||||
relationships: [],
|
||||
})
|
||||
|
||||
@@ -12,6 +12,7 @@ describe("Big Number property", () => {
|
||||
name: "bigNumber",
|
||||
},
|
||||
nullable: false,
|
||||
computed: false,
|
||||
indexes: [],
|
||||
relationships: [],
|
||||
})
|
||||
|
||||
@@ -12,6 +12,7 @@ describe("Boolean property", () => {
|
||||
name: "boolean",
|
||||
},
|
||||
nullable: false,
|
||||
computed: false,
|
||||
indexes: [],
|
||||
relationships: [],
|
||||
})
|
||||
|
||||
@@ -12,6 +12,7 @@ describe("DateTime property", () => {
|
||||
name: "dateTime",
|
||||
},
|
||||
nullable: false,
|
||||
computed: false,
|
||||
indexes: [],
|
||||
relationships: [],
|
||||
})
|
||||
|
||||
@@ -17,6 +17,7 @@ describe("Enum property", () => {
|
||||
},
|
||||
},
|
||||
nullable: false,
|
||||
computed: false,
|
||||
indexes: [],
|
||||
relationships: [],
|
||||
})
|
||||
@@ -42,6 +43,7 @@ describe("Enum property", () => {
|
||||
},
|
||||
},
|
||||
nullable: true,
|
||||
computed: false,
|
||||
indexes: [],
|
||||
relationships: [],
|
||||
})
|
||||
@@ -66,6 +68,7 @@ describe("Enum property", () => {
|
||||
},
|
||||
},
|
||||
nullable: false,
|
||||
computed: false,
|
||||
indexes: [],
|
||||
relationships: [],
|
||||
})
|
||||
@@ -90,6 +93,7 @@ describe("Enum property", () => {
|
||||
},
|
||||
},
|
||||
nullable: false,
|
||||
computed: false,
|
||||
indexes: [],
|
||||
relationships: [],
|
||||
})
|
||||
|
||||
@@ -12,6 +12,7 @@ describe("Float property", () => {
|
||||
name: "float",
|
||||
},
|
||||
nullable: false,
|
||||
computed: false,
|
||||
indexes: [],
|
||||
relationships: [],
|
||||
})
|
||||
|
||||
@@ -13,6 +13,7 @@ describe("Id property", () => {
|
||||
options: {},
|
||||
},
|
||||
nullable: false,
|
||||
computed: false,
|
||||
indexes: [],
|
||||
relationships: [],
|
||||
})
|
||||
@@ -29,6 +30,7 @@ describe("Id property", () => {
|
||||
options: {},
|
||||
},
|
||||
nullable: false,
|
||||
computed: false,
|
||||
indexes: [],
|
||||
relationships: [],
|
||||
primaryKey: true,
|
||||
|
||||
@@ -12,6 +12,7 @@ describe("JSON property", () => {
|
||||
name: "json",
|
||||
},
|
||||
nullable: false,
|
||||
computed: false,
|
||||
indexes: [],
|
||||
relationships: [],
|
||||
})
|
||||
@@ -30,6 +31,7 @@ describe("JSON property", () => {
|
||||
a: 1,
|
||||
},
|
||||
nullable: false,
|
||||
computed: false,
|
||||
indexes: [],
|
||||
relationships: [],
|
||||
})
|
||||
|
||||
@@ -13,6 +13,7 @@ describe("Number property", () => {
|
||||
options: {},
|
||||
},
|
||||
nullable: false,
|
||||
computed: false,
|
||||
indexes: [],
|
||||
relationships: [],
|
||||
})
|
||||
|
||||
@@ -13,6 +13,7 @@ describe("Text property", () => {
|
||||
options: { searchable: false },
|
||||
},
|
||||
nullable: false,
|
||||
computed: false,
|
||||
indexes: [],
|
||||
relationships: [],
|
||||
})
|
||||
@@ -29,6 +30,7 @@ describe("Text property", () => {
|
||||
options: { searchable: false },
|
||||
},
|
||||
nullable: false,
|
||||
computed: false,
|
||||
indexes: [],
|
||||
relationships: [],
|
||||
primaryKey: true,
|
||||
|
||||
@@ -41,10 +41,16 @@ export function createBigNumberProperties<Schema extends DMLSchema>(
|
||||
continue
|
||||
}
|
||||
|
||||
const jsonProperty = parsed.nullable
|
||||
let jsonProperty = parsed.nullable
|
||||
? new JSONProperty().nullable()
|
||||
: new JSONProperty()
|
||||
|
||||
if (parsed.computed) {
|
||||
jsonProperty = jsonProperty.computed() as unknown as
|
||||
| JSONProperty
|
||||
| NullableModifier<Record<string, unknown>, JSONProperty>
|
||||
}
|
||||
|
||||
schemaWithBigNumber[`raw_${key}`] = jsonProperty
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,6 +140,10 @@ export function defineProperty(
|
||||
BeforeCreate()(MikroORMEntity.prototype, defaultValueSetterHookName)
|
||||
}
|
||||
|
||||
if (field.computed) {
|
||||
return
|
||||
}
|
||||
|
||||
if (SPECIAL_PROPERTIES[field.fieldName]) {
|
||||
SPECIAL_PROPERTIES[field.fieldName](MikroORMEntity, field, tableName)
|
||||
return
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { PropertyMetadata, PropertyType } from "@medusajs/types"
|
||||
import { ComputedProperty } from "./computed"
|
||||
import { NullableModifier } from "./nullable"
|
||||
|
||||
/**
|
||||
@@ -48,6 +49,27 @@ export abstract class BaseProperty<T> implements PropertyType<T> {
|
||||
return new NullableModifier<T, this>(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* This method indicated that the property is a computed property.
|
||||
* Computed properties are not stored in the database but are
|
||||
* computed on the fly.
|
||||
*
|
||||
* @example
|
||||
* import { model } from "@medusajs/framework/utils"
|
||||
*
|
||||
* const MyCustom = model.define("my_custom", {
|
||||
* calculated_price: model.bigNumber().computed(),
|
||||
* // ...
|
||||
* })
|
||||
*
|
||||
* export default MyCustom
|
||||
*
|
||||
* @customNamespace Property Configuration Methods
|
||||
*/
|
||||
computed() {
|
||||
return new ComputedProperty<T | null, this>(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* This method defines an index on a property.
|
||||
*
|
||||
@@ -132,6 +154,7 @@ export abstract class BaseProperty<T> implements PropertyType<T> {
|
||||
fieldName,
|
||||
dataType: this.dataType,
|
||||
nullable: false,
|
||||
computed: false,
|
||||
defaultValue: this.#defaultValue,
|
||||
indexes: this.#indexes,
|
||||
relationships: this.#relationships,
|
||||
|
||||
39
packages/core/utils/src/dml/properties/computed.ts
Normal file
39
packages/core/utils/src/dml/properties/computed.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { PropertyType } from "@medusajs/types"
|
||||
|
||||
const IsComputedProperty = Symbol.for("isComputedProperty")
|
||||
/**
|
||||
* Computed property marks a schema node as computed
|
||||
*/
|
||||
export class ComputedProperty<T, Schema extends PropertyType<T>>
|
||||
implements PropertyType<T | null>
|
||||
{
|
||||
[IsComputedProperty]: true = true
|
||||
|
||||
static isComputedProperty(obj: any): obj is ComputedProperty<any, any> {
|
||||
return !!obj?.[IsComputedProperty]
|
||||
}
|
||||
/**
|
||||
* A type-only property to infer the JavScript data-type
|
||||
* of the schema property
|
||||
*/
|
||||
declare $dataType: T | null
|
||||
|
||||
/**
|
||||
* The parent schema on which the computed property is
|
||||
* applied
|
||||
*/
|
||||
#schema: Schema
|
||||
|
||||
constructor(schema: Schema) {
|
||||
this.#schema = schema
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the serialized metadata
|
||||
*/
|
||||
parse(fieldName: string) {
|
||||
const schema = this.#schema.parse(fieldName)
|
||||
schema.computed = true
|
||||
return schema
|
||||
}
|
||||
}
|
||||
@@ -3,12 +3,13 @@ export * from "./autoincrement"
|
||||
export * from "./base"
|
||||
export * from "./big-number"
|
||||
export * from "./boolean"
|
||||
export * from "./computed"
|
||||
export * from "./date-time"
|
||||
export * from "./enum"
|
||||
export * from "./float"
|
||||
export * from "./id"
|
||||
export * from "./json"
|
||||
export * from "./nullable"
|
||||
export * from "./number"
|
||||
export * from "./float"
|
||||
export * from "./primary-key"
|
||||
export * from "./text"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { PropertyType } from "@medusajs/types"
|
||||
import { ComputedProperty } from "./computed"
|
||||
|
||||
const IsNullableModifier = Symbol.for("isNullableModifier")
|
||||
/**
|
||||
@@ -28,6 +29,13 @@ export class NullableModifier<T, Schema extends PropertyType<T>>
|
||||
this.#schema = schema
|
||||
}
|
||||
|
||||
/**
|
||||
* This method indicated that the property is a computed property.
|
||||
*/
|
||||
computed() {
|
||||
return new ComputedProperty<T | null, this>(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the serialized metadata
|
||||
*/
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { dropDatabase } from "pg-god"
|
||||
import {
|
||||
createClient,
|
||||
parseConnectionString,
|
||||
dbExists,
|
||||
createDb,
|
||||
dbExists,
|
||||
parseConnectionString,
|
||||
} from "../../index"
|
||||
|
||||
const DB_HOST = process.env.DB_HOST ?? "localhost"
|
||||
|
||||
@@ -229,6 +229,29 @@ moduleIntegrationTestRunner<IInventoryService>({
|
||||
expect(inventoryLevel).toEqual(
|
||||
expect.objectContaining({ id: expect.any(String), ...data })
|
||||
)
|
||||
|
||||
const getItems = await service.listInventoryItems(
|
||||
{},
|
||||
{
|
||||
select: [
|
||||
"id",
|
||||
"sku",
|
||||
"origin_country",
|
||||
"reserved_quantity",
|
||||
"stocked_quantity",
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
expect(getItems).toEqual([
|
||||
{
|
||||
id: inventoryItem.id,
|
||||
sku: "test-sku",
|
||||
origin_country: "test-country",
|
||||
reserved_quantity: 0,
|
||||
stocked_quantity: 2,
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
it("should create inventoryLevels from array", async () => {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
{
|
||||
"namespaces": ["public"],
|
||||
"namespaces": [
|
||||
"public"
|
||||
],
|
||||
"name": "public",
|
||||
"tables": [
|
||||
{
|
||||
@@ -13,38 +15,6 @@
|
||||
"nullable": false,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"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"
|
||||
},
|
||||
"sku": {
|
||||
"name": "sku",
|
||||
"type": "text",
|
||||
@@ -92,7 +62,7 @@
|
||||
},
|
||||
"weight": {
|
||||
"name": "weight",
|
||||
"type": "int",
|
||||
"type": "integer",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
@@ -101,7 +71,7 @@
|
||||
},
|
||||
"length": {
|
||||
"name": "length",
|
||||
"type": "int",
|
||||
"type": "integer",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
@@ -110,7 +80,7 @@
|
||||
},
|
||||
"height": {
|
||||
"name": "height",
|
||||
"type": "int",
|
||||
"type": "integer",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
@@ -119,7 +89,7 @@
|
||||
},
|
||||
"width": {
|
||||
"name": "width",
|
||||
"type": "int",
|
||||
"type": "integer",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
@@ -171,6 +141,38 @@
|
||||
"primary": false,
|
||||
"nullable": true,
|
||||
"mappedType": "json"
|
||||
},
|
||||
"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": "inventory_item",
|
||||
@@ -178,23 +180,33 @@
|
||||
"indexes": [
|
||||
{
|
||||
"keyName": "IDX_inventory_item_deleted_at",
|
||||
"columnNames": ["deleted_at"],
|
||||
"columnNames": [],
|
||||
"composite": false,
|
||||
"primary": false,
|
||||
"unique": false,
|
||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_inventory_item_deleted_at\" ON \"inventory_item\" (deleted_at) WHERE deleted_at IS NULL"
|
||||
},
|
||||
{
|
||||
"keyName": "IDX_inventory_item_deleted_at",
|
||||
"columnNames": [],
|
||||
"composite": false,
|
||||
"primary": false,
|
||||
"unique": false,
|
||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_inventory_item_deleted_at\" ON \"inventory_item\" (deleted_at) WHERE deleted_at IS NOT NULL"
|
||||
},
|
||||
{
|
||||
"keyName": "IDX_inventory_item_sku_unique",
|
||||
"columnNames": ["sku"],
|
||||
"keyName": "IDX_inventory_item_sku",
|
||||
"columnNames": [],
|
||||
"composite": false,
|
||||
"primary": false,
|
||||
"unique": false,
|
||||
"expression": "CREATE UNIQUE INDEX IF NOT EXISTS \"IDX_inventory_item_sku_unique\" ON \"inventory_item\" (sku)"
|
||||
"expression": "CREATE UNIQUE INDEX IF NOT EXISTS \"IDX_inventory_item_sku\" ON \"inventory_item\" (sku) WHERE deleted_at IS NULL"
|
||||
},
|
||||
{
|
||||
"keyName": "inventory_item_pkey",
|
||||
"columnNames": ["id"],
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"composite": false,
|
||||
"primary": true,
|
||||
"unique": true
|
||||
@@ -214,6 +226,90 @@
|
||||
"nullable": false,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"location_id": {
|
||||
"name": "location_id",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"stocked_quantity": {
|
||||
"name": "stocked_quantity",
|
||||
"type": "numeric",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"default": "0",
|
||||
"mappedType": "decimal"
|
||||
},
|
||||
"reserved_quantity": {
|
||||
"name": "reserved_quantity",
|
||||
"type": "numeric",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"default": "0",
|
||||
"mappedType": "decimal"
|
||||
},
|
||||
"incoming_quantity": {
|
||||
"name": "incoming_quantity",
|
||||
"type": "numeric",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"default": "0",
|
||||
"mappedType": "decimal"
|
||||
},
|
||||
"metadata": {
|
||||
"name": "metadata",
|
||||
"type": "jsonb",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": true,
|
||||
"mappedType": "json"
|
||||
},
|
||||
"inventory_item_id": {
|
||||
"name": "inventory_item_id",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"raw_stocked_quantity": {
|
||||
"name": "raw_stocked_quantity",
|
||||
"type": "jsonb",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"mappedType": "json"
|
||||
},
|
||||
"raw_reserved_quantity": {
|
||||
"name": "raw_reserved_quantity",
|
||||
"type": "jsonb",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"mappedType": "json"
|
||||
},
|
||||
"raw_incoming_quantity": {
|
||||
"name": "raw_incoming_quantity",
|
||||
"type": "jsonb",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"mappedType": "json"
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamptz",
|
||||
@@ -245,91 +341,26 @@
|
||||
"nullable": true,
|
||||
"length": 6,
|
||||
"mappedType": "datetime"
|
||||
},
|
||||
"inventory_item_id": {
|
||||
"name": "inventory_item_id",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"location_id": {
|
||||
"name": "location_id",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"stocked_quantity": {
|
||||
"name": "stocked_quantity",
|
||||
"type": "int",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"default": "0",
|
||||
"mappedType": "integer"
|
||||
},
|
||||
"reserved_quantity": {
|
||||
"name": "reserved_quantity",
|
||||
"type": "int",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"default": "0",
|
||||
"mappedType": "integer"
|
||||
},
|
||||
"incoming_quantity": {
|
||||
"name": "incoming_quantity",
|
||||
"type": "int",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"default": "0",
|
||||
"mappedType": "integer"
|
||||
},
|
||||
"metadata": {
|
||||
"name": "metadata",
|
||||
"type": "jsonb",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": true,
|
||||
"mappedType": "json"
|
||||
}
|
||||
},
|
||||
"name": "inventory_level",
|
||||
"schema": "public",
|
||||
"indexes": [
|
||||
{
|
||||
"keyName": "IDX_inventory_level_deleted_at",
|
||||
"columnNames": ["deleted_at"],
|
||||
"composite": false,
|
||||
"primary": false,
|
||||
"unique": false,
|
||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_inventory_level_deleted_at\" ON \"inventory_level\" (deleted_at) WHERE deleted_at IS NOT NULL"
|
||||
},
|
||||
{
|
||||
"keyName": "IDX_inventory_level_inventory_item_id",
|
||||
"columnNames": ["inventory_item_id"],
|
||||
"columnNames": [],
|
||||
"composite": false,
|
||||
"primary": false,
|
||||
"unique": false,
|
||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_inventory_level_inventory_item_id\" ON \"inventory_level\" (inventory_item_id)"
|
||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_inventory_level_inventory_item_id\" ON \"inventory_level\" (inventory_item_id) WHERE deleted_at IS NULL"
|
||||
},
|
||||
{
|
||||
"keyName": "IDX_inventory_level_location_id",
|
||||
"columnNames": ["location_id"],
|
||||
"keyName": "IDX_inventory_level_deleted_at",
|
||||
"columnNames": [],
|
||||
"composite": false,
|
||||
"primary": false,
|
||||
"unique": false,
|
||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_inventory_level_location_id\" ON \"inventory_level\" (location_id)"
|
||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_inventory_level_deleted_at\" ON \"inventory_level\" (deleted_at) WHERE deleted_at IS NULL"
|
||||
},
|
||||
{
|
||||
"keyName": "IDX_inventory_level_location_id",
|
||||
@@ -337,11 +368,21 @@
|
||||
"composite": false,
|
||||
"primary": false,
|
||||
"unique": false,
|
||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_inventory_level_location_id\" ON \"inventory_level\" (location_id)"
|
||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_inventory_level_location_id\" ON \"inventory_level\" (location_id) WHERE deleted_at IS NULL"
|
||||
},
|
||||
{
|
||||
"keyName": "IDX_inventory_level_location_id_inventory_item_id",
|
||||
"columnNames": [],
|
||||
"composite": false,
|
||||
"primary": false,
|
||||
"unique": false,
|
||||
"expression": "CREATE UNIQUE INDEX IF NOT EXISTS \"IDX_inventory_level_location_id_inventory_item_id\" ON \"inventory_level\" (inventory_item_id, location_id) WHERE deleted_at IS NULL"
|
||||
},
|
||||
{
|
||||
"keyName": "inventory_level_pkey",
|
||||
"columnNames": ["id"],
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"composite": false,
|
||||
"primary": true,
|
||||
"unique": true
|
||||
@@ -351,9 +392,13 @@
|
||||
"foreignKeys": {
|
||||
"inventory_level_inventory_item_id_foreign": {
|
||||
"constraintName": "inventory_level_inventory_item_id_foreign",
|
||||
"columnNames": ["inventory_item_id"],
|
||||
"columnNames": [
|
||||
"inventory_item_id"
|
||||
],
|
||||
"localTableName": "public.inventory_level",
|
||||
"referencedColumnNames": ["id"],
|
||||
"referencedColumnNames": [
|
||||
"id"
|
||||
],
|
||||
"referencedTableName": "public.inventory_item",
|
||||
"deleteRule": "cascade",
|
||||
"updateRule": "cascade"
|
||||
@@ -371,38 +416,6 @@
|
||||
"nullable": false,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"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"
|
||||
},
|
||||
"line_item_id": {
|
||||
"name": "line_item_id",
|
||||
"type": "text",
|
||||
@@ -412,6 +425,16 @@
|
||||
"nullable": true,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"allow_backorder": {
|
||||
"name": "allow_backorder",
|
||||
"type": "boolean",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"default": "false",
|
||||
"mappedType": "boolean"
|
||||
},
|
||||
"location_id": {
|
||||
"name": "location_id",
|
||||
"type": "text",
|
||||
@@ -423,12 +446,21 @@
|
||||
},
|
||||
"quantity": {
|
||||
"name": "quantity",
|
||||
"type": "integer",
|
||||
"type": "numeric",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"mappedType": "integer"
|
||||
"mappedType": "decimal"
|
||||
},
|
||||
"raw_quantity": {
|
||||
"name": "raw_quantity",
|
||||
"type": "jsonb",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"mappedType": "json"
|
||||
},
|
||||
"external_id": {
|
||||
"name": "external_id",
|
||||
@@ -474,46 +506,80 @@
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"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": "reservation_item",
|
||||
"schema": "public",
|
||||
"indexes": [
|
||||
{
|
||||
"keyName": "IDX_reservation_item_deleted_at",
|
||||
"columnNames": ["deleted_at"],
|
||||
"keyName": "IDX_reservation_item_inventory_item_id",
|
||||
"columnNames": [],
|
||||
"composite": false,
|
||||
"primary": false,
|
||||
"unique": false,
|
||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_reservation_item_deleted_at\" ON \"reservation_item\" (deleted_at) WHERE deleted_at IS NOT NULL"
|
||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_reservation_item_inventory_item_id\" ON \"reservation_item\" (inventory_item_id) WHERE deleted_at IS NULL"
|
||||
},
|
||||
{
|
||||
"keyName": "IDX_reservation_item_deleted_at",
|
||||
"columnNames": [],
|
||||
"composite": false,
|
||||
"primary": false,
|
||||
"unique": false,
|
||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_reservation_item_deleted_at\" ON \"reservation_item\" (deleted_at) WHERE deleted_at IS NULL"
|
||||
},
|
||||
{
|
||||
"keyName": "IDX_reservation_item_line_item_id",
|
||||
"columnNames": ["line_item_id"],
|
||||
"columnNames": [],
|
||||
"composite": false,
|
||||
"primary": false,
|
||||
"unique": false,
|
||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_reservation_item_line_item_id\" ON \"reservation_item\" (line_item_id)"
|
||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_reservation_item_line_item_id\" ON \"reservation_item\" (line_item_id) WHERE deleted_at IS NULL"
|
||||
},
|
||||
{
|
||||
"keyName": "IDX_reservation_item_location_id",
|
||||
"columnNames": ["location_id"],
|
||||
"columnNames": [],
|
||||
"composite": false,
|
||||
"primary": false,
|
||||
"unique": false,
|
||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_reservation_item_location_id\" ON \"reservation_item\" (location_id)"
|
||||
},
|
||||
{
|
||||
"keyName": "IDX_reservation_item_inventory_item_id",
|
||||
"columnNames": ["inventory_item_id"],
|
||||
"composite": false,
|
||||
"primary": false,
|
||||
"unique": false,
|
||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_reservation_item_inventory_item_id\" ON \"reservation_item\" (inventory_item_id)"
|
||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_reservation_item_location_id\" ON \"reservation_item\" (location_id) WHERE deleted_at IS NULL"
|
||||
},
|
||||
{
|
||||
"keyName": "reservation_item_pkey",
|
||||
"columnNames": ["id"],
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"composite": false,
|
||||
"primary": true,
|
||||
"unique": true
|
||||
@@ -523,9 +589,13 @@
|
||||
"foreignKeys": {
|
||||
"reservation_item_inventory_item_id_foreign": {
|
||||
"constraintName": "reservation_item_inventory_item_id_foreign",
|
||||
"columnNames": ["inventory_item_id"],
|
||||
"columnNames": [
|
||||
"inventory_item_id"
|
||||
],
|
||||
"localTableName": "public.reservation_item",
|
||||
"referencedColumnNames": ["id"],
|
||||
"referencedColumnNames": [
|
||||
"id"
|
||||
],
|
||||
"referencedTableName": "public.inventory_item",
|
||||
"deleteRule": "cascade",
|
||||
"updateRule": "cascade"
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
import { Migration } from "@mikro-orm/migrations"
|
||||
|
||||
export class Migration20241213063611 extends Migration {
|
||||
async up(): Promise<void> {
|
||||
this.addSql('drop index if exists "IDX_inventory_item_sku_unique";')
|
||||
this.addSql(
|
||||
'CREATE UNIQUE INDEX IF NOT EXISTS "IDX_inventory_item_sku" ON "inventory_item" (sku) WHERE deleted_at IS NULL;'
|
||||
)
|
||||
|
||||
this.addSql(
|
||||
'alter table if exists "inventory_level" add column if not exists "raw_stocked_quantity" jsonb not null, add column if not exists "raw_reserved_quantity" jsonb not null, add column if not exists "raw_incoming_quantity" jsonb not null;'
|
||||
)
|
||||
this.addSql(
|
||||
'alter table if exists "inventory_level" alter column "stocked_quantity" type numeric using ("stocked_quantity"::numeric);'
|
||||
)
|
||||
this.addSql(
|
||||
'alter table if exists "inventory_level" alter column "reserved_quantity" type numeric using ("reserved_quantity"::numeric);'
|
||||
)
|
||||
this.addSql(
|
||||
'alter table if exists "inventory_level" alter column "incoming_quantity" type numeric using ("incoming_quantity"::numeric);'
|
||||
)
|
||||
this.addSql(
|
||||
'CREATE UNIQUE INDEX IF NOT EXISTS "IDX_inventory_level_location_id_inventory_item_id" ON "inventory_level" (inventory_item_id, location_id) WHERE deleted_at IS NULL;'
|
||||
)
|
||||
|
||||
this.addSql(
|
||||
'alter table if exists "reservation_item" add column if not exists "allow_backorder" boolean not null default false, add column if not exists "raw_quantity" jsonb not null;'
|
||||
)
|
||||
this.addSql(
|
||||
'alter table if exists "reservation_item" alter column "quantity" type numeric using ("quantity"::numeric);'
|
||||
)
|
||||
}
|
||||
|
||||
async down(): Promise<void> {
|
||||
this.addSql('drop index if exists "IDX_inventory_item_sku";')
|
||||
this.addSql(
|
||||
'CREATE UNIQUE INDEX IF NOT EXISTS "IDX_inventory_item_sku_unique" ON "inventory_item" (sku);'
|
||||
)
|
||||
|
||||
this.addSql(
|
||||
'alter table if exists "inventory_level" alter column "stocked_quantity" type int using ("stocked_quantity"::int);'
|
||||
)
|
||||
this.addSql(
|
||||
'alter table if exists "inventory_level" alter column "reserved_quantity" type int using ("reserved_quantity"::int);'
|
||||
)
|
||||
this.addSql(
|
||||
'alter table if exists "inventory_level" alter column "incoming_quantity" type int using ("incoming_quantity"::int);'
|
||||
)
|
||||
this.addSql(
|
||||
'drop index if exists "IDX_inventory_level_location_id_inventory_item_id";'
|
||||
)
|
||||
this.addSql(
|
||||
'alter table if exists "inventory_level" drop column if exists "raw_stocked_quantity";'
|
||||
)
|
||||
this.addSql(
|
||||
'alter table if exists "inventory_level" drop column if exists "raw_reserved_quantity";'
|
||||
)
|
||||
this.addSql(
|
||||
'alter table if exists "inventory_level" drop column if exists "raw_incoming_quantity";'
|
||||
)
|
||||
|
||||
this.addSql(
|
||||
'alter table if exists "reservation_item" alter column "quantity" type integer using ("quantity"::integer);'
|
||||
)
|
||||
this.addSql(
|
||||
'alter table if exists "reservation_item" drop column if exists "allow_backorder";'
|
||||
)
|
||||
this.addSql(
|
||||
'alter table if exists "reservation_item" drop column if exists "raw_quantity";'
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
export * from "./reservation-item"
|
||||
export * from "./inventory-item"
|
||||
export * from "./inventory-level"
|
||||
export { default as InventoryItem } from "./inventory-item"
|
||||
export { default as InventoryLevel } from "./inventory-level"
|
||||
export { default as ReservationItem } from "./reservation-item"
|
||||
|
||||
@@ -1,156 +1,43 @@
|
||||
import {
|
||||
createPsqlIndexStatementHelper,
|
||||
DALUtils,
|
||||
generateEntityId,
|
||||
Searchable,
|
||||
} from "@medusajs/framework/utils"
|
||||
import {
|
||||
BeforeCreate,
|
||||
Collection,
|
||||
Entity,
|
||||
Filter,
|
||||
Formula,
|
||||
OneToMany,
|
||||
OnInit,
|
||||
OptionalProps,
|
||||
PrimaryKey,
|
||||
Property,
|
||||
Rel,
|
||||
} from "@mikro-orm/core"
|
||||
import { model } from "@medusajs/framework/utils"
|
||||
import InventoryLevel from "./inventory-level"
|
||||
import ReservationItem from "./reservation-item"
|
||||
|
||||
import { DAL } from "@medusajs/framework/types"
|
||||
import { InventoryLevel } from "./inventory-level"
|
||||
import { ReservationItem } from "./reservation-item"
|
||||
|
||||
const InventoryItemDeletedAtIndex = createPsqlIndexStatementHelper({
|
||||
tableName: "inventory_item",
|
||||
columns: "deleted_at",
|
||||
where: "deleted_at IS NOT NULL",
|
||||
})
|
||||
|
||||
const InventoryItemSkuIndex = createPsqlIndexStatementHelper({
|
||||
tableName: "inventory_item",
|
||||
columns: "sku",
|
||||
unique: true,
|
||||
where: "deleted_at IS NULL",
|
||||
})
|
||||
|
||||
type InventoryItemOptionalProps = DAL.SoftDeletableModelDateColumns
|
||||
|
||||
@Entity()
|
||||
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
|
||||
export class InventoryItem {
|
||||
[OptionalProps]: InventoryItemOptionalProps
|
||||
|
||||
@PrimaryKey({ columnType: "text" })
|
||||
id: string
|
||||
|
||||
@Property({
|
||||
onCreate: () => new Date(),
|
||||
columnType: "timestamptz",
|
||||
defaultRaw: "now()",
|
||||
const InventoryItem = model
|
||||
.define("InventoryItem", {
|
||||
id: model.id({ prefix: "iitem" }).primaryKey(),
|
||||
sku: model.text().searchable().nullable(),
|
||||
origin_country: model.text().nullable(),
|
||||
hs_code: model.text().searchable().nullable(),
|
||||
mid_code: model.text().searchable().nullable(),
|
||||
material: model.text().nullable(),
|
||||
weight: model.number().nullable(),
|
||||
length: model.number().nullable(),
|
||||
height: model.number().nullable(),
|
||||
width: model.number().nullable(),
|
||||
requires_shipping: model.boolean().default(true),
|
||||
description: model.text().searchable().nullable(),
|
||||
title: model.text().searchable().nullable(),
|
||||
thumbnail: model.text().nullable(),
|
||||
metadata: model.json().nullable(),
|
||||
location_levels: model.hasMany(() => InventoryLevel, {
|
||||
mappedBy: "inventory_item",
|
||||
}),
|
||||
reservation_items: model.hasMany(() => ReservationItem, {
|
||||
mappedBy: "inventory_item",
|
||||
}),
|
||||
reserved_quantity: model.number().computed(),
|
||||
stocked_quantity: model.number().computed(),
|
||||
})
|
||||
created_at: Date
|
||||
|
||||
@Property({
|
||||
onCreate: () => new Date(),
|
||||
onUpdate: () => new Date(),
|
||||
columnType: "timestamptz",
|
||||
defaultRaw: "now()",
|
||||
.cascades({
|
||||
delete: ["location_levels", "reservation_items"],
|
||||
})
|
||||
updated_at: Date
|
||||
|
||||
@InventoryItemDeletedAtIndex.MikroORMIndex()
|
||||
@Property({ columnType: "timestamptz", nullable: true })
|
||||
deleted_at: Date | null = null
|
||||
|
||||
@InventoryItemSkuIndex.MikroORMIndex()
|
||||
@Searchable()
|
||||
@Property({ columnType: "text", nullable: true })
|
||||
sku: string | null = null
|
||||
|
||||
@Property({ columnType: "text", nullable: true })
|
||||
origin_country: string | null = null
|
||||
|
||||
@Searchable()
|
||||
@Property({ columnType: "text", nullable: true })
|
||||
hs_code: string | null = null
|
||||
|
||||
@Searchable()
|
||||
@Property({ columnType: "text", nullable: true })
|
||||
mid_code: string | null = null
|
||||
|
||||
@Property({ columnType: "text", nullable: true })
|
||||
material: string | null = null
|
||||
|
||||
@Property({ type: "int", nullable: true })
|
||||
weight: number | null = null
|
||||
|
||||
@Property({ type: "int", nullable: true })
|
||||
length: number | null = null
|
||||
|
||||
@Property({ type: "int", nullable: true })
|
||||
height: number | null = null
|
||||
|
||||
@Property({ type: "int", nullable: true })
|
||||
width: number | null = null
|
||||
|
||||
@Property({ columnType: "boolean" })
|
||||
requires_shipping: boolean = true
|
||||
|
||||
@Searchable()
|
||||
@Property({ columnType: "text", nullable: true })
|
||||
description: string | null = null
|
||||
|
||||
@Searchable()
|
||||
@Property({ columnType: "text", nullable: true })
|
||||
title: string | null = null
|
||||
|
||||
@Property({ columnType: "text", nullable: true })
|
||||
thumbnail: string | null = null
|
||||
|
||||
@Property({ columnType: "jsonb", nullable: true })
|
||||
metadata: Record<string, unknown> | null = null
|
||||
|
||||
@OneToMany(
|
||||
() => InventoryLevel,
|
||||
(inventoryLevel) => inventoryLevel.inventory_item,
|
||||
.indexes([
|
||||
{
|
||||
cascade: ["soft-remove" as any],
|
||||
}
|
||||
)
|
||||
location_levels = new Collection<Rel<InventoryLevel>>(this)
|
||||
name: "IDX_inventory_item_sku",
|
||||
on: ["sku"],
|
||||
unique: true,
|
||||
where: "deleted_at IS NULL",
|
||||
},
|
||||
])
|
||||
|
||||
@OneToMany(
|
||||
() => ReservationItem,
|
||||
(reservationItem) => reservationItem.inventory_item,
|
||||
{
|
||||
cascade: ["soft-remove" as any],
|
||||
}
|
||||
)
|
||||
reservation_items = new Collection<Rel<ReservationItem>>(this)
|
||||
|
||||
@Formula(
|
||||
(item) =>
|
||||
`(SELECT SUM(reserved_quantity) FROM inventory_level il WHERE il.inventory_item_id = ${item}.id AND il.deleted_at IS NULL)`,
|
||||
{ lazy: true, serializer: Number, hidden: true }
|
||||
)
|
||||
reserved_quantity: number
|
||||
|
||||
@Formula(
|
||||
(item) =>
|
||||
`(SELECT SUM(stocked_quantity) FROM inventory_level il WHERE il.inventory_item_id = ${item}.id AND il.deleted_at IS NULL)`,
|
||||
{ lazy: true, serializer: Number, hidden: true }
|
||||
)
|
||||
stocked_quantity: number
|
||||
|
||||
@BeforeCreate()
|
||||
beforeCreate(): void {
|
||||
this.id = generateEntityId(this.id, "iitem")
|
||||
}
|
||||
|
||||
@OnInit()
|
||||
onInit(): void {
|
||||
this.id = generateEntityId(this.id, "iitem")
|
||||
}
|
||||
}
|
||||
export default InventoryItem
|
||||
|
||||
@@ -1,135 +1,36 @@
|
||||
import { DALUtils, isDefined, MathBN } from "@medusajs/framework/utils"
|
||||
import {
|
||||
BeforeCreate,
|
||||
Entity,
|
||||
Filter,
|
||||
ManyToOne,
|
||||
OnInit,
|
||||
OnLoad,
|
||||
PrimaryKey,
|
||||
Property,
|
||||
Rel,
|
||||
} from "@mikro-orm/core"
|
||||
import { model } from "@medusajs/framework/utils"
|
||||
import InventoryItem from "./inventory-item"
|
||||
|
||||
import { BigNumberRawValue } from "@medusajs/framework/types"
|
||||
import {
|
||||
BigNumber,
|
||||
createPsqlIndexStatementHelper,
|
||||
generateEntityId,
|
||||
MikroOrmBigNumberProperty,
|
||||
} from "@medusajs/framework/utils"
|
||||
import { InventoryItem } from "./inventory-item"
|
||||
|
||||
const InventoryLevelDeletedAtIndex = createPsqlIndexStatementHelper({
|
||||
tableName: "inventory_level",
|
||||
columns: "deleted_at",
|
||||
where: "deleted_at IS NOT NULL",
|
||||
})
|
||||
|
||||
const InventoryLevelInventoryItemIdIndex = createPsqlIndexStatementHelper({
|
||||
tableName: "inventory_level",
|
||||
columns: "inventory_item_id",
|
||||
where: "deleted_at IS NULL",
|
||||
})
|
||||
|
||||
const InventoryLevelLocationIdIndex = createPsqlIndexStatementHelper({
|
||||
tableName: "inventory_level",
|
||||
columns: "location_id",
|
||||
where: "deleted_at IS NULL",
|
||||
})
|
||||
|
||||
const InventoryLevelLocationIdInventoryItemIdIndex =
|
||||
createPsqlIndexStatementHelper({
|
||||
tableName: "inventory_level",
|
||||
columns: ["inventory_item_id", "location_id"],
|
||||
unique: true,
|
||||
where: "deleted_at IS NULL",
|
||||
const InventoryLevel = model
|
||||
.define("InventoryLevel", {
|
||||
id: model.id({ prefix: "ilev" }).primaryKey(),
|
||||
location_id: model.text(),
|
||||
stocked_quantity: model.bigNumber().default(0),
|
||||
reserved_quantity: model.bigNumber().default(0),
|
||||
incoming_quantity: model.bigNumber().default(0),
|
||||
metadata: model.json().nullable(),
|
||||
inventory_item: model.belongsTo(() => InventoryItem, {
|
||||
mappedBy: "location_levels",
|
||||
}),
|
||||
available_quantity: model.bigNumber().computed(),
|
||||
})
|
||||
.indexes([
|
||||
{
|
||||
name: "IDX_inventory_level_inventory_item_id",
|
||||
on: ["inventory_item_id"],
|
||||
where: "deleted_at IS NULL",
|
||||
},
|
||||
{
|
||||
name: "IDX_inventory_level_location_id",
|
||||
on: ["location_id"],
|
||||
where: "deleted_at IS NULL",
|
||||
},
|
||||
{
|
||||
name: "IDX_inventory_level_location_id_inventory_item_id",
|
||||
on: ["inventory_item_id", "location_id"],
|
||||
unique: true,
|
||||
where: "deleted_at IS NULL",
|
||||
},
|
||||
])
|
||||
|
||||
@Entity()
|
||||
@InventoryLevelLocationIdInventoryItemIdIndex.MikroORMIndex()
|
||||
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
|
||||
export class InventoryLevel {
|
||||
@PrimaryKey({ columnType: "text" })
|
||||
id: string
|
||||
|
||||
@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
|
||||
|
||||
@InventoryLevelDeletedAtIndex.MikroORMIndex()
|
||||
@Property({ columnType: "timestamptz", nullable: true })
|
||||
deleted_at: Date | null = null
|
||||
|
||||
@ManyToOne(() => InventoryItem, {
|
||||
fieldName: "inventory_item_id",
|
||||
type: "text",
|
||||
mapToPk: true,
|
||||
onDelete: "cascade",
|
||||
})
|
||||
@InventoryLevelInventoryItemIdIndex.MikroORMIndex()
|
||||
inventory_item_id: string
|
||||
|
||||
@InventoryLevelLocationIdIndex.MikroORMIndex()
|
||||
@Property({ type: "text" })
|
||||
location_id: string
|
||||
|
||||
@MikroOrmBigNumberProperty()
|
||||
stocked_quantity: BigNumber | number = 0
|
||||
|
||||
@Property({ columnType: "jsonb" })
|
||||
raw_stocked_quantity: BigNumberRawValue
|
||||
|
||||
@MikroOrmBigNumberProperty()
|
||||
reserved_quantity: BigNumber | number = 0
|
||||
|
||||
@Property({ columnType: "jsonb" })
|
||||
raw_reserved_quantity: BigNumberRawValue
|
||||
|
||||
@MikroOrmBigNumberProperty()
|
||||
incoming_quantity: BigNumber | number = 0
|
||||
|
||||
@Property({ columnType: "jsonb" })
|
||||
raw_incoming_quantity: BigNumberRawValue
|
||||
|
||||
@Property({ columnType: "jsonb", nullable: true })
|
||||
metadata: Record<string, unknown> | null
|
||||
|
||||
@ManyToOne(() => InventoryItem, {
|
||||
persist: false,
|
||||
})
|
||||
inventory_item: Rel<InventoryItem>
|
||||
|
||||
available_quantity: BigNumber | number | null = null
|
||||
|
||||
@BeforeCreate()
|
||||
beforeCreate(): void {
|
||||
this.id = generateEntityId(this.id, "ilev")
|
||||
this.inventory_item_id ??= this.inventory_item?.id
|
||||
}
|
||||
|
||||
@OnInit()
|
||||
onInit(): void {
|
||||
this.id = generateEntityId(this.id, "ilev")
|
||||
}
|
||||
|
||||
@OnLoad()
|
||||
onLoad(): void {
|
||||
if (isDefined(this.stocked_quantity) && isDefined(this.reserved_quantity)) {
|
||||
this.available_quantity = new BigNumber(
|
||||
MathBN.sub(this.raw_stocked_quantity, this.raw_reserved_quantity)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
export default InventoryLevel
|
||||
|
||||
@@ -1,125 +1,40 @@
|
||||
import {
|
||||
BeforeCreate,
|
||||
Entity,
|
||||
Filter,
|
||||
ManyToOne,
|
||||
OnInit,
|
||||
PrimaryKey,
|
||||
Property,
|
||||
Rel,
|
||||
} from "@mikro-orm/core"
|
||||
import { model } from "@medusajs/framework/utils"
|
||||
import InventoryItem from "./inventory-item"
|
||||
|
||||
import { BigNumberRawValue } from "@medusajs/framework/types"
|
||||
import {
|
||||
BigNumber,
|
||||
DALUtils,
|
||||
MikroOrmBigNumberProperty,
|
||||
Searchable,
|
||||
createPsqlIndexStatementHelper,
|
||||
generateEntityId,
|
||||
} from "@medusajs/framework/utils"
|
||||
import { InventoryItem } from "./inventory-item"
|
||||
|
||||
const ReservationItemDeletedAtIndex = createPsqlIndexStatementHelper({
|
||||
tableName: "reservation_item",
|
||||
columns: "deleted_at",
|
||||
where: "deleted_at IS NOT NULL",
|
||||
})
|
||||
const ReservationItemLineItemIdIndex = createPsqlIndexStatementHelper({
|
||||
tableName: "reservation_item",
|
||||
columns: "line_item_id",
|
||||
where: "deleted_at IS NULL",
|
||||
})
|
||||
|
||||
const ReservationItemInventoryItemIdIndex = createPsqlIndexStatementHelper({
|
||||
tableName: "reservation_item",
|
||||
columns: "inventory_item_id",
|
||||
where: "deleted_at IS NULL",
|
||||
})
|
||||
|
||||
const ReservationItemLocationIdIndex = createPsqlIndexStatementHelper({
|
||||
tableName: "reservation_item",
|
||||
columns: "location_id",
|
||||
where: "deleted_at IS NULL",
|
||||
})
|
||||
|
||||
@Entity()
|
||||
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
|
||||
export class ReservationItem {
|
||||
@PrimaryKey({ columnType: "text" })
|
||||
id: string
|
||||
|
||||
@Property({
|
||||
onCreate: () => new Date(),
|
||||
columnType: "timestamptz",
|
||||
defaultRaw: "now()",
|
||||
const ReservationItem = model
|
||||
.define("ReservationItem", {
|
||||
id: model.id({ prefix: "resitem" }).primaryKey(),
|
||||
line_item_id: model.text().nullable(),
|
||||
allow_backorder: model.boolean().default(false),
|
||||
location_id: model.text(),
|
||||
quantity: model.bigNumber(),
|
||||
raw_quantity: model.json(),
|
||||
external_id: model.text().nullable(),
|
||||
description: model.text().searchable().nullable(),
|
||||
created_by: model.text().nullable(),
|
||||
metadata: model.json().nullable(),
|
||||
inventory_item: model
|
||||
.belongsTo(() => InventoryItem, {
|
||||
mappedBy: "reservation_items",
|
||||
})
|
||||
.searchable(),
|
||||
})
|
||||
created_at: Date
|
||||
.indexes([
|
||||
{
|
||||
name: "IDX_reservation_item_line_item_id",
|
||||
on: ["line_item_id"],
|
||||
where: "deleted_at IS NULL",
|
||||
},
|
||||
{
|
||||
name: "IDX_reservation_item_location_id",
|
||||
on: ["location_id"],
|
||||
where: "deleted_at IS NULL",
|
||||
},
|
||||
{
|
||||
name: "IDX_reservation_item_inventory_item_id",
|
||||
on: ["inventory_item_id"],
|
||||
where: "deleted_at IS NULL",
|
||||
},
|
||||
])
|
||||
|
||||
@Property({
|
||||
onCreate: () => new Date(),
|
||||
onUpdate: () => new Date(),
|
||||
columnType: "timestamptz",
|
||||
defaultRaw: "now()",
|
||||
})
|
||||
updated_at: Date
|
||||
|
||||
@ReservationItemDeletedAtIndex.MikroORMIndex()
|
||||
@Property({ columnType: "timestamptz", nullable: true })
|
||||
deleted_at: Date | null = null
|
||||
|
||||
@ReservationItemLineItemIdIndex.MikroORMIndex()
|
||||
@Property({ type: "text", nullable: true })
|
||||
line_item_id: string | null = null
|
||||
|
||||
@Property({ type: "boolean" })
|
||||
allow_backorder: boolean = false
|
||||
|
||||
@ReservationItemLocationIdIndex.MikroORMIndex()
|
||||
@Property({ type: "text" })
|
||||
location_id: string
|
||||
|
||||
@MikroOrmBigNumberProperty()
|
||||
quantity: BigNumber | number
|
||||
|
||||
@Property({ columnType: "jsonb" })
|
||||
raw_quantity: BigNumberRawValue
|
||||
|
||||
@Property({ type: "text", nullable: true })
|
||||
external_id: string | null = null
|
||||
|
||||
@Searchable()
|
||||
@Property({ type: "text", nullable: true })
|
||||
description: string | null = null
|
||||
|
||||
@Property({ type: "text", nullable: true })
|
||||
created_by: string | null = null
|
||||
|
||||
@Property({ type: "jsonb", nullable: true })
|
||||
metadata: Record<string, unknown> | null = null
|
||||
|
||||
@ReservationItemInventoryItemIdIndex.MikroORMIndex()
|
||||
@ManyToOne(() => InventoryItem, {
|
||||
fieldName: "inventory_item_id",
|
||||
type: "text",
|
||||
mapToPk: true,
|
||||
onDelete: "cascade",
|
||||
})
|
||||
inventory_item_id: string
|
||||
|
||||
@Searchable()
|
||||
@ManyToOne(() => InventoryItem, {
|
||||
persist: false,
|
||||
})
|
||||
inventory_item: Rel<InventoryItem>
|
||||
|
||||
@BeforeCreate()
|
||||
beforeCreate(): void {
|
||||
this.id = generateEntityId(this.id, "resitem")
|
||||
}
|
||||
|
||||
@OnInit()
|
||||
onInit(): void {
|
||||
this.id = generateEntityId(this.id, "resitem")
|
||||
}
|
||||
}
|
||||
export default ReservationItem
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
export * from "./inventory-level"
|
||||
export { MikroOrmBaseRepository as BaseRepository } from "@medusajs/framework/utils"
|
||||
export * from "./inventory-level"
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { Context } from "@medusajs/framework/types"
|
||||
import { BigNumber, ModulesSdkUtils } from "@medusajs/framework/utils"
|
||||
import { applyEntityHooks } from "../utils/apply-decorators"
|
||||
|
||||
import { InventoryLevel } from "@models"
|
||||
import { InventoryLevelRepository } from "@repositories"
|
||||
import { InventoryLevel } from "../models/inventory-level"
|
||||
|
||||
type InjectedDependencies = {
|
||||
inventoryLevelRepository: InventoryLevelRepository
|
||||
@@ -10,7 +11,7 @@ type InjectedDependencies = {
|
||||
|
||||
export default class InventoryLevelService extends ModulesSdkUtils.MedusaInternalService<
|
||||
InjectedDependencies,
|
||||
InventoryLevel
|
||||
typeof InventoryLevel
|
||||
>(InventoryLevel) {
|
||||
protected readonly inventoryLevelRepository: InventoryLevelRepository
|
||||
|
||||
@@ -67,3 +68,5 @@ export default class InventoryLevelService extends ModulesSdkUtils.MedusaInterna
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
applyEntityHooks()
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
Context,
|
||||
DAL,
|
||||
IInventoryService,
|
||||
InferEntityType,
|
||||
InternalModuleDeclaration,
|
||||
InventoryTypes,
|
||||
ModuleJoinerConfig,
|
||||
@@ -29,6 +30,7 @@ import {
|
||||
} from "@medusajs/framework/utils"
|
||||
import { InventoryItem, InventoryLevel, ReservationItem } from "@models"
|
||||
import { joinerConfig } from "../joiner-config"
|
||||
import { applyEntityHooks } from "../utils/apply-decorators"
|
||||
import InventoryLevelService from "./inventory-level"
|
||||
|
||||
type InjectedDependencies = {
|
||||
@@ -46,6 +48,8 @@ type InventoryItemCheckLevel = {
|
||||
allow_backorder?: boolean
|
||||
}
|
||||
|
||||
applyEntityHooks()
|
||||
|
||||
export default class InventoryModuleService
|
||||
extends MedusaService<{
|
||||
InventoryItem: {
|
||||
@@ -66,8 +70,12 @@ export default class InventoryModuleService
|
||||
{
|
||||
protected baseRepository_: DAL.RepositoryService
|
||||
|
||||
protected readonly inventoryItemService_: ModulesSdkTypes.IMedusaInternalService<InventoryItem>
|
||||
protected readonly reservationItemService_: ModulesSdkTypes.IMedusaInternalService<ReservationItem>
|
||||
protected readonly inventoryItemService_: ModulesSdkTypes.IMedusaInternalService<
|
||||
typeof InventoryItem
|
||||
>
|
||||
protected readonly reservationItemService_: ModulesSdkTypes.IMedusaInternalService<
|
||||
typeof ReservationItem
|
||||
>
|
||||
protected readonly inventoryLevelService_: InventoryLevelService
|
||||
|
||||
constructor(
|
||||
@@ -263,7 +271,7 @@ export default class InventoryModuleService
|
||||
async createReservationItems_(
|
||||
input: InventoryTypes.CreateReservationItemInput[],
|
||||
@MedusaContext() context: Context = {}
|
||||
): Promise<ReservationItem[]> {
|
||||
): Promise<InferEntityType<typeof ReservationItem>[]> {
|
||||
const inventoryLevels = await this.ensureInventoryLevels(
|
||||
input.map(
|
||||
({ location_id, inventory_item_id, quantity, allow_backorder }) => ({
|
||||
@@ -417,7 +425,7 @@ export default class InventoryModuleService
|
||||
async createInventoryLevels_(
|
||||
input: InventoryTypes.CreateInventoryLevelInput[],
|
||||
@MedusaContext() context: Context = {}
|
||||
): Promise<InventoryLevel[]> {
|
||||
): Promise<InferEntityType<typeof InventoryLevel>[]> {
|
||||
return await this.inventoryLevelService_.create(input, context)
|
||||
}
|
||||
|
||||
@@ -473,7 +481,7 @@ export default class InventoryModuleService
|
||||
id: string
|
||||
})[],
|
||||
@MedusaContext() context: Context = {}
|
||||
): Promise<InventoryItem[]> {
|
||||
): Promise<InferEntityType<typeof InventoryItem>[]> {
|
||||
return await this.inventoryItemService_.update(input, context)
|
||||
}
|
||||
|
||||
@@ -670,7 +678,7 @@ export default class InventoryModuleService
|
||||
async updateReservationItems_(
|
||||
input: (InventoryTypes.UpdateReservationItemInput & { id: string })[],
|
||||
@MedusaContext() context: Context = {}
|
||||
): Promise<ReservationItem[]> {
|
||||
): Promise<InferEntityType<typeof ReservationItem>[]> {
|
||||
const ids = input.map((u) => u.id)
|
||||
const reservationItems = await this.listReservationItems(
|
||||
{ id: ids },
|
||||
@@ -989,7 +997,7 @@ export default class InventoryModuleService
|
||||
]
|
||||
}
|
||||
|
||||
const results: InventoryLevel[] = []
|
||||
const results: InferEntityType<typeof InventoryLevel>[] = []
|
||||
|
||||
for (const data of all) {
|
||||
const result = await this.adjustInventory_(
|
||||
@@ -1024,7 +1032,7 @@ export default class InventoryModuleService
|
||||
locationId: string,
|
||||
adjustment: BigNumberInput,
|
||||
@MedusaContext() context: Context = {}
|
||||
): Promise<InventoryLevel> {
|
||||
): Promise<InferEntityType<typeof InventoryLevel>> {
|
||||
const inventoryLevel = await this.retrieveInventoryLevelByItemAndLocation(
|
||||
inventoryItemId,
|
||||
locationId,
|
||||
|
||||
45
packages/modules/inventory/src/utils/apply-decorators.ts
Normal file
45
packages/modules/inventory/src/utils/apply-decorators.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import {
|
||||
BigNumber,
|
||||
isDefined,
|
||||
MathBN,
|
||||
toMikroORMEntity,
|
||||
} from "@medusajs/framework/utils"
|
||||
import { Formula, OnInit } from "@mikro-orm/core"
|
||||
|
||||
import InventoryItem from "../models/inventory-item"
|
||||
import InventoryLevel from "../models/inventory-level"
|
||||
|
||||
function applyHook() {
|
||||
const MikroORMEntity = toMikroORMEntity(InventoryLevel)
|
||||
|
||||
MikroORMEntity.prototype["onInit"] = function () {
|
||||
if (isDefined(this.stocked_quantity) && isDefined(this.reserved_quantity)) {
|
||||
this.available_quantity = new BigNumber(
|
||||
MathBN.sub(this.raw_stocked_quantity, this.raw_reserved_quantity)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
OnInit()(MikroORMEntity.prototype, "onInit")
|
||||
}
|
||||
|
||||
function applyFormulas() {
|
||||
const MikroORMEntity = toMikroORMEntity(InventoryItem)
|
||||
|
||||
Formula(
|
||||
(item) =>
|
||||
`(SELECT SUM(reserved_quantity) FROM inventory_level il WHERE il.inventory_item_id = ${item}.id AND il.deleted_at IS NULL)`,
|
||||
{ lazy: true, serializer: Number, hidden: true, type: "number" }
|
||||
)(MikroORMEntity.prototype, "reserved_quantity")
|
||||
|
||||
Formula(
|
||||
(item) =>
|
||||
`(SELECT SUM(stocked_quantity) FROM inventory_level il WHERE il.inventory_item_id = ${item}.id AND il.deleted_at IS NULL)`,
|
||||
{ lazy: true, serializer: Number, hidden: true, type: "number" }
|
||||
)(MikroORMEntity.prototype, "stocked_quantity")
|
||||
}
|
||||
|
||||
export const applyEntityHooks = () => {
|
||||
applyHook()
|
||||
applyFormulas()
|
||||
}
|
||||
Reference in New Issue
Block a user