chore(medusa): Typeorm upgrade to 0.3.11 (#3041)
This commit is contained in:
1 parent
8137061908
commit
121b42acfe
275 files changed
+4493
-4780
No files matched your search
@@ -21,6 +21,7 @@
|
||||
"cross-env": "^5.2.1",
|
||||
"jest": "^25.5.4",
|
||||
"ts-jest": "^25.5.1",
|
||||
"typeorm": "^0.3.11",
|
||||
"typescript": "^4.4.4"
|
||||
},
|
||||
"scripts": {
|
||||
@@ -32,9 +33,6 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@medusajs/medusa": "1.7.7",
|
||||
"medusa-interfaces": "1.3.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"typeorm": "^0.2.31"
|
||||
"typeorm": "^0.3.11"
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
import { DeepPartial, EntityManager } from "typeorm"
|
||||
import { DeepPartial, EntityManager, FindManyOptions } from "typeorm"
|
||||
import { isDefined, MedusaError } from "medusa-core-utils"
|
||||
import {
|
||||
FindConfig,
|
||||
buildQuery,
|
||||
IEventBusService,
|
||||
FilterableInventoryItemProps,
|
||||
CreateInventoryItemInput,
|
||||
FilterableInventoryItemProps,
|
||||
FindConfig,
|
||||
IEventBusService,
|
||||
InventoryItemDTO,
|
||||
TransactionBaseService,
|
||||
} from "@medusajs/medusa"
|
||||
@@ -87,7 +87,7 @@ export default class InventoryItemService extends TransactionBaseService {
|
||||
const manager = this.getManager()
|
||||
const itemRepository = manager.getRepository(InventoryItem)
|
||||
|
||||
const query = buildQuery({ id: inventoryItemId }, config)
|
||||
const query = buildQuery({ id: inventoryItemId }, config) as FindManyOptions
|
||||
const [inventoryItem] = await itemRepository.find(query)
|
||||
|
||||
if (!inventoryItem) {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { DeepPartial, EntityManager } from "typeorm"
|
||||
import { DeepPartial, EntityManager, FindManyOptions } from "typeorm"
|
||||
import { isDefined, MedusaError } from "medusa-core-utils"
|
||||
import {
|
||||
FindConfig,
|
||||
buildQuery,
|
||||
FilterableInventoryLevelProps,
|
||||
CreateInventoryLevelInput,
|
||||
FilterableInventoryLevelProps,
|
||||
FindConfig,
|
||||
IEventBusService,
|
||||
TransactionBaseService,
|
||||
} from "@medusajs/medusa"
|
||||
@@ -52,7 +52,7 @@ export default class InventoryLevelService extends TransactionBaseService {
|
||||
const manager = this.getManager()
|
||||
const levelRepository = manager.getRepository(InventoryLevel)
|
||||
|
||||
const query = buildQuery(selector, config)
|
||||
const query = buildQuery(selector, config) as FindManyOptions
|
||||
return await levelRepository.find(query)
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ export default class InventoryLevelService extends TransactionBaseService {
|
||||
const manager = this.getManager()
|
||||
const levelRepository = manager.getRepository(InventoryLevel)
|
||||
|
||||
const query = buildQuery(selector, config)
|
||||
const query = buildQuery(selector, config) as FindManyOptions
|
||||
return await levelRepository.findAndCount(query)
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ export default class InventoryLevelService extends TransactionBaseService {
|
||||
const manager = this.getManager()
|
||||
const levelRepository = manager.getRepository(InventoryLevel)
|
||||
|
||||
const query = buildQuery({ id: inventoryLevelId }, config)
|
||||
const query = buildQuery({ id: inventoryLevelId }, config) as FindManyOptions
|
||||
const [inventoryLevel] = await levelRepository.find(query)
|
||||
|
||||
if (!inventoryLevel) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { EntityManager } from "typeorm"
|
||||
import { EntityManager, FindManyOptions } from "typeorm"
|
||||
import { isDefined, MedusaError } from "medusa-core-utils"
|
||||
import {
|
||||
FindConfig,
|
||||
@@ -62,7 +62,7 @@ export default class ReservationItemService extends TransactionBaseService {
|
||||
const manager = this.getManager()
|
||||
const itemRepository = manager.getRepository(ReservationItem)
|
||||
|
||||
const query = buildQuery(selector, config)
|
||||
const query = buildQuery(selector, config) as FindManyOptions
|
||||
return await itemRepository.find(query)
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ export default class ReservationItemService extends TransactionBaseService {
|
||||
const manager = this.getManager()
|
||||
const itemRepository = manager.getRepository(ReservationItem)
|
||||
|
||||
const query = buildQuery(selector, config)
|
||||
const query = buildQuery(selector, config) as FindManyOptions
|
||||
return await itemRepository.findAndCount(query)
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ export default class ReservationItemService extends TransactionBaseService {
|
||||
const manager = this.getManager()
|
||||
const reservationItemRepository = manager.getRepository(ReservationItem)
|
||||
|
||||
const query = buildQuery({ id: reservationItemId }, config)
|
||||
const query = buildQuery({ id: reservationItemId }, config) as FindManyOptions
|
||||
const [reservationItem] = await reservationItemRepository.find(query)
|
||||
|
||||
if (!reservationItem) {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { EntityManager, ILike } from "typeorm"
|
||||
import { EntityManager, FindOptionsWhere, ILike } from "typeorm"
|
||||
import {
|
||||
buildLegacyFieldsListFrom,
|
||||
buildQuery,
|
||||
ExtendedFindConfig,
|
||||
FilterableInventoryItemProps,
|
||||
FindConfig,
|
||||
} from "@medusajs/medusa"
|
||||
@@ -12,14 +14,19 @@ export function getListQuery(
|
||||
config: FindConfig<InventoryItem> = { relations: [], skip: 0, take: 10 }
|
||||
) {
|
||||
const inventoryItemRepository = manager.getRepository(InventoryItem)
|
||||
const query = buildQuery(selector, config)
|
||||
|
||||
const { q, ...selectorRest } = selector
|
||||
const query = buildQuery(selectorRest, config) as ExtendedFindConfig<InventoryItem> & {
|
||||
where: FindOptionsWhere<InventoryItem & {
|
||||
location_id?: string
|
||||
}>
|
||||
}
|
||||
|
||||
|
||||
const queryBuilder = inventoryItemRepository.createQueryBuilder("inv_item")
|
||||
|
||||
if (query.where.q) {
|
||||
query.where.sku = ILike(`%${query.where.q as string}%`)
|
||||
|
||||
delete query.where.q
|
||||
if (q) {
|
||||
query.where.sku = ILike(`%${q}%`)
|
||||
}
|
||||
|
||||
if ("location_id" in query.where) {
|
||||
@@ -50,7 +57,8 @@ export function getListQuery(
|
||||
}
|
||||
|
||||
if (query.select) {
|
||||
queryBuilder.select(query.select.map((s) => "inv_item." + s))
|
||||
const legacySelect = buildLegacyFieldsListFrom(query.select)
|
||||
queryBuilder.select(legacySelect.map((s) => "inv_item." + s))
|
||||
}
|
||||
|
||||
if (query.order) {
|
||||
|
||||
@@ -25,7 +25,7 @@ type ComputePropertyNames<
|
||||
* ```
|
||||
*/
|
||||
export const transformIdableFields = <
|
||||
T = Record<string, unknown>,
|
||||
T extends object = Record<string, unknown>,
|
||||
TFields extends (keyof T | string)[] = (keyof T | string)[],
|
||||
TOutput = {
|
||||
[P in ComputePropertyNames<T, keyof T & string, TFields>]: P extends keyof T
|
||||
|
||||
@@ -26,10 +26,10 @@
|
||||
"medusa-interfaces": "^1.3.6"
|
||||
},
|
||||
"scripts": {
|
||||
"prepare": "cross-env NODE_ENV=production yarn run build",
|
||||
"test": "jest --passWithNoTests src",
|
||||
"build": "babel src --out-dir dist/ --ignore '**/__tests__','**/__mocks__'",
|
||||
"watch": "babel -w src --out-dir dist/ --ignore '**/__tests__','**/__mocks__'"
|
||||
"prepare": "cross-env NODE_ENV=production yarn run build",
|
||||
"watch": "babel -w src --out-dir dist/ --ignore '**/__tests__','**/__mocks__'",
|
||||
"test": "jest --passWithNoTests src"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"medusa-interfaces": "1.3.6"
|
||||
|
||||
@@ -33,7 +33,6 @@
|
||||
"jest": "^25.5.4",
|
||||
"medusa-core-utils": "^1.1.39",
|
||||
"medusa-test-utils": "^1.1.37",
|
||||
"typeorm": "^0.2.29",
|
||||
"typescript": "^4.4.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@@ -1,33 +1,6 @@
|
||||
import BaseService from "../base-service"
|
||||
import { In, Not } from "typeorm"
|
||||
|
||||
describe("BaseService", () => {
|
||||
describe("buildQuery_", () => {
|
||||
const baseService = new BaseService()
|
||||
|
||||
it("successfully creates query", () => {
|
||||
const q = baseService.buildQuery_(
|
||||
{
|
||||
id: "1234",
|
||||
test1: ["123", "12", "1"],
|
||||
test2: Not("this"),
|
||||
},
|
||||
{
|
||||
relations: ["1234"],
|
||||
}
|
||||
)
|
||||
|
||||
expect(q).toEqual({
|
||||
where: {
|
||||
id: "1234",
|
||||
test1: In(["123", "12", "1"]),
|
||||
test2: Not("this"),
|
||||
},
|
||||
relations: ["1234"],
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("addDecorator", () => {
|
||||
const baseService = new BaseService()
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { MedusaError } from "medusa-core-utils"
|
||||
import { FindOperator, In, Raw } from "typeorm"
|
||||
// Import from dist to avoid circular deps which result in the base service to be undefined
|
||||
import { buildQuery, setMetadata, validateId } from "@medusajs/medusa/dist/utils"
|
||||
|
||||
/**
|
||||
* Common functionality for Services
|
||||
* @interface
|
||||
* @deprecated use TransactionBaseService from @medusajs/medusa instead
|
||||
*/
|
||||
class BaseService {
|
||||
export default class BaseService {
|
||||
constructor() {
|
||||
this.decorators_ = []
|
||||
}
|
||||
@@ -19,93 +20,7 @@ class BaseService {
|
||||
* Used to build TypeORM queries.
|
||||
*/
|
||||
buildQuery_(selector, config = {}) {
|
||||
const build = (obj) => {
|
||||
const where = Object.entries(obj).reduce((acc, [key, value]) => {
|
||||
// Undefined values indicate that they have no significance to the query.
|
||||
// If the query is looking for rows where a column is not set it should use null instead of undefined
|
||||
if (typeof value === "undefined") {
|
||||
return acc
|
||||
}
|
||||
switch (true) {
|
||||
case value instanceof FindOperator:
|
||||
acc[key] = value
|
||||
break
|
||||
case Array.isArray(value):
|
||||
acc[key] = In([...value])
|
||||
break
|
||||
case value !== null && typeof value === "object":
|
||||
const subquery = []
|
||||
|
||||
Object.entries(value).map(([modifier, val]) => {
|
||||
switch (modifier) {
|
||||
case "lt":
|
||||
subquery.push({ operator: "<", value: val })
|
||||
break
|
||||
case "gt":
|
||||
subquery.push({ operator: ">", value: val })
|
||||
break
|
||||
case "lte":
|
||||
subquery.push({ operator: "<=", value: val })
|
||||
break
|
||||
case "gte":
|
||||
subquery.push({ operator: ">=", value: val })
|
||||
break
|
||||
default:
|
||||
acc[key] = value
|
||||
break
|
||||
}
|
||||
})
|
||||
|
||||
if (subquery.length) {
|
||||
acc[key] = Raw(
|
||||
(a) =>
|
||||
subquery
|
||||
.map((s, index) => `${a} ${s.operator} :${index}`)
|
||||
.join(" AND "),
|
||||
subquery.map((s) => s.value)
|
||||
)
|
||||
}
|
||||
break
|
||||
default:
|
||||
acc[key] = value
|
||||
break
|
||||
}
|
||||
|
||||
return acc
|
||||
}, {})
|
||||
|
||||
return where
|
||||
}
|
||||
|
||||
const query = {
|
||||
where: build(selector),
|
||||
}
|
||||
|
||||
if ("deleted_at" in selector) {
|
||||
query.withDeleted = true
|
||||
}
|
||||
|
||||
if ("skip" in config) {
|
||||
query.skip = config.skip
|
||||
}
|
||||
|
||||
if ("take" in config) {
|
||||
query.take = config.take
|
||||
}
|
||||
|
||||
if ("relations" in config) {
|
||||
query.relations = config.relations
|
||||
}
|
||||
|
||||
if ("select" in config) {
|
||||
query.select = config.select
|
||||
}
|
||||
|
||||
if ("order" in config) {
|
||||
query.order = config.order
|
||||
}
|
||||
|
||||
return query
|
||||
return buildQuery(selector, config)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -117,32 +32,7 @@ class BaseService {
|
||||
* @returns {string} the rawId given that nothing failed
|
||||
*/
|
||||
validateId_(rawId, config = {}) {
|
||||
const { prefix, length } = config
|
||||
if (!rawId) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`Failed to validate id: ${rawId}`
|
||||
)
|
||||
}
|
||||
|
||||
if (prefix || length) {
|
||||
const [pre, rand] = rawId.split("_")
|
||||
if (prefix && pre !== prefix) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`The provided id: ${rawId} does not adhere to prefix constraint: ${prefix}`
|
||||
)
|
||||
}
|
||||
|
||||
if (length && length !== rand.length) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`The provided id: ${rawId} does not adhere to length constraint: ${length}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return rawId
|
||||
return validateId(rawId, config)
|
||||
}
|
||||
|
||||
shouldRetryTransaction(err) {
|
||||
@@ -249,24 +139,7 @@ class BaseService {
|
||||
* @return {Promise} resolves to the updated result.
|
||||
*/
|
||||
setMetadata_(obj, metadata) {
|
||||
const existing = obj.metadata || {}
|
||||
const newData = {}
|
||||
for (const [key, value] of Object.entries(metadata)) {
|
||||
if (typeof key !== "string") {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_ARGUMENT,
|
||||
"Key type is invalid. Metadata keys must be strings"
|
||||
)
|
||||
}
|
||||
newData[key] = value
|
||||
}
|
||||
|
||||
const updated = {
|
||||
...existing,
|
||||
...newData,
|
||||
}
|
||||
|
||||
return updated
|
||||
return setMetadata(obj, metadata)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -295,4 +168,3 @@ class BaseService {
|
||||
}, Promise.resolve(obj))
|
||||
}
|
||||
}
|
||||
export default BaseService
|
||||
@@ -1,6 +1,7 @@
|
||||
import SendGrid from "@sendgrid/mail"
|
||||
import { humanizeAmount, zeroDecimalCurrencies } from "medusa-core-utils"
|
||||
import { NotificationService } from "medusa-interfaces"
|
||||
import { IsNull, Not } from "typeorm"
|
||||
|
||||
class SendGridService extends NotificationService {
|
||||
static identifier = "sendgrid"
|
||||
@@ -621,7 +622,7 @@ class SendGridService extends NotificationService {
|
||||
{
|
||||
id: returnRequest.items.map(({ item_id }) => item_id),
|
||||
},
|
||||
{ relations: ["tax_lines"] }
|
||||
{ relations: ["tax_lines", "variant", "variant.product"] }
|
||||
)
|
||||
|
||||
// Fetch the order
|
||||
@@ -629,6 +630,7 @@ class SendGridService extends NotificationService {
|
||||
select: ["total"],
|
||||
relations: [
|
||||
"items",
|
||||
"items.variant",
|
||||
"items.tax_lines",
|
||||
"discounts",
|
||||
"discounts.rule",
|
||||
@@ -712,20 +714,22 @@ class SendGridService extends NotificationService {
|
||||
|
||||
async swapReceivedData({ id }) {
|
||||
const store = await this.storeService_.retrieve()
|
||||
|
||||
const swap = await this.swapService_.retrieve(id, {
|
||||
relations: [
|
||||
"additional_items",
|
||||
"additional_items.tax_lines",
|
||||
"additional_items.variant",
|
||||
"return_order",
|
||||
"return_order.items",
|
||||
"return_order.items.item",
|
||||
"return_order.items.item.variant",
|
||||
"return_order.shipping_method",
|
||||
"return_order.shipping_method.shipping_option",
|
||||
],
|
||||
})
|
||||
|
||||
const returnRequest = swap.return_order
|
||||
|
||||
const items = await this.lineItemService_.list(
|
||||
{
|
||||
id: returnRequest.items.map(({ item_id }) => item_id),
|
||||
@@ -752,16 +756,19 @@ class SendGridService extends NotificationService {
|
||||
select: ["total"],
|
||||
relations: [
|
||||
"items",
|
||||
"items.variant",
|
||||
"discounts",
|
||||
"discounts.rule",
|
||||
"shipping_address",
|
||||
"swaps",
|
||||
"swaps.additional_items",
|
||||
"swaps.additional_items.tax_lines",
|
||||
"swaps.additional_items.variant",
|
||||
],
|
||||
})
|
||||
|
||||
const cart = await this.cartService_.retrieve(swap.cart_id, {
|
||||
relations: ["items", "items.variant", "items.variant.product"],
|
||||
select: [
|
||||
"total",
|
||||
"tax_total",
|
||||
@@ -770,8 +777,8 @@ class SendGridService extends NotificationService {
|
||||
"subtotal",
|
||||
],
|
||||
})
|
||||
const currencyCode = order.currency_code.toUpperCase()
|
||||
|
||||
const currencyCode = order.currency_code.toUpperCase()
|
||||
const decoratedItems = await Promise.all(
|
||||
cart.items.map(async (i) => {
|
||||
const totals = await this.totalsService_.getLineItemTotals(i, cart, {
|
||||
@@ -837,10 +844,11 @@ class SendGridService extends NotificationService {
|
||||
}
|
||||
|
||||
async swapCreatedData({ id }) {
|
||||
const store = await this.storeService_.retrieve()
|
||||
const store = await this.storeService_.retrieve({ where: { id: Not(IsNull()) } })
|
||||
const swap = await this.swapService_.retrieve(id, {
|
||||
relations: [
|
||||
"additional_items",
|
||||
"additional_items.variant.product",
|
||||
"additional_items.tax_lines",
|
||||
"return_order",
|
||||
"return_order.items",
|
||||
@@ -857,7 +865,7 @@ class SendGridService extends NotificationService {
|
||||
id: returnRequest.items.map(({ item_id }) => item_id),
|
||||
},
|
||||
{
|
||||
relations: ["tax_lines"],
|
||||
relations: ["tax_lines", "variant", "variant.product"],
|
||||
}
|
||||
)
|
||||
|
||||
@@ -878,6 +886,8 @@ class SendGridService extends NotificationService {
|
||||
select: ["total"],
|
||||
relations: [
|
||||
"items",
|
||||
"items.variant",
|
||||
"items.variant.product",
|
||||
"items.tax_lines",
|
||||
"discounts",
|
||||
"discounts.rule",
|
||||
@@ -885,6 +895,7 @@ class SendGridService extends NotificationService {
|
||||
"swaps",
|
||||
"swaps.additional_items",
|
||||
"swaps.additional_items.tax_lines",
|
||||
"swaps.additional_items.variant",
|
||||
],
|
||||
})
|
||||
|
||||
@@ -896,6 +907,7 @@ class SendGridService extends NotificationService {
|
||||
"shipping_total",
|
||||
"subtotal",
|
||||
],
|
||||
relations: ["items", "items.variant", "items.variant.product"]
|
||||
})
|
||||
const currencyCode = order.currency_code.toUpperCase()
|
||||
|
||||
@@ -977,6 +989,8 @@ class SendGridService extends NotificationService {
|
||||
"shipping_methods",
|
||||
"shipping_methods.tax_lines",
|
||||
"additional_items",
|
||||
"additional_items.variant",
|
||||
"additional_items.variant.product",
|
||||
"additional_items.tax_lines",
|
||||
"return_order",
|
||||
"return_order.items",
|
||||
@@ -988,10 +1002,14 @@ class SendGridService extends NotificationService {
|
||||
"region",
|
||||
"items",
|
||||
"items.tax_lines",
|
||||
"items.variant",
|
||||
"items.variant.product",
|
||||
"discounts",
|
||||
"discounts.rule",
|
||||
"swaps",
|
||||
"swaps.additional_items",
|
||||
"swaps.additional_items.variant",
|
||||
"swaps.additional_items.variant.product",
|
||||
"swaps.additional_items.tax_lines",
|
||||
],
|
||||
})
|
||||
@@ -1004,6 +1022,11 @@ class SendGridService extends NotificationService {
|
||||
"shipping_total",
|
||||
"subtotal",
|
||||
],
|
||||
relations: [
|
||||
"items",
|
||||
"items.variant",
|
||||
"items.variant.product",
|
||||
]
|
||||
})
|
||||
|
||||
const returnRequest = swap.return_order
|
||||
@@ -1012,7 +1035,7 @@ class SendGridService extends NotificationService {
|
||||
id: returnRequest.items.map(({ item_id }) => item_id),
|
||||
},
|
||||
{
|
||||
relations: ["tax_lines"],
|
||||
relations: ["tax_lines", "variant", "variant.product"],
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1118,7 +1141,13 @@ class SendGridService extends NotificationService {
|
||||
|
||||
async claimShipmentCreatedData({ id, fulfillment_id }) {
|
||||
const claim = await this.claimService_.retrieve(id, {
|
||||
relations: ["order", "order.items", "order.shipping_address"],
|
||||
relations: [
|
||||
"order",
|
||||
"order.items",
|
||||
"order.items.variant",
|
||||
"order.items.variant.product",
|
||||
"order.shipping_address"
|
||||
],
|
||||
})
|
||||
|
||||
const shipment = await this.fulfillmentService_.retrieve(fulfillment_id, {
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
export default {
|
||||
getRepository: function (repo) {
|
||||
return repo;
|
||||
return repo
|
||||
},
|
||||
|
||||
getCustomRepository: function (repo) {
|
||||
return repo;
|
||||
withRepository: function (repo) {
|
||||
return repo
|
||||
},
|
||||
|
||||
transaction: function (isolationOrCb, cb) {
|
||||
if (typeof isolationOrCb === "string") {
|
||||
return cb(this);
|
||||
return cb(this)
|
||||
} else {
|
||||
return isolationOrCb(this);
|
||||
return isolationOrCb(this)
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -12,6 +12,7 @@ class MockRepo {
|
||||
save,
|
||||
findAndCount,
|
||||
del,
|
||||
count,
|
||||
}) {
|
||||
this.create_ = create;
|
||||
this.update_ = update;
|
||||
@@ -28,45 +29,45 @@ class MockRepo {
|
||||
}
|
||||
|
||||
setFindOne(fn) {
|
||||
this.findOne_ = fn;
|
||||
this.findOne_ = fn
|
||||
}
|
||||
|
||||
create = jest.fn().mockImplementation((...args) => {
|
||||
if (this.create_) {
|
||||
return this.create_(...args);
|
||||
return this.create_(...args)
|
||||
}
|
||||
return {};
|
||||
});
|
||||
return {}
|
||||
})
|
||||
softRemove = jest.fn().mockImplementation((...args) => {
|
||||
if (this.softRemove_) {
|
||||
return this.softRemove_(...args);
|
||||
return this.softRemove_(...args)
|
||||
}
|
||||
return {};
|
||||
});
|
||||
return {}
|
||||
})
|
||||
remove = jest.fn().mockImplementation((...args) => {
|
||||
if (this.remove_) {
|
||||
return this.remove_(...args);
|
||||
return this.remove_(...args)
|
||||
}
|
||||
return {};
|
||||
});
|
||||
return {}
|
||||
})
|
||||
update = jest.fn().mockImplementation((...args) => {
|
||||
if (this.update_) {
|
||||
return this.update_(...args);
|
||||
return this.update_(...args)
|
||||
}
|
||||
});
|
||||
})
|
||||
findOneOrFail = jest.fn().mockImplementation((...args) => {
|
||||
if (this.findOneOrFail_) {
|
||||
return this.findOneOrFail_(...args);
|
||||
return this.findOneOrFail_(...args)
|
||||
}
|
||||
});
|
||||
})
|
||||
findOneWithRelations = jest.fn().mockImplementation((...args) => {
|
||||
if (this.findOneWithRelations_) {
|
||||
return this.findOneWithRelations_(...args);
|
||||
return this.findOneWithRelations_(...args)
|
||||
}
|
||||
});
|
||||
})
|
||||
findOne = jest.fn().mockImplementation((...args) => {
|
||||
if (this.findOne_) {
|
||||
return this.findOne_(...args);
|
||||
return this.findOne_(...args)
|
||||
}
|
||||
});
|
||||
findDescendantsTree = jest.fn().mockImplementation((...args) => {
|
||||
@@ -76,40 +77,46 @@ class MockRepo {
|
||||
});
|
||||
findOneOrFail = jest.fn().mockImplementation((...args) => {
|
||||
if (this.findOneOrFail_) {
|
||||
return this.findOneOrFail_(...args);
|
||||
return this.findOneOrFail_(...args)
|
||||
}
|
||||
});
|
||||
})
|
||||
find = jest.fn().mockImplementation((...args) => {
|
||||
if (this.find_) {
|
||||
return this.find_(...args);
|
||||
return this.find_(...args)
|
||||
}
|
||||
});
|
||||
})
|
||||
softRemove = jest.fn().mockImplementation((...args) => {
|
||||
if (this.softRemove_) {
|
||||
return this.softRemove_(...args);
|
||||
return this.softRemove_(...args)
|
||||
}
|
||||
});
|
||||
})
|
||||
save = jest.fn().mockImplementation((...args) => {
|
||||
if (this.save_) {
|
||||
return this.save_(...args);
|
||||
return this.save_(...args)
|
||||
}
|
||||
return Promise.resolve(...args);
|
||||
});
|
||||
return Promise.resolve(...args)
|
||||
})
|
||||
|
||||
findAndCount = jest.fn().mockImplementation((...args) => {
|
||||
if (this.findAndCount_) {
|
||||
return this.findAndCount_(...args);
|
||||
return this.findAndCount_(...args)
|
||||
}
|
||||
return {};
|
||||
});
|
||||
return {}
|
||||
})
|
||||
count = jest.fn().mockImplementation((...args) => {
|
||||
if (this.count_) {
|
||||
return this.count(...args)
|
||||
}
|
||||
return {}
|
||||
})
|
||||
delete = jest.fn().mockImplementation((...args) => {
|
||||
if (this.delete_) {
|
||||
return this.delete_(...args);
|
||||
return this.delete_(...args)
|
||||
}
|
||||
return {};
|
||||
});
|
||||
return {}
|
||||
})
|
||||
}
|
||||
|
||||
export default (methods = {}) => {
|
||||
return new MockRepo(methods);
|
||||
};
|
||||
return new MockRepo(methods)
|
||||
}
|
||||
@@ -45,7 +45,7 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"medusa-interfaces": "1.3.6",
|
||||
"typeorm": "0.2.x"
|
||||
"typeorm": "^0.3.11"
|
||||
},
|
||||
"dependencies": {
|
||||
"@medusajs/medusa-cli": "^1.3.8",
|
||||
|
||||
@@ -18,9 +18,14 @@ export const getVariantsByInventoryItemId = async (
|
||||
inventoryItems.map((item) => item.id)
|
||||
)
|
||||
|
||||
const variants = await productVariantService.list({
|
||||
id: variantInventory.map((varInventory) => varInventory.variant_id),
|
||||
})
|
||||
const variants = await productVariantService.list(
|
||||
{
|
||||
id: variantInventory.map((varInventory) => varInventory.variant_id),
|
||||
},
|
||||
{
|
||||
relations: ["product"],
|
||||
}
|
||||
)
|
||||
const variantMap = new Map(variants.map((variant) => [variant.id, variant]))
|
||||
|
||||
return variantInventory.reduce((acc, cur) => {
|
||||
|
||||
@@ -142,9 +142,11 @@ export default async (req, res) => {
|
||||
relations: [
|
||||
"cart",
|
||||
"items",
|
||||
"items.variant",
|
||||
"items.tax_lines",
|
||||
"swaps",
|
||||
"swaps.additional_items",
|
||||
"swaps.additional_items.variant",
|
||||
"swaps.additional_items.tax_lines",
|
||||
],
|
||||
})
|
||||
|
||||
@@ -319,6 +319,7 @@ export const defaultAdminOrdersRelations = [
|
||||
"claims.shipping_methods",
|
||||
"claims.shipping_address",
|
||||
"claims.additional_items",
|
||||
"claims.additional_items.variant",
|
||||
"claims.fulfillments",
|
||||
"claims.fulfillments.tracking_links",
|
||||
"claims.claim_items",
|
||||
@@ -334,6 +335,7 @@ export const defaultAdminOrdersRelations = [
|
||||
"swaps.shipping_methods.tax_lines",
|
||||
"swaps.shipping_address",
|
||||
"swaps.additional_items",
|
||||
"swaps.additional_items.variant",
|
||||
"swaps.fulfillments",
|
||||
"swaps.fulfillments.tracking_links",
|
||||
]
|
||||
|
||||
@@ -33,7 +33,7 @@ export default (app, featureFlagRouter: FlagRouter) => {
|
||||
return app
|
||||
}
|
||||
|
||||
export const defaultFields = [
|
||||
export const defaultFields: (keyof ShippingOption)[] = [
|
||||
"id",
|
||||
"name",
|
||||
"region_id",
|
||||
|
||||
@@ -14,6 +14,8 @@ import { EntityManager } from "typeorm"
|
||||
import TaxInclusivePricingFeatureFlag from "../../../../loaders/feature-flags/tax-inclusive-pricing"
|
||||
import { FeatureFlagDecorators } from "../../../../utils/feature-flag-decorators"
|
||||
import { validator } from "../../../../utils/validator"
|
||||
import { ShippingOptionService } from "../../../../services"
|
||||
import { UpdateShippingOptionInput } from "../../../../types/shipping-options"
|
||||
|
||||
/**
|
||||
* @oas [post] /shipping-options/{id}
|
||||
@@ -92,9 +94,14 @@ import { validator } from "../../../../utils/validator"
|
||||
export default async (req, res) => {
|
||||
const { option_id } = req.params
|
||||
|
||||
const validated = await validator(AdminPostShippingOptionsOptionReq, req.body)
|
||||
const validated = (await validator(
|
||||
AdminPostShippingOptionsOptionReq,
|
||||
req.body
|
||||
)) as UpdateShippingOptionInput
|
||||
|
||||
const optionService = req.scope.resolve("shippingOptionService")
|
||||
const optionService: ShippingOptionService = req.scope.resolve(
|
||||
"shippingOptionService"
|
||||
)
|
||||
|
||||
const manager: EntityManager = req.scope.resolve("manager")
|
||||
await manager.transaction(async (transactionManager) => {
|
||||
|
||||
@@ -13,6 +13,7 @@ const defaultRelations = [
|
||||
"shipping_methods",
|
||||
"cart",
|
||||
"cart.items",
|
||||
"cart.items.variant",
|
||||
"cart.items.adjustments",
|
||||
]
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ export const defaultAdminSwapRelations = [
|
||||
"shipping_methods",
|
||||
"cart",
|
||||
"cart.items",
|
||||
"cart.items.variant",
|
||||
"cart.items.adjustments",
|
||||
]
|
||||
|
||||
|
||||
@@ -57,6 +57,8 @@ export const defaultAdminVariantFields: (keyof ProductVariant)[] = [
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"metadata",
|
||||
"deleted_at",
|
||||
"manage_inventory",
|
||||
]
|
||||
|
||||
/**
|
||||
|
||||
@@ -143,6 +143,7 @@ export const defaultStoreCartRelations = [
|
||||
"gift_cards",
|
||||
"region",
|
||||
"items",
|
||||
"items.variant",
|
||||
"items.adjustments",
|
||||
"payment",
|
||||
"shipping_address",
|
||||
|
||||
@@ -78,7 +78,7 @@ export default async (req, res) => {
|
||||
} else {
|
||||
const cart = await cartService
|
||||
.withTransaction(m)
|
||||
.retrieve(id, { relations: ["items"] })
|
||||
.retrieve(id, { relations: ["items", "items.variant"] })
|
||||
|
||||
const existing = cart.items.find((i) => i.id === line_id)
|
||||
if (!existing) {
|
||||
|
||||
@@ -75,7 +75,6 @@ export const defaultStoreOrdersRelations = [
|
||||
"fulfillments.tracking_links",
|
||||
"items",
|
||||
"items.variant",
|
||||
"items.variant.product",
|
||||
"shipping_methods",
|
||||
"discounts",
|
||||
"discounts.rule",
|
||||
|
||||
@@ -140,9 +140,11 @@ export default async (req, res) => {
|
||||
select: ["refunded_total", "total"],
|
||||
relations: [
|
||||
"items",
|
||||
"items.variant",
|
||||
"items.tax_lines",
|
||||
"swaps",
|
||||
"swaps.additional_items",
|
||||
"swaps.additional_items.variant",
|
||||
"swaps.additional_items.tax_lines",
|
||||
],
|
||||
})
|
||||
|
||||
@@ -21,6 +21,7 @@ export default (app) => {
|
||||
export const defaultStoreSwapRelations = [
|
||||
"order",
|
||||
"additional_items",
|
||||
"additional_items.variant",
|
||||
"return_order",
|
||||
"return_order.shipping_method",
|
||||
"fulfillments",
|
||||
|
||||
@@ -15,7 +15,7 @@ describe("Get variant by id", () => {
|
||||
it("calls get variant from variantSerice", () => {
|
||||
expect(ProductVariantServiceMock.retrieve).toHaveBeenCalledTimes(1)
|
||||
expect(ProductVariantServiceMock.retrieve).toHaveBeenCalledWith("1", {
|
||||
relations: ["prices", "options"],
|
||||
relations: ["prices", "options", "product"],
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ export default (app) => {
|
||||
return app
|
||||
}
|
||||
|
||||
export const defaultStoreVariantRelations = ["prices", "options"]
|
||||
export const defaultStoreVariantRelations = ["prices", "options", "product"]
|
||||
|
||||
/**
|
||||
* @schema StoreVariantsRes
|
||||
|
||||
@@ -1,71 +1,55 @@
|
||||
import { createConnection } from "typeorm"
|
||||
import { asValue, createContainer } from "awilix"
|
||||
import featureFlagLoader from "../loaders/feature-flags"
|
||||
import configModuleLoader from "../loaders/config"
|
||||
import Logger from "../loaders/logger"
|
||||
|
||||
import databaseLoader from "../loaders/database"
|
||||
import configModuleLoader from "../loaders/config"
|
||||
import getMigrations, { getModuleSharedResources } from "./utils/get-migrations"
|
||||
|
||||
const getDataSource = async (directory) => {
|
||||
const configModule = configModuleLoader(directory)
|
||||
|
||||
const featureFlagRouter = featureFlagLoader(configModule)
|
||||
|
||||
const { coreMigrations } = getMigrations(directory, featureFlagRouter)
|
||||
|
||||
const { migrations: moduleMigrations } = getModuleSharedResources(
|
||||
configModule,
|
||||
featureFlagRouter
|
||||
)
|
||||
|
||||
return await createConnection({
|
||||
type: configModule.projectConfig.database_type,
|
||||
url: configModule.projectConfig.database_url,
|
||||
extra: configModule.projectConfig.database_extra || {},
|
||||
schema: configModule.projectConfig.database_schema,
|
||||
migrations: coreMigrations.concat(moduleMigrations),
|
||||
logging: true,
|
||||
const container = createContainer()
|
||||
container.register("db_entities", asValue([]))
|
||||
|
||||
return await databaseLoader({
|
||||
container,
|
||||
configModule,
|
||||
customOptions: {
|
||||
migrations: coreMigrations.concat(moduleMigrations),
|
||||
logging: "all",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const main = async function ({ directory }) {
|
||||
const args = process.argv
|
||||
|
||||
args.shift()
|
||||
args.shift()
|
||||
args.shift()
|
||||
|
||||
const dataSource = await getDataSource(directory)
|
||||
|
||||
if (args[0] === "run") {
|
||||
const dataSource = await getDataSource(directory)
|
||||
|
||||
await dataSource.runMigrations()
|
||||
await dataSource.close()
|
||||
await dataSource.destroy()
|
||||
Logger.info("Migrations completed.")
|
||||
process.exit()
|
||||
} else if (args[0] === "revert") {
|
||||
const dataSource = await getDataSource(directory)
|
||||
|
||||
await dataSource.undoLastMigration({ transaction: "all" })
|
||||
await dataSource.close()
|
||||
await dataSource.destroy()
|
||||
Logger.info("Migrations reverted.")
|
||||
|
||||
process.exit()
|
||||
} else if (args[0] === "show") {
|
||||
const configModule = configModuleLoader(directory)
|
||||
|
||||
const featureFlagRouter = featureFlagLoader(configModule)
|
||||
|
||||
const { coreMigrations } = getMigrations(directory, featureFlagRouter)
|
||||
|
||||
const connection = await createConnection({
|
||||
type: configModule.projectConfig.database_type,
|
||||
url: configModule.projectConfig.database_url,
|
||||
extra: configModule.projectConfig.database_extra || {},
|
||||
schema: configModule.projectConfig.database_schema,
|
||||
migrations: coreMigrations,
|
||||
logging: true,
|
||||
})
|
||||
|
||||
const unapplied = await connection.showMigrations()
|
||||
await connection.close()
|
||||
const unapplied = await dataSource.showMigrations()
|
||||
Logger.info(unapplied)
|
||||
await dataSource.destroy()
|
||||
process.exit(unapplied ? 1 : 0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,29 +1,36 @@
|
||||
import { asValue, createContainer } from "awilix";
|
||||
import { MockRepository, MockManager } from "medusa-test-utils"
|
||||
import { StoreServiceMock } from "../../services/__mocks__/store";
|
||||
import { ShippingProfileServiceMock } from "../../services/__mocks__/shipping-profile";
|
||||
import Logger from "../logger";
|
||||
import featureFlagsLoader from "../feature-flags";
|
||||
import { asValue, createContainer } from "awilix"
|
||||
import { MockManager, MockRepository } from "medusa-test-utils"
|
||||
import { StoreServiceMock } from "../../services/__mocks__/store"
|
||||
import { ShippingProfileServiceMock } from "../../services/__mocks__/shipping-profile"
|
||||
import Logger from "../logger"
|
||||
import featureFlagsLoader from "../feature-flags"
|
||||
import { default as defaultLoader } from "../defaults"
|
||||
import { SalesChannelServiceMock } from "../../services/__mocks__/sales-channel";
|
||||
import { PaymentProviderServiceMock } from "../../services/__mocks__/payment-provider";
|
||||
import { SalesChannelServiceMock } from "../../services/__mocks__/sales-channel"
|
||||
import { PaymentProviderServiceMock } from "../../services/__mocks__/payment-provider"
|
||||
|
||||
describe('default', () => {
|
||||
describe('sales channel default', () => {
|
||||
describe("default", () => {
|
||||
describe("sales channel default", () => {
|
||||
let featureFlagRouter
|
||||
const container = createContainer()
|
||||
|
||||
beforeAll(async () => {
|
||||
featureFlagRouter = await featureFlagsLoader({
|
||||
featureFlags: {
|
||||
sales_channels: true,
|
||||
featureFlagRouter = await featureFlagsLoader(
|
||||
{
|
||||
featureFlags: {
|
||||
sales_channels: true,
|
||||
},
|
||||
},
|
||||
}, Logger)
|
||||
Logger
|
||||
)
|
||||
|
||||
container.register({
|
||||
storeService: asValue(StoreServiceMock),
|
||||
currencyRepository: asValue(MockRepository()),
|
||||
countryRepository: asValue(MockRepository()),
|
||||
countryRepository: asValue(
|
||||
MockRepository({
|
||||
count: jest.fn().mockImplementation(() => 1),
|
||||
})
|
||||
),
|
||||
shippingProfileService: asValue(ShippingProfileServiceMock),
|
||||
salesChannelService: asValue(SalesChannelServiceMock),
|
||||
logger: asValue(Logger),
|
||||
|
||||
@@ -1,48 +1,66 @@
|
||||
import {
|
||||
Connection,
|
||||
createConnection,
|
||||
getConnectionManager,
|
||||
getConnection,
|
||||
DataSource,
|
||||
DataSourceOptions,
|
||||
Repository,
|
||||
TreeRepository,
|
||||
} from "typeorm"
|
||||
import { ShortenedNamingStrategy } from "../utils/naming-strategy"
|
||||
import { AwilixContainer } from "awilix"
|
||||
import { ConnectionOptions } from "typeorm/connection/ConnectionOptions"
|
||||
import { ConfigModule } from "../types/global"
|
||||
import "../utils/naming-strategy"
|
||||
|
||||
type Options = {
|
||||
configModule: ConfigModule
|
||||
container: AwilixContainer
|
||||
customOptions?: {
|
||||
migrations: DataSourceOptions["migrations"]
|
||||
logging: DataSourceOptions["logging"]
|
||||
}
|
||||
}
|
||||
|
||||
export let dataSource: DataSource
|
||||
|
||||
// TODO: With the latest version of typeorm, the datasource is expected to be
|
||||
// available globally. During the integration test, the medusa
|
||||
// files are imported from @medusajs/medusa, therefore, the repositories are
|
||||
// evaluated at the same time, unfortunately, the integration tests have their
|
||||
// own way to load and at the moment, the datasource does not exists. This is
|
||||
// why we are mocking them here
|
||||
if (process.env.NODE_ENV === "test") {
|
||||
dataSource = {
|
||||
getRepository: (target) => new Repository(target, {} as any) as any,
|
||||
getTreeRepository: (target) => new TreeRepository(target, {} as any) as any,
|
||||
} as unknown as DataSource
|
||||
}
|
||||
|
||||
export default async ({
|
||||
container,
|
||||
configModule,
|
||||
}: Options): Promise<Connection> => {
|
||||
customOptions,
|
||||
}: Options): Promise<DataSource> => {
|
||||
const entities = container.resolve("db_entities")
|
||||
|
||||
const isSqlite = configModule.projectConfig.database_type === "sqlite"
|
||||
|
||||
const cnnManager = getConnectionManager()
|
||||
if (cnnManager.has("default") && getConnection().isConnected) {
|
||||
await getConnection().close()
|
||||
}
|
||||
|
||||
const connection = await createConnection({
|
||||
dataSource = new DataSource({
|
||||
type: configModule.projectConfig.database_type,
|
||||
url: configModule.projectConfig.database_url,
|
||||
database: configModule.projectConfig.database_database,
|
||||
extra: configModule.projectConfig.database_extra || {},
|
||||
schema: configModule.projectConfig.database_schema,
|
||||
entities,
|
||||
namingStrategy: new ShortenedNamingStrategy(),
|
||||
logging: configModule.projectConfig.database_logging || false,
|
||||
} as ConnectionOptions)
|
||||
migrations: customOptions?.migrations,
|
||||
logging:
|
||||
customOptions?.logging ??
|
||||
(configModule.projectConfig.database_logging || false),
|
||||
} as DataSourceOptions)
|
||||
|
||||
await dataSource.initialize()
|
||||
|
||||
if (isSqlite) {
|
||||
await connection.query(`PRAGMA foreign_keys = OFF`)
|
||||
await connection.synchronize()
|
||||
await connection.query(`PRAGMA foreign_keys = ON`)
|
||||
await dataSource.query(`PRAGMA foreign_keys = OFF`)
|
||||
await dataSource.synchronize()
|
||||
await dataSource.query(`PRAGMA foreign_keys = ON`)
|
||||
}
|
||||
|
||||
return connection
|
||||
return dataSource
|
||||
}
|
||||
@@ -78,8 +78,8 @@ export default async ({
|
||||
const entityManager = container.resolve<EntityManager>("manager")
|
||||
|
||||
await entityManager.transaction(async (manager: EntityManager) => {
|
||||
const countryRepo = manager.getCustomRepository(countryRepository)
|
||||
const hasCountries = await countryRepo.findOne()
|
||||
const countryRepo = manager.withRepository(countryRepository)
|
||||
const hasCountries = !!(await countryRepo.count())
|
||||
if (!hasCountries) {
|
||||
for (const c of countries) {
|
||||
const query = `INSERT INTO "country" ("iso_2", "iso_3", "num_code", "name", "display_name")
|
||||
@@ -103,8 +103,8 @@ export default async ({
|
||||
})
|
||||
|
||||
await entityManager.transaction(async (manager: EntityManager) => {
|
||||
const currencyRepo = manager.getCustomRepository(currencyRepository)
|
||||
const hasCurrencies = await currencyRepo.findOne()
|
||||
const currencyRepo = manager.withRepository(currencyRepository)
|
||||
const hasCurrencies = !!(await currencyRepo.count())
|
||||
if (!hasCurrencies) {
|
||||
for (const [, c] of Object.entries(currencies)) {
|
||||
const query = `INSERT INTO "currency" ("code", "symbol", "symbol_native", "name")
|
||||
|
||||
@@ -11,11 +11,11 @@ import { track } from "medusa-telemetry"
|
||||
import { EOL } from "os"
|
||||
import "reflect-metadata"
|
||||
import requestIp from "request-ip"
|
||||
import { Connection, getManager } from "typeorm"
|
||||
import { Connection } from "typeorm"
|
||||
import { MedusaContainer } from "../types/global"
|
||||
import apiLoader from "./api"
|
||||
import loadConfig from "./config"
|
||||
import databaseLoader from "./database"
|
||||
import databaseLoader, { dataSource } from "./database"
|
||||
import defaultsLoader from "./defaults"
|
||||
import expressLoader from "./express"
|
||||
import featureFlagsLoader from "./feature-flags"
|
||||
@@ -109,12 +109,6 @@ export default async ({
|
||||
const pmAct = Logger.success(pmActivity, "Plugin models initialized") || {}
|
||||
track("PLUGIN_MODELS_INIT_COMPLETED", { duration: pmAct.duration })
|
||||
|
||||
const repoActivity = Logger.activity(`Initializing repositories${EOL}`)
|
||||
track("REPOSITORIES_INIT_STARTED")
|
||||
repositoriesLoader({ container })
|
||||
const rAct = Logger.success(repoActivity, "Repositories initialized") || {}
|
||||
track("REPOSITORIES_INIT_COMPLETED", { duration: rAct.duration })
|
||||
|
||||
const stratActivity = Logger.activity(`Initializing strategies${EOL}`)
|
||||
track("STRATEGIES_INIT_STARTED")
|
||||
strategiesLoader({ container, configModule, isTest })
|
||||
@@ -136,7 +130,13 @@ export default async ({
|
||||
const dbAct = Logger.success(dbActivity, "Database initialized") || {}
|
||||
track("DATABASE_INIT_COMPLETED", { duration: dbAct.duration })
|
||||
|
||||
container.register({ manager: asValue(dbConnection.manager) })
|
||||
const repoActivity = Logger.activity(`Initializing repositories${EOL}`)
|
||||
track("REPOSITORIES_INIT_STARTED")
|
||||
repositoriesLoader({ container })
|
||||
const rAct = Logger.success(repoActivity, "Repositories initialized") || {}
|
||||
track("REPOSITORIES_INIT_COMPLETED", { duration: rAct.duration })
|
||||
|
||||
container.register({ manager: asValue(dataSource.manager) })
|
||||
|
||||
const servicesActivity = Logger.activity(`Initializing services${EOL}`)
|
||||
track("SERVICES_INIT_STARTED")
|
||||
@@ -153,7 +153,7 @@ export default async ({
|
||||
|
||||
// Add the registered services to the request scope
|
||||
expressApp.use((req: Request, res: Response, next: NextFunction) => {
|
||||
container.register({ manager: asValue(getManager()) })
|
||||
container.register({ manager: asValue(dataSource.manager) })
|
||||
;(req as any).scope = container.createScope()
|
||||
next()
|
||||
})
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import glob from "glob"
|
||||
import path from "path"
|
||||
import { asClass } from "awilix"
|
||||
|
||||
import formatRegistrationName from "../utils/format-registration-name"
|
||||
import { ClassConstructor, MedusaContainer } from "../types/global"
|
||||
import { MedusaContainer } from "../types/global"
|
||||
import { asValue } from "awilix"
|
||||
|
||||
/**
|
||||
* Registers all models in the model directory
|
||||
@@ -14,17 +14,13 @@ export default ({ container }: { container: MedusaContainer }): void => {
|
||||
|
||||
const core = glob.sync(coreFull, { cwd: __dirname })
|
||||
core.forEach((fn) => {
|
||||
const loaded = require(fn) as ClassConstructor<unknown>
|
||||
const loaded = require(fn).default
|
||||
|
||||
Object.entries(loaded).map(
|
||||
([, val]: [string, ClassConstructor<unknown>]) => {
|
||||
if (typeof val === "function") {
|
||||
const name = formatRegistrationName(fn)
|
||||
container.register({
|
||||
[name]: asClass(val),
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
if (typeof loaded === "object") {
|
||||
const name = formatRegistrationName(fn)
|
||||
container.register({
|
||||
[name]: asValue(loaded),
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
JoinTable,
|
||||
ManyToMany,
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
Unique,
|
||||
} from "typeorm"
|
||||
|
||||
@@ -19,6 +20,7 @@ import { ProductTag } from "./product-tag"
|
||||
import { ProductType } from "./product-type"
|
||||
import { SoftDeletableEntity } from "../interfaces/models/soft-deletable-entity"
|
||||
import { generateEntityId } from "../utils/generate-entity-id"
|
||||
import { Discount } from "./discount"
|
||||
|
||||
export enum DiscountConditionType {
|
||||
PRODUCTS = "products",
|
||||
|
||||
@@ -118,7 +118,7 @@ export class LineItem extends BaseEntity {
|
||||
@Column({ nullable: true, type: "text" })
|
||||
variant_id: string | null
|
||||
|
||||
@ManyToOne(() => ProductVariant, { eager: true })
|
||||
@ManyToOne(() => ProductVariant)
|
||||
@JoinColumn({ name: "variant_id" })
|
||||
variant: ProductVariant
|
||||
|
||||
|
||||
@@ -11,15 +11,8 @@ import {
|
||||
OneToMany,
|
||||
OneToOne,
|
||||
} from "typeorm"
|
||||
import {
|
||||
DbAwareColumn,
|
||||
resolveDbGenerationStrategy,
|
||||
resolveDbType,
|
||||
} from "../utils/db-aware-column"
|
||||
import {
|
||||
FeatureFlagColumn,
|
||||
FeatureFlagDecorators,
|
||||
} from "../utils/feature-flag-decorators"
|
||||
import { DbAwareColumn, resolveDbGenerationStrategy, resolveDbType, } from "../utils/db-aware-column"
|
||||
import { FeatureFlagColumn, FeatureFlagDecorators, } from "../utils/feature-flag-decorators"
|
||||
|
||||
import { BaseEntity } from "../interfaces/models/base-entity"
|
||||
import { generateEntityId } from "../utils/generate-entity-id"
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
import { DbAwareColumn, generateEntityId } from "../utils"
|
||||
import { MoneyAmount } from "./money-amount"
|
||||
import { Product } from "./product"
|
||||
import { ProductOptionValue } from "./product-option-value"
|
||||
import { SoftDeletableEntity } from "../interfaces"
|
||||
import {
|
||||
BeforeInsert,
|
||||
Column,
|
||||
@@ -5,15 +10,9 @@ import {
|
||||
Index,
|
||||
JoinColumn,
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
OneToMany
|
||||
} from "typeorm"
|
||||
|
||||
import { DbAwareColumn } from "../utils/db-aware-column"
|
||||
import { MoneyAmount } from "./money-amount"
|
||||
import { Product } from "./product"
|
||||
import { ProductOptionValue } from "./product-option-value"
|
||||
import { SoftDeletableEntity } from "../interfaces/models/soft-deletable-entity"
|
||||
import { generateEntityId } from "../utils/generate-entity-id"
|
||||
import { ProductVariantInventoryItem } from "./product-variant-inventory-item"
|
||||
|
||||
@Entity()
|
||||
@@ -25,7 +24,7 @@ export class ProductVariant extends SoftDeletableEntity {
|
||||
@Column()
|
||||
product_id: string
|
||||
|
||||
@ManyToOne(() => Product, (product) => product.variants, { eager: true })
|
||||
@ManyToOne(() => Product, (product) => product.variants)
|
||||
@JoinColumn({ name: "product_id" })
|
||||
product: Product
|
||||
|
||||
@@ -51,7 +50,7 @@ export class ProductVariant extends SoftDeletableEntity {
|
||||
@Index({ unique: true, where: "deleted_at IS NULL" })
|
||||
upc: string
|
||||
|
||||
@Column({ nullable: true, default: 0, select: false })
|
||||
@Column({ nullable: true, default: 0 })
|
||||
variant_rank: number
|
||||
|
||||
@Column({ type: "int" })
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { EntityRepository, Repository } from "typeorm"
|
||||
import { Address } from "../models/address"
|
||||
import { Address } from "../models"
|
||||
import { dataSource } from "../loaders/database"
|
||||
|
||||
@EntityRepository(Address)
|
||||
export class AddressRepository extends Repository<Address> {}
|
||||
export const AddressRepository = dataSource.getRepository(Address)
|
||||
export default AddressRepository
|
||||
@@ -1,5 +1,5 @@
|
||||
import { EntityRepository, Repository } from "typeorm"
|
||||
import { AnalyticsConfig } from "../models/analytics-config"
|
||||
import { dataSource } from "../loaders/database"
|
||||
|
||||
@EntityRepository(AnalyticsConfig)
|
||||
export class AnalyticsConfigRepository extends Repository<AnalyticsConfig> {}
|
||||
export const AnalyticsConfigRepository = dataSource.getRepository(AnalyticsConfig)
|
||||
export default AnalyticsConfigRepository
|
||||
@@ -1,5 +1,5 @@
|
||||
import { EntityRepository, Repository } from "typeorm"
|
||||
import { BatchJob } from "../models"
|
||||
import { dataSource } from "../loaders/database"
|
||||
|
||||
@EntityRepository(BatchJob)
|
||||
export class BatchJobRepository extends Repository<BatchJob> {}
|
||||
export const BatchJobRepository = dataSource.getRepository(BatchJob)
|
||||
export default BatchJobRepository
|
||||
@@ -1,53 +1,5 @@
|
||||
import { flatten, groupBy, map, merge } from "lodash"
|
||||
import { EntityRepository, FindManyOptions, Repository } from "typeorm"
|
||||
import { Cart } from "../models/cart"
|
||||
import { Cart } from "../models"
|
||||
import { dataSource } from "../loaders/database"
|
||||
|
||||
@EntityRepository(Cart)
|
||||
export class CartRepository extends Repository<Cart> {
|
||||
public async findWithRelations(
|
||||
relations: string[] = [],
|
||||
optionsWithoutRelations: Omit<FindManyOptions<Cart>, "relations"> = {}
|
||||
): Promise<Cart[]> {
|
||||
const entities = await this.find(optionsWithoutRelations)
|
||||
const entitiesIds = entities.map(({ id }) => id)
|
||||
|
||||
const groupedRelations = {}
|
||||
for (const rel of relations) {
|
||||
const [topLevel] = rel.split(".")
|
||||
if (groupedRelations[topLevel]) {
|
||||
groupedRelations[topLevel].push(rel)
|
||||
} else {
|
||||
groupedRelations[topLevel] = [rel]
|
||||
}
|
||||
}
|
||||
|
||||
const entitiesIdsWithRelations = await Promise.all(
|
||||
Object.entries(groupedRelations).map(async ([_, rels]) => {
|
||||
return this.findByIds(entitiesIds, {
|
||||
select: ["id"],
|
||||
relations: rels as string[],
|
||||
})
|
||||
})
|
||||
).then(flatten)
|
||||
const entitiesAndRelations = entitiesIdsWithRelations.concat(entities)
|
||||
|
||||
const entitiesAndRelationsById = groupBy(entitiesAndRelations, "id")
|
||||
return map(entitiesAndRelationsById, (entityAndRelations) =>
|
||||
merge({}, ...entityAndRelations)
|
||||
)
|
||||
}
|
||||
|
||||
public async findOneWithRelations(
|
||||
relations: string[] = [],
|
||||
optionsWithoutRelations: Omit<FindManyOptions<Cart>, "relations"> = {}
|
||||
): Promise<Cart> {
|
||||
// Limit 1
|
||||
optionsWithoutRelations.take = 1
|
||||
|
||||
const result = await this.findWithRelations(
|
||||
relations,
|
||||
optionsWithoutRelations
|
||||
)
|
||||
return result[0]
|
||||
}
|
||||
}
|
||||
export const CartRepository = dataSource.getRepository(Cart)
|
||||
export default CartRepository
|
||||
@@ -1,5 +1,5 @@
|
||||
import { EntityRepository, Repository } from "typeorm"
|
||||
import { ClaimImage } from "../models/claim-image"
|
||||
import { dataSource } from "../loaders/database"
|
||||
|
||||
@EntityRepository(ClaimImage)
|
||||
export class ClaimImageRepository extends Repository<ClaimImage> {}
|
||||
export const ClaimImageRepository = dataSource.getRepository(ClaimImage)
|
||||
export default ClaimImageRepository
|
||||
@@ -1,5 +1,5 @@
|
||||
import { EntityRepository, Repository } from "typeorm"
|
||||
import { ClaimItem } from "../models/claim-item"
|
||||
import { dataSource } from "../loaders/database"
|
||||
|
||||
@EntityRepository(ClaimItem)
|
||||
export class ClaimItemRepository extends Repository<ClaimItem> {}
|
||||
export const ClaimItemRepository = dataSource.getRepository(ClaimItem)
|
||||
export default ClaimItemRepository
|
||||
@@ -1,5 +1,5 @@
|
||||
import { EntityRepository, Repository } from "typeorm"
|
||||
import { ClaimTag } from "../models/claim-tag"
|
||||
import { dataSource } from "../loaders/database"
|
||||
|
||||
@EntityRepository(ClaimTag)
|
||||
export class ClaimTagRepository extends Repository<ClaimTag> {}
|
||||
export const ClaimTagRepository = dataSource.getRepository(ClaimTag)
|
||||
export default ClaimTagRepository
|
||||
@@ -1,5 +1,5 @@
|
||||
import { EntityRepository, Repository } from "typeorm"
|
||||
import { ClaimOrder } from "../models/claim-order"
|
||||
import { dataSource } from "../loaders/database"
|
||||
|
||||
@EntityRepository(ClaimOrder)
|
||||
export class ClaimRepository extends Repository<ClaimOrder> {}
|
||||
export const ClaimRepository = dataSource.getRepository(ClaimOrder)
|
||||
export default ClaimRepository
|
||||
@@ -1,5 +1,5 @@
|
||||
import { EntityRepository, Repository } from "typeorm"
|
||||
import { Country } from "../models/country"
|
||||
import { dataSource } from "../loaders/database"
|
||||
|
||||
@EntityRepository(Country)
|
||||
export class CountryRepository extends Repository<Country> {}
|
||||
export const CountryRepository = dataSource.getRepository(Country)
|
||||
export default CountryRepository
|
||||
@@ -1,5 +1,5 @@
|
||||
import { EntityRepository, Repository } from "typeorm"
|
||||
import { Currency } from "../models"
|
||||
import { dataSource } from "../loaders/database"
|
||||
|
||||
@EntityRepository(Currency)
|
||||
export class CurrencyRepository extends Repository<Currency> {}
|
||||
export const CurrencyRepository = dataSource.getRepository(Currency)
|
||||
export default CurrencyRepository
|
||||
@@ -1,6 +1,6 @@
|
||||
import { EntityRepository, Repository } from "typeorm"
|
||||
import { CustomShippingOption } from "./../models/custom-shipping-option"
|
||||
import { CustomShippingOption } from "../models"
|
||||
import { dataSource } from "../loaders/database"
|
||||
|
||||
@EntityRepository(CustomShippingOption)
|
||||
// eslint-disable-next-line max-len
|
||||
export class CustomShippingOptionRepository extends Repository<CustomShippingOption> {}
|
||||
export const CustomShippingOptionRepository =
|
||||
dataSource.getRepository(CustomShippingOption)
|
||||
export default CustomShippingOptionRepository
|
||||
@@ -1,22 +1,23 @@
|
||||
import {
|
||||
DeleteResult,
|
||||
EntityRepository,
|
||||
FindOperator,
|
||||
FindOptionsRelations,
|
||||
In,
|
||||
Repository,
|
||||
SelectQueryBuilder,
|
||||
} from "typeorm"
|
||||
import { CustomerGroup } from "../models"
|
||||
import { ExtendedFindConfig, Selector } from "../types/common"
|
||||
import { ExtendedFindConfig } from "../types/common"
|
||||
import {
|
||||
getGroupedRelations,
|
||||
mergeEntitiesWithRelations,
|
||||
queryEntityWithIds,
|
||||
queryEntityWithoutRelations,
|
||||
} from "../utils/repository"
|
||||
import { buildLegacyFieldsListFrom } from "../utils"
|
||||
import { dataSource } from "../loaders/database"
|
||||
|
||||
export type DefaultWithoutRelations = Omit<
|
||||
ExtendedFindConfig<CustomerGroup, Selector<CustomerGroup>>,
|
||||
ExtendedFindConfig<CustomerGroup>,
|
||||
"relations"
|
||||
>
|
||||
|
||||
@@ -26,118 +27,134 @@ export type FindWithoutRelationsOptions = DefaultWithoutRelations & {
|
||||
}
|
||||
}
|
||||
|
||||
@EntityRepository(CustomerGroup)
|
||||
export class CustomerGroupRepository extends Repository<CustomerGroup> {
|
||||
async addCustomers(
|
||||
groupId: string,
|
||||
customerIds: string[]
|
||||
): Promise<CustomerGroup> {
|
||||
const customerGroup = await this.findOne(groupId)
|
||||
|
||||
await this.createQueryBuilder()
|
||||
.insert()
|
||||
.into("customer_group_customers")
|
||||
.values(
|
||||
customerIds.map((id) => ({
|
||||
customer_id: id,
|
||||
customer_group_id: groupId,
|
||||
}))
|
||||
)
|
||||
.orIgnore()
|
||||
.execute()
|
||||
|
||||
return customerGroup as CustomerGroup
|
||||
}
|
||||
|
||||
async removeCustomers(
|
||||
groupId: string,
|
||||
customerIds: string[]
|
||||
): Promise<DeleteResult> {
|
||||
return await this.createQueryBuilder()
|
||||
.delete()
|
||||
.from("customer_group_customers")
|
||||
.where({
|
||||
customer_group_id: groupId,
|
||||
customer_id: In(customerIds),
|
||||
export const CustomerGroupRepository = dataSource
|
||||
.getRepository(CustomerGroup)
|
||||
.extend({
|
||||
async addCustomers(
|
||||
groupId: string,
|
||||
customerIds: string[]
|
||||
): Promise<CustomerGroup> {
|
||||
const customerGroup = await this.findOne({
|
||||
where: {
|
||||
id: groupId,
|
||||
},
|
||||
})
|
||||
.execute()
|
||||
}
|
||||
|
||||
public async findWithRelationsAndCount(
|
||||
relations: string[] = [],
|
||||
idsOrOptionsWithoutRelations: FindWithoutRelationsOptions = { where: {} }
|
||||
): Promise<[CustomerGroup[], number]> {
|
||||
let count: number
|
||||
let entities: CustomerGroup[]
|
||||
if (Array.isArray(idsOrOptionsWithoutRelations)) {
|
||||
entities = await this.findByIds(idsOrOptionsWithoutRelations, {
|
||||
withDeleted: idsOrOptionsWithoutRelations.withDeleted ?? false,
|
||||
})
|
||||
count = entities.length
|
||||
} else {
|
||||
const customJoinsBuilders: ((
|
||||
qb: SelectQueryBuilder<CustomerGroup>,
|
||||
alias: string
|
||||
) => void)[] = []
|
||||
|
||||
if (idsOrOptionsWithoutRelations?.where?.discount_condition_id) {
|
||||
const discountConditionId =
|
||||
idsOrOptionsWithoutRelations?.where?.discount_condition_id
|
||||
delete idsOrOptionsWithoutRelations?.where?.discount_condition_id
|
||||
|
||||
customJoinsBuilders.push(
|
||||
(qb: SelectQueryBuilder<CustomerGroup>, alias: string) => {
|
||||
qb.innerJoin(
|
||||
"discount_condition_customer_group",
|
||||
"dc_cg",
|
||||
`dc_cg.customer_group_id = ${alias}.id AND dc_cg.condition_id = :dcId`,
|
||||
{ dcId: discountConditionId }
|
||||
)
|
||||
}
|
||||
await this.createQueryBuilder()
|
||||
.insert()
|
||||
.into("customer_group_customers")
|
||||
.values(
|
||||
customerIds.map((id) => ({
|
||||
customer_id: id,
|
||||
customer_group_id: groupId,
|
||||
}))
|
||||
)
|
||||
.orIgnore()
|
||||
.execute()
|
||||
|
||||
return customerGroup as CustomerGroup
|
||||
},
|
||||
|
||||
async removeCustomers(
|
||||
groupId: string,
|
||||
customerIds: string[]
|
||||
): Promise<DeleteResult> {
|
||||
return await this.createQueryBuilder()
|
||||
.delete()
|
||||
.from("customer_group_customers")
|
||||
.where({
|
||||
customer_group_id: groupId,
|
||||
customer_id: In(customerIds),
|
||||
})
|
||||
.execute()
|
||||
},
|
||||
|
||||
async findWithRelationsAndCount(
|
||||
relations: FindOptionsRelations<CustomerGroup> = {},
|
||||
idsOrOptionsWithoutRelations: FindWithoutRelationsOptions = { where: {} }
|
||||
): Promise<[CustomerGroup[], number]> {
|
||||
let count: number
|
||||
let entities: CustomerGroup[]
|
||||
if (Array.isArray(idsOrOptionsWithoutRelations)) {
|
||||
entities = await this.find({
|
||||
where: { id: In(idsOrOptionsWithoutRelations) },
|
||||
withDeleted: idsOrOptionsWithoutRelations.withDeleted ?? false,
|
||||
})
|
||||
count = entities.length
|
||||
} else {
|
||||
const customJoinsBuilders: ((
|
||||
qb: SelectQueryBuilder<CustomerGroup>,
|
||||
alias: string
|
||||
) => void)[] = []
|
||||
|
||||
if (idsOrOptionsWithoutRelations?.where?.discount_condition_id) {
|
||||
const discountConditionId =
|
||||
idsOrOptionsWithoutRelations?.where?.discount_condition_id
|
||||
delete idsOrOptionsWithoutRelations?.where?.discount_condition_id
|
||||
|
||||
customJoinsBuilders.push(
|
||||
(qb: SelectQueryBuilder<CustomerGroup>, alias: string) => {
|
||||
qb.innerJoin(
|
||||
"discount_condition_customer_group",
|
||||
"dc_cg",
|
||||
`dc_cg.customer_group_id = ${alias}.id AND dc_cg.condition_id = :dcId`,
|
||||
{ dcId: discountConditionId }
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
const result = await queryEntityWithoutRelations(
|
||||
this,
|
||||
idsOrOptionsWithoutRelations,
|
||||
true,
|
||||
customJoinsBuilders
|
||||
)
|
||||
entities = result[0]
|
||||
count = result[1]
|
||||
}
|
||||
const entitiesIds = entities.map(({ id }) => id)
|
||||
|
||||
if (entitiesIds.length === 0) {
|
||||
// no need to continue
|
||||
return [[], count]
|
||||
}
|
||||
|
||||
const result = await queryEntityWithoutRelations(
|
||||
if (Object.keys(relations).length === 0) {
|
||||
const options = { ...idsOrOptionsWithoutRelations }
|
||||
|
||||
// Since we are finding by the ids that have been retrieved above and those ids are already
|
||||
// applying skip/take. Remove those options to avoid getting no results
|
||||
delete options.skip
|
||||
delete options.take
|
||||
|
||||
const toReturn = await this.find({
|
||||
...options,
|
||||
where: { id: In(entitiesIds) },
|
||||
})
|
||||
return [toReturn, toReturn.length]
|
||||
}
|
||||
|
||||
const legacyRelations = buildLegacyFieldsListFrom(relations)
|
||||
const groupedRelations = getGroupedRelations(legacyRelations)
|
||||
|
||||
const legacySelect = buildLegacyFieldsListFrom(
|
||||
idsOrOptionsWithoutRelations.select
|
||||
)
|
||||
const entitiesIdsWithRelations = await queryEntityWithIds(
|
||||
this,
|
||||
idsOrOptionsWithoutRelations,
|
||||
true,
|
||||
customJoinsBuilders
|
||||
entitiesIds,
|
||||
groupedRelations,
|
||||
idsOrOptionsWithoutRelations.withDeleted,
|
||||
legacySelect
|
||||
)
|
||||
entities = result[0]
|
||||
count = result[1]
|
||||
}
|
||||
const entitiesIds = entities.map(({ id }) => id)
|
||||
|
||||
if (entitiesIds.length === 0) {
|
||||
// no need to continue
|
||||
return [[], count]
|
||||
}
|
||||
const entitiesAndRelations = entitiesIdsWithRelations.concat(entities)
|
||||
const entitiesToReturn =
|
||||
mergeEntitiesWithRelations<CustomerGroup>(entitiesAndRelations)
|
||||
|
||||
if (relations.length === 0) {
|
||||
const options = { ...idsOrOptionsWithoutRelations }
|
||||
return [entitiesToReturn, count]
|
||||
},
|
||||
})
|
||||
|
||||
// Since we are finding by the ids that have been retrieved above and those ids are already
|
||||
// applying skip/take. Remove those options to avoid getting no results
|
||||
delete options.skip
|
||||
delete options.take
|
||||
|
||||
const toReturn = await this.findByIds(entitiesIds, options)
|
||||
return [toReturn, toReturn.length]
|
||||
}
|
||||
|
||||
const groupedRelations = getGroupedRelations(relations)
|
||||
const entitiesIdsWithRelations = await queryEntityWithIds(
|
||||
this,
|
||||
entitiesIds,
|
||||
groupedRelations,
|
||||
idsOrOptionsWithoutRelations.withDeleted,
|
||||
idsOrOptionsWithoutRelations.select
|
||||
)
|
||||
|
||||
const entitiesAndRelations = entitiesIdsWithRelations.concat(entities)
|
||||
const entitiesToReturn =
|
||||
mergeEntitiesWithRelations<CustomerGroup>(entitiesAndRelations)
|
||||
|
||||
return [entitiesToReturn, count]
|
||||
}
|
||||
}
|
||||
export default CustomerGroupRepository
|
||||
@@ -1,49 +1,49 @@
|
||||
import { Brackets, EntityRepository, ILike, Repository } from "typeorm"
|
||||
import { Customer } from "../models/customer"
|
||||
import { ExtendedFindConfig, Selector } from "../types/common"
|
||||
import { FindOperator, FindOptionsWhere, ILike, In } from "typeorm"
|
||||
import { Customer } from "../models"
|
||||
import { dataSource } from "../loaders/database"
|
||||
import { ExtendedFindConfig } from "../types/common"
|
||||
|
||||
@EntityRepository(Customer)
|
||||
export class CustomerRepository extends Repository<Customer> {
|
||||
export const CustomerRepository = dataSource.getRepository(Customer).extend({
|
||||
async listAndCount(
|
||||
query: ExtendedFindConfig<Customer, Selector<Customer>>,
|
||||
query: ExtendedFindConfig<Customer> & {
|
||||
where: FindOptionsWhere<Customer & { groups?: FindOperator<string[]> }>
|
||||
},
|
||||
q: string | undefined = undefined
|
||||
): Promise<[Customer[], number]> {
|
||||
const groups = query.where.groups as { value: string[] }
|
||||
delete query.where.groups
|
||||
const query_ = { ...query }
|
||||
|
||||
const qb = this.createQueryBuilder("customer")
|
||||
.skip(query.skip)
|
||||
.take(query.take)
|
||||
if (query_.where.groups) {
|
||||
query_.relations = query_.relations ?? {}
|
||||
query_.relations.groups = query_.relations.groups ?? true
|
||||
|
||||
query.where.groups = {
|
||||
id: In((query_.where.groups as FindOperator<string[]>).value),
|
||||
}
|
||||
}
|
||||
|
||||
if (q) {
|
||||
delete query.where.email
|
||||
delete query.where.first_name
|
||||
delete query.where.last_name
|
||||
query_.where = query_.where as FindOptionsWhere<Customer>
|
||||
delete query_.where.email
|
||||
delete query_.where.first_name
|
||||
delete query_.where.last_name
|
||||
|
||||
qb.where(
|
||||
new Brackets((qb) => {
|
||||
qb.where({ email: ILike(`%${q}%`) })
|
||||
.orWhere({ first_name: ILike(`%${q}%`) })
|
||||
.orWhere({ last_name: ILike(`%${q}%`) })
|
||||
})
|
||||
)
|
||||
query_.where = [
|
||||
{
|
||||
...query_.where,
|
||||
email: ILike(`%${q}%`),
|
||||
},
|
||||
{
|
||||
...query_.where,
|
||||
first_name: ILike(`%${q}%`),
|
||||
},
|
||||
{
|
||||
...query_.where,
|
||||
last_name: ILike(`%${q}%`),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
qb.andWhere(query.where)
|
||||
|
||||
if (groups) {
|
||||
qb.leftJoinAndSelect("customer.groups", "group").andWhere(
|
||||
`group.id IN (:...ids)`,
|
||||
{ ids: groups.value }
|
||||
)
|
||||
}
|
||||
|
||||
if (query.relations?.length) {
|
||||
query.relations.forEach((rel) => {
|
||||
qb.leftJoinAndSelect(`customer.${rel}`, rel)
|
||||
})
|
||||
}
|
||||
|
||||
return await qb.getManyAndCount()
|
||||
}
|
||||
}
|
||||
return await this.findAndCount(query_)
|
||||
},
|
||||
})
|
||||
export default CustomerRepository
|
||||
@@ -1,11 +1,4 @@
|
||||
import {
|
||||
DeleteResult,
|
||||
EntityRepository,
|
||||
EntityTarget,
|
||||
In,
|
||||
Not,
|
||||
Repository,
|
||||
} from "typeorm"
|
||||
import { DeleteResult, EntityTarget, In, Not } from "typeorm"
|
||||
import {
|
||||
Discount,
|
||||
DiscountCondition,
|
||||
@@ -18,6 +11,7 @@ import {
|
||||
DiscountConditionType,
|
||||
} from "../models"
|
||||
import { isString } from "../utils"
|
||||
import { dataSource } from "../loaders/database"
|
||||
|
||||
export enum DiscountConditionJoinTableForeignKey {
|
||||
PRODUCT_ID = "product_id",
|
||||
@@ -35,301 +29,305 @@ type DiscountConditionResourceType = EntityTarget<
|
||||
| DiscountConditionCustomerGroup
|
||||
>
|
||||
|
||||
@EntityRepository(DiscountCondition)
|
||||
export class DiscountConditionRepository extends Repository<DiscountCondition> {
|
||||
async findOneWithDiscount(
|
||||
conditionId: string,
|
||||
discountId: string
|
||||
): Promise<(DiscountCondition & { discount: Discount }) | undefined> {
|
||||
return (await this.createQueryBuilder("condition")
|
||||
.leftJoinAndMapOne(
|
||||
"condition.discount",
|
||||
Discount,
|
||||
"discount",
|
||||
`condition.discount_rule_id = discount.rule_id and discount.id = :discId and condition.id = :dcId`,
|
||||
{ discId: discountId, dcId: conditionId }
|
||||
)
|
||||
.getOne()) as (DiscountCondition & { discount: Discount }) | undefined
|
||||
}
|
||||
export const DiscountConditionRepository = dataSource
|
||||
.getRepository(DiscountCondition)
|
||||
.extend({
|
||||
async findOneWithDiscount(
|
||||
conditionId: string,
|
||||
discountId: string
|
||||
): Promise<(DiscountCondition & { discount: Discount }) | undefined> {
|
||||
return (await this.createQueryBuilder("condition")
|
||||
.leftJoinAndMapOne(
|
||||
"condition.discount",
|
||||
Discount,
|
||||
"discount",
|
||||
`condition.discount_rule_id = discount.rule_id and discount.id = :discId and condition.id = :dcId`,
|
||||
{ discId: discountId, dcId: conditionId }
|
||||
)
|
||||
.getOne()) as (DiscountCondition & { discount: Discount }) | undefined
|
||||
},
|
||||
|
||||
getJoinTableResourceIdentifiers(type: string): {
|
||||
joinTable: string
|
||||
resourceKey: string
|
||||
joinTableForeignKey: DiscountConditionJoinTableForeignKey
|
||||
conditionTable: DiscountConditionResourceType
|
||||
joinTableKey: string
|
||||
} {
|
||||
let conditionTable: DiscountConditionResourceType = DiscountConditionProduct
|
||||
getJoinTableResourceIdentifiers(type: string): {
|
||||
joinTable: string
|
||||
resourceKey: string
|
||||
joinTableForeignKey: DiscountConditionJoinTableForeignKey
|
||||
conditionTable: DiscountConditionResourceType
|
||||
joinTableKey: string
|
||||
} {
|
||||
let conditionTable: DiscountConditionResourceType =
|
||||
DiscountConditionProduct
|
||||
|
||||
let joinTable = "product"
|
||||
let joinTableForeignKey: DiscountConditionJoinTableForeignKey =
|
||||
DiscountConditionJoinTableForeignKey.PRODUCT_ID
|
||||
let joinTableKey = "id"
|
||||
let joinTable = "product"
|
||||
let joinTableForeignKey: DiscountConditionJoinTableForeignKey =
|
||||
DiscountConditionJoinTableForeignKey.PRODUCT_ID
|
||||
let joinTableKey = "id"
|
||||
|
||||
// On the joined table (e.g. `product`), what key should be match on
|
||||
// (e.g `type_id` for product types and `id` for products)
|
||||
let resourceKey
|
||||
// On the joined table (e.g. `product`), what key should be match on
|
||||
// (e.g `type_id` for product types and `id` for products)
|
||||
let resourceKey
|
||||
|
||||
switch (type) {
|
||||
case DiscountConditionType.PRODUCTS: {
|
||||
resourceKey = "id"
|
||||
joinTableForeignKey = DiscountConditionJoinTableForeignKey.PRODUCT_ID
|
||||
joinTable = "product"
|
||||
switch (type) {
|
||||
case DiscountConditionType.PRODUCTS: {
|
||||
resourceKey = "id"
|
||||
joinTableForeignKey = DiscountConditionJoinTableForeignKey.PRODUCT_ID
|
||||
joinTable = "product"
|
||||
|
||||
conditionTable = DiscountConditionProduct
|
||||
break
|
||||
conditionTable = DiscountConditionProduct
|
||||
break
|
||||
}
|
||||
case DiscountConditionType.PRODUCT_TYPES: {
|
||||
resourceKey = "type_id"
|
||||
joinTableForeignKey =
|
||||
DiscountConditionJoinTableForeignKey.PRODUCT_TYPE_ID
|
||||
joinTable = "product"
|
||||
|
||||
conditionTable = DiscountConditionProductType
|
||||
break
|
||||
}
|
||||
case DiscountConditionType.PRODUCT_COLLECTIONS: {
|
||||
resourceKey = "collection_id"
|
||||
joinTableForeignKey =
|
||||
DiscountConditionJoinTableForeignKey.PRODUCT_COLLECTION_ID
|
||||
joinTable = "product"
|
||||
|
||||
conditionTable = DiscountConditionProductCollection
|
||||
break
|
||||
}
|
||||
case DiscountConditionType.PRODUCT_TAGS: {
|
||||
joinTableKey = "product_id"
|
||||
resourceKey = "product_tag_id"
|
||||
joinTableForeignKey =
|
||||
DiscountConditionJoinTableForeignKey.PRODUCT_TAG_ID
|
||||
joinTable = "product_tags"
|
||||
|
||||
conditionTable = DiscountConditionProductTag
|
||||
break
|
||||
}
|
||||
case DiscountConditionType.CUSTOMER_GROUPS: {
|
||||
joinTableKey = "customer_id"
|
||||
resourceKey = "customer_group_id"
|
||||
joinTable = "customer_group_customers"
|
||||
joinTableForeignKey =
|
||||
DiscountConditionJoinTableForeignKey.CUSTOMER_GROUP_ID
|
||||
|
||||
conditionTable = DiscountConditionCustomerGroup
|
||||
break
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
case DiscountConditionType.PRODUCT_TYPES: {
|
||||
resourceKey = "type_id"
|
||||
joinTableForeignKey =
|
||||
DiscountConditionJoinTableForeignKey.PRODUCT_TYPE_ID
|
||||
joinTable = "product"
|
||||
|
||||
conditionTable = DiscountConditionProductType
|
||||
break
|
||||
return {
|
||||
joinTable,
|
||||
joinTableKey,
|
||||
resourceKey,
|
||||
joinTableForeignKey,
|
||||
conditionTable,
|
||||
}
|
||||
case DiscountConditionType.PRODUCT_COLLECTIONS: {
|
||||
resourceKey = "collection_id"
|
||||
joinTableForeignKey =
|
||||
DiscountConditionJoinTableForeignKey.PRODUCT_COLLECTION_ID
|
||||
joinTable = "product"
|
||||
},
|
||||
|
||||
conditionTable = DiscountConditionProductCollection
|
||||
break
|
||||
async removeConditionResources(
|
||||
id: string,
|
||||
type: DiscountConditionType,
|
||||
resourceIds: (string | { id: string })[]
|
||||
): Promise<DeleteResult | void> {
|
||||
const { conditionTable, joinTableForeignKey } =
|
||||
this.getJoinTableResourceIdentifiers(type)
|
||||
|
||||
if (!conditionTable || !joinTableForeignKey) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
case DiscountConditionType.PRODUCT_TAGS: {
|
||||
joinTableKey = "product_id"
|
||||
resourceKey = "product_tag_id"
|
||||
joinTableForeignKey =
|
||||
DiscountConditionJoinTableForeignKey.PRODUCT_TAG_ID
|
||||
joinTable = "product_tags"
|
||||
|
||||
conditionTable = DiscountConditionProductTag
|
||||
break
|
||||
}
|
||||
case DiscountConditionType.CUSTOMER_GROUPS: {
|
||||
joinTableKey = "customer_id"
|
||||
resourceKey = "customer_group_id"
|
||||
joinTable = "customer_group_customers"
|
||||
joinTableForeignKey =
|
||||
DiscountConditionJoinTableForeignKey.CUSTOMER_GROUP_ID
|
||||
|
||||
conditionTable = DiscountConditionCustomerGroup
|
||||
break
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
return {
|
||||
joinTable,
|
||||
joinTableKey,
|
||||
resourceKey,
|
||||
joinTableForeignKey,
|
||||
conditionTable,
|
||||
}
|
||||
}
|
||||
|
||||
async removeConditionResources(
|
||||
id: string,
|
||||
type: DiscountConditionType,
|
||||
resourceIds: (string | { id: string })[]
|
||||
): Promise<DeleteResult | void> {
|
||||
const { conditionTable, joinTableForeignKey } =
|
||||
this.getJoinTableResourceIdentifiers(type)
|
||||
|
||||
if (!conditionTable || !joinTableForeignKey) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
const idsToDelete = resourceIds.map((rId): string => {
|
||||
return isString(rId) ? rId : rId.id
|
||||
})
|
||||
return await this.createQueryBuilder()
|
||||
.delete()
|
||||
.from(conditionTable)
|
||||
.where({ condition_id: id, [joinTableForeignKey]: In(idsToDelete) })
|
||||
.execute()
|
||||
}
|
||||
|
||||
async addConditionResources(
|
||||
conditionId: string,
|
||||
resourceIds: (string | { id: string })[],
|
||||
type: DiscountConditionType,
|
||||
overrideExisting = false
|
||||
): Promise<
|
||||
(
|
||||
| DiscountConditionProduct
|
||||
| DiscountConditionProductType
|
||||
| DiscountConditionProductCollection
|
||||
| DiscountConditionProductTag
|
||||
| DiscountConditionCustomerGroup
|
||||
)[]
|
||||
> {
|
||||
let toInsert: { condition_id: string; [x: string]: string }[] | [] = []
|
||||
|
||||
const { conditionTable, joinTableForeignKey } =
|
||||
this.getJoinTableResourceIdentifiers(type)
|
||||
|
||||
if (!conditionTable || !joinTableForeignKey) {
|
||||
return Promise.resolve([])
|
||||
}
|
||||
|
||||
const idsToInsert = resourceIds.map((rId): string => {
|
||||
return isString(rId) ? rId : rId.id
|
||||
})
|
||||
toInsert = idsToInsert.map((rId) => ({
|
||||
condition_id: conditionId,
|
||||
[joinTableForeignKey]: rId,
|
||||
}))
|
||||
|
||||
const insertResult = await this.createQueryBuilder()
|
||||
.insert()
|
||||
.orIgnore(true)
|
||||
.into(conditionTable)
|
||||
.values(toInsert)
|
||||
.execute()
|
||||
|
||||
if (overrideExisting) {
|
||||
await this.createQueryBuilder()
|
||||
const idsToDelete = resourceIds.map((rId): string => {
|
||||
return isString(rId) ? rId : rId.id
|
||||
})
|
||||
return await this.createQueryBuilder()
|
||||
.delete()
|
||||
.from(conditionTable)
|
||||
.where({
|
||||
condition_id: conditionId,
|
||||
[joinTableForeignKey]: Not(In(idsToInsert)),
|
||||
})
|
||||
.where({ condition_id: id, [joinTableForeignKey]: In(idsToDelete) })
|
||||
.execute()
|
||||
}
|
||||
},
|
||||
|
||||
return await this.manager
|
||||
.createQueryBuilder(conditionTable, "discon")
|
||||
.select()
|
||||
.where(insertResult.identifiers)
|
||||
.getMany()
|
||||
}
|
||||
async addConditionResources(
|
||||
conditionId: string,
|
||||
resourceIds: (string | { id: string })[],
|
||||
type: DiscountConditionType,
|
||||
overrideExisting = false
|
||||
): Promise<
|
||||
(
|
||||
| DiscountConditionProduct
|
||||
| DiscountConditionProductType
|
||||
| DiscountConditionProductCollection
|
||||
| DiscountConditionProductTag
|
||||
| DiscountConditionCustomerGroup
|
||||
)[]
|
||||
> {
|
||||
let toInsert: { condition_id: string; [x: string]: string }[] | [] = []
|
||||
|
||||
async queryConditionTable({ type, condId, resourceId }): Promise<number> {
|
||||
const {
|
||||
conditionTable,
|
||||
joinTable,
|
||||
joinTableForeignKey,
|
||||
resourceKey,
|
||||
joinTableKey,
|
||||
} = this.getJoinTableResourceIdentifiers(type)
|
||||
const { conditionTable, joinTableForeignKey } =
|
||||
this.getJoinTableResourceIdentifiers(type)
|
||||
|
||||
return await this.manager
|
||||
.createQueryBuilder(conditionTable, "dc")
|
||||
.innerJoin(
|
||||
if (!conditionTable || !joinTableForeignKey) {
|
||||
return Promise.resolve([])
|
||||
}
|
||||
|
||||
const idsToInsert = resourceIds.map((rId): string => {
|
||||
return isString(rId) ? rId : rId.id
|
||||
})
|
||||
toInsert = idsToInsert.map((rId) => ({
|
||||
condition_id: conditionId,
|
||||
[joinTableForeignKey]: rId,
|
||||
}))
|
||||
|
||||
const insertResult = await this.createQueryBuilder()
|
||||
.insert()
|
||||
.orIgnore(true)
|
||||
.into(conditionTable)
|
||||
.values(toInsert)
|
||||
.execute()
|
||||
|
||||
if (overrideExisting) {
|
||||
await this.createQueryBuilder()
|
||||
.delete()
|
||||
.from(conditionTable)
|
||||
.where({
|
||||
condition_id: conditionId,
|
||||
[joinTableForeignKey]: Not(In(idsToInsert)),
|
||||
})
|
||||
.execute()
|
||||
}
|
||||
|
||||
return await this.manager
|
||||
.createQueryBuilder(conditionTable, "discon")
|
||||
.select()
|
||||
.where(insertResult.identifiers)
|
||||
.getMany()
|
||||
},
|
||||
|
||||
async queryConditionTable({ type, condId, resourceId }): Promise<number> {
|
||||
const {
|
||||
conditionTable,
|
||||
joinTable,
|
||||
"resource",
|
||||
`dc.${joinTableForeignKey} = resource.${resourceKey} and resource.${joinTableKey} = :resourceId `,
|
||||
{
|
||||
resourceId,
|
||||
joinTableForeignKey,
|
||||
resourceKey,
|
||||
joinTableKey,
|
||||
} = this.getJoinTableResourceIdentifiers(type)
|
||||
|
||||
return await this.manager
|
||||
.createQueryBuilder(conditionTable, "dc")
|
||||
.innerJoin(
|
||||
joinTable,
|
||||
"resource",
|
||||
`dc.${joinTableForeignKey} = resource.${resourceKey} and resource.${joinTableKey} = :resourceId `,
|
||||
{
|
||||
resourceId,
|
||||
}
|
||||
)
|
||||
.where(`dc.condition_id = :conditionId`, {
|
||||
conditionId: condId,
|
||||
})
|
||||
.getCount()
|
||||
},
|
||||
|
||||
async isValidForProduct(
|
||||
discountRuleId: string,
|
||||
productId: string
|
||||
): Promise<boolean> {
|
||||
const discountConditions = await this.createQueryBuilder("discon")
|
||||
.select(["discon.id", "discon.type", "discon.operator"])
|
||||
.where("discon.discount_rule_id = :discountRuleId", {
|
||||
discountRuleId,
|
||||
})
|
||||
.getMany()
|
||||
|
||||
// in case of no discount conditions, we assume that the discount
|
||||
// is valid for all
|
||||
if (!discountConditions.length) {
|
||||
return true
|
||||
}
|
||||
|
||||
// retrieve all conditions for each type where condition type id is in jointable (products, product_types, product_collections, product_tags)
|
||||
// "E.g. for a given product condition, give me all products affected by it"
|
||||
// for each of these types, we check:
|
||||
// if condition operation is `in` and the query for conditions defined for the given type is empty, the discount is invalid
|
||||
// if condition operation is `not_in` and the query for conditions defined for the given type is not empty, the discount is invalid
|
||||
for (const condition of discountConditions) {
|
||||
if (condition.type === DiscountConditionType.CUSTOMER_GROUPS) {
|
||||
continue
|
||||
}
|
||||
)
|
||||
.where(`dc.condition_id = :conditionId`, {
|
||||
conditionId: condId,
|
||||
})
|
||||
.getCount()
|
||||
}
|
||||
|
||||
async isValidForProduct(
|
||||
discountRuleId: string,
|
||||
productId: string
|
||||
): Promise<boolean> {
|
||||
const discountConditions = await this.createQueryBuilder("discon")
|
||||
.select(["discon.id", "discon.type", "discon.operator"])
|
||||
.where("discon.discount_rule_id = :discountRuleId", {
|
||||
discountRuleId,
|
||||
})
|
||||
.getMany()
|
||||
const numConditions = await this.queryConditionTable({
|
||||
type: condition.type,
|
||||
condId: condition.id,
|
||||
resourceId: productId,
|
||||
})
|
||||
|
||||
if (
|
||||
condition.operator === DiscountConditionOperator.IN &&
|
||||
numConditions === 0
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (
|
||||
condition.operator === DiscountConditionOperator.NOT_IN &&
|
||||
numConditions > 0
|
||||
) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// in case of no discount conditions, we assume that the discount
|
||||
// is valid for all
|
||||
if (!discountConditions.length) {
|
||||
return true
|
||||
}
|
||||
},
|
||||
|
||||
// retrieve all conditions for each type where condition type id is in jointable (products, product_types, product_collections, product_tags)
|
||||
// "E.g. for a given product condition, give me all products affected by it"
|
||||
// for each of these types, we check:
|
||||
// if condition operation is `in` and the query for conditions defined for the given type is empty, the discount is invalid
|
||||
// if condition operation is `not_in` and the query for conditions defined for the given type is not empty, the discount is invalid
|
||||
for (const condition of discountConditions) {
|
||||
if (condition.type === DiscountConditionType.CUSTOMER_GROUPS) {
|
||||
continue
|
||||
async canApplyForCustomer(
|
||||
discountRuleId: string,
|
||||
customerId: string
|
||||
): Promise<boolean> {
|
||||
const discountConditions = await this.createQueryBuilder("discon")
|
||||
.select(["discon.id", "discon.type", "discon.operator"])
|
||||
.where("discon.discount_rule_id = :discountRuleId", {
|
||||
discountRuleId,
|
||||
})
|
||||
.andWhere("discon.type = :type", {
|
||||
type: DiscountConditionType.CUSTOMER_GROUPS,
|
||||
})
|
||||
.getMany()
|
||||
|
||||
// in case of no discount conditions, we assume that the discount
|
||||
// is valid for all
|
||||
if (!discountConditions.length) {
|
||||
return true
|
||||
}
|
||||
|
||||
const numConditions = await this.queryConditionTable({
|
||||
type: condition.type,
|
||||
condId: condition.id,
|
||||
resourceId: productId,
|
||||
})
|
||||
// retrieve conditions for customer groups
|
||||
// for each customer group
|
||||
// if condition operation is `in` and the query for customer group conditions is empty, the discount is invalid
|
||||
// if condition operation is `not_in` and the query for customer group conditions is not empty, the discount is invalid
|
||||
for (const condition of discountConditions) {
|
||||
const numConditions = await this.queryConditionTable({
|
||||
type: "customer_groups",
|
||||
condId: condition.id,
|
||||
resourceId: customerId,
|
||||
})
|
||||
|
||||
if (
|
||||
condition.operator === DiscountConditionOperator.IN &&
|
||||
numConditions === 0
|
||||
) {
|
||||
return false
|
||||
if (
|
||||
condition.operator === DiscountConditionOperator.IN &&
|
||||
numConditions === 0
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (
|
||||
condition.operator === DiscountConditionOperator.NOT_IN &&
|
||||
numConditions > 0
|
||||
) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
condition.operator === DiscountConditionOperator.NOT_IN &&
|
||||
numConditions > 0
|
||||
) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
async canApplyForCustomer(
|
||||
discountRuleId: string,
|
||||
customerId: string
|
||||
): Promise<boolean> {
|
||||
const discountConditions = await this.createQueryBuilder("discon")
|
||||
.select(["discon.id", "discon.type", "discon.operator"])
|
||||
.where("discon.discount_rule_id = :discountRuleId", {
|
||||
discountRuleId,
|
||||
})
|
||||
.andWhere("discon.type = :type", {
|
||||
type: DiscountConditionType.CUSTOMER_GROUPS,
|
||||
})
|
||||
.getMany()
|
||||
|
||||
// in case of no discount conditions, we assume that the discount
|
||||
// is valid for all
|
||||
if (!discountConditions.length) {
|
||||
return true
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
// retrieve conditions for customer groups
|
||||
// for each customer group
|
||||
// if condition operation is `in` and the query for customer group conditions is empty, the discount is invalid
|
||||
// if condition operation is `not_in` and the query for customer group conditions is not empty, the discount is invalid
|
||||
for (const condition of discountConditions) {
|
||||
const numConditions = await this.queryConditionTable({
|
||||
type: "customer_groups",
|
||||
condId: condition.id,
|
||||
resourceId: customerId,
|
||||
})
|
||||
|
||||
if (
|
||||
condition.operator === DiscountConditionOperator.IN &&
|
||||
numConditions === 0
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (
|
||||
condition.operator === DiscountConditionOperator.NOT_IN &&
|
||||
numConditions > 0
|
||||
) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
export default DiscountConditionRepository
|
||||
@@ -1,5 +1,5 @@
|
||||
import { EntityRepository, Repository } from "typeorm"
|
||||
import { DiscountRule } from "../models/discount-rule"
|
||||
import { DiscountRule } from "../models"
|
||||
import { dataSource } from "../loaders/database"
|
||||
|
||||
@EntityRepository(DiscountRule)
|
||||
export class DiscountRuleRepository extends Repository<DiscountRule> {}
|
||||
export const DiscountRuleRepository = dataSource.getRepository(DiscountRule)
|
||||
export default DiscountRuleRepository
|
||||
@@ -1,5 +1,5 @@
|
||||
import { EntityRepository, Repository } from "typeorm"
|
||||
import { Discount } from "../models/discount"
|
||||
import { Discount } from "../models"
|
||||
import { dataSource } from "../loaders/database"
|
||||
|
||||
@EntityRepository(Discount)
|
||||
export class DiscountRepository extends Repository<Discount> {}
|
||||
export const DiscountRepository = dataSource.getRepository(Discount)
|
||||
export default DiscountRepository
|
||||
@@ -1,5 +1,5 @@
|
||||
import { EntityRepository, Repository } from "typeorm"
|
||||
import { DraftOrder } from "../models/draft-order"
|
||||
import { DraftOrder } from "../models"
|
||||
import { dataSource } from "../loaders/database"
|
||||
|
||||
@EntityRepository(DraftOrder)
|
||||
export class DraftOrderRepository extends Repository<DraftOrder> {}
|
||||
export const DraftOrderRepository = dataSource.getRepository(DraftOrder)
|
||||
export default DraftOrderRepository
|
||||
@@ -1,6 +1,6 @@
|
||||
import { EntityRepository, Repository } from "typeorm"
|
||||
import { FulfillmentProvider } from "../models/fulfillment-provider"
|
||||
import { FulfillmentProvider } from "../models"
|
||||
import { dataSource } from "../loaders/database"
|
||||
|
||||
@EntityRepository(FulfillmentProvider)
|
||||
// eslint-disable-next-line max-len
|
||||
export class FulfillmentProviderRepository extends Repository<FulfillmentProvider> {}
|
||||
export const FulfillmentProviderRepository =
|
||||
dataSource.getRepository(FulfillmentProvider)
|
||||
export default FulfillmentProviderRepository
|
||||
@@ -1,5 +1,5 @@
|
||||
import { EntityRepository, Repository } from "typeorm"
|
||||
import { Fulfillment } from "../models/fulfillment"
|
||||
import { Fulfillment } from "../models"
|
||||
import { dataSource } from "../loaders/database"
|
||||
|
||||
@EntityRepository(Fulfillment)
|
||||
export class FulfillmentRepository extends Repository<Fulfillment> {}
|
||||
export const FulfillmentRepository = dataSource.getRepository(Fulfillment)
|
||||
export default FulfillmentRepository
|
||||
@@ -1,6 +1,6 @@
|
||||
import { EntityRepository, Repository } from "typeorm"
|
||||
import { GiftCardTransaction } from "../models/gift-card-transaction"
|
||||
import { GiftCardTransaction } from "../models"
|
||||
import { dataSource } from "../loaders/database"
|
||||
|
||||
@EntityRepository(GiftCardTransaction)
|
||||
// eslint-disable-next-line max-len
|
||||
export class GiftCardTransactionRepository extends Repository<GiftCardTransaction> {}
|
||||
export const GiftCardTransactionRepository =
|
||||
dataSource.getRepository(GiftCardTransaction)
|
||||
export default GiftCardTransactionRepository
|
||||
@@ -1,142 +1,40 @@
|
||||
import { flatten, groupBy, merge } from "lodash"
|
||||
import {
|
||||
Brackets,
|
||||
EntityRepository,
|
||||
FindManyOptions,
|
||||
Repository,
|
||||
} from "typeorm"
|
||||
import { GiftCard } from "../models/gift-card"
|
||||
import { ExtendedFindConfig, QuerySelector, Writable } from "../types/common"
|
||||
import { FindOptionsWhere, ILike, Raw } from "typeorm"
|
||||
import { GiftCard } from "../models"
|
||||
import { ExtendedFindConfig } from "../types/common"
|
||||
import { dataSource } from "../loaders/database"
|
||||
|
||||
@EntityRepository(GiftCard)
|
||||
export class GiftCardRepository extends Repository<GiftCard> {
|
||||
public async findWithRelations(
|
||||
relations: (keyof GiftCard | string)[] = [],
|
||||
idsOrOptionsWithoutRelations:
|
||||
| Omit<FindManyOptions<GiftCard>, "relations">
|
||||
| string[] = {}
|
||||
): Promise<[GiftCard[], number]> {
|
||||
let entities: GiftCard[] = []
|
||||
let count = 0
|
||||
if (Array.isArray(idsOrOptionsWithoutRelations)) {
|
||||
entities = await this.findByIds(idsOrOptionsWithoutRelations)
|
||||
count = idsOrOptionsWithoutRelations.length
|
||||
} else {
|
||||
const [results, resultCount] = await this.findAndCount(
|
||||
idsOrOptionsWithoutRelations
|
||||
)
|
||||
entities = results
|
||||
count = resultCount
|
||||
}
|
||||
const entitiesIds = entities.map(({ id }) => id)
|
||||
|
||||
const groupedRelations = {}
|
||||
for (const rel of relations) {
|
||||
const [topLevel] = rel.split(".")
|
||||
if (groupedRelations[topLevel]) {
|
||||
groupedRelations[topLevel].push(rel)
|
||||
} else {
|
||||
groupedRelations[topLevel] = [rel]
|
||||
}
|
||||
}
|
||||
|
||||
const entitiesIdsWithRelations = await Promise.all(
|
||||
Object.entries(groupedRelations).map(async ([_, rels]) => {
|
||||
return this.findByIds(entitiesIds, {
|
||||
select: ["id"],
|
||||
relations: rels as string[],
|
||||
})
|
||||
})
|
||||
).then(flatten)
|
||||
const entitiesAndRelations = entitiesIdsWithRelations.concat(entities)
|
||||
|
||||
const entitiesAndRelationsById = groupBy(entitiesAndRelations, "id")
|
||||
return [
|
||||
Object.values(entitiesAndRelationsById).map((v) => merge({}, ...v)),
|
||||
count,
|
||||
]
|
||||
}
|
||||
|
||||
protected async queryGiftCards(
|
||||
q: string,
|
||||
where: Partial<Writable<QuerySelector<GiftCard>>>,
|
||||
rels: (keyof GiftCard | string)[],
|
||||
shouldCount = false
|
||||
): Promise<[GiftCard[], number]> {
|
||||
const qb = this.createQueryBuilder("gift_card")
|
||||
.leftJoinAndSelect("gift_card.order", "order")
|
||||
.select(["gift_card.id"])
|
||||
.where(where)
|
||||
.andWhere(
|
||||
new Brackets((qb) => {
|
||||
return qb
|
||||
.where(`gift_card.code ILIKE :q`, { q: `%${q}%` })
|
||||
.orWhere(`display_id::varchar(255) ILIKE :dId`, { dId: `${q}` })
|
||||
})
|
||||
)
|
||||
|
||||
let raw: GiftCard[] = []
|
||||
let count = 0
|
||||
if (shouldCount) {
|
||||
const [results, resultCount] = await qb.getManyAndCount()
|
||||
raw = results
|
||||
count = resultCount
|
||||
} else {
|
||||
raw = await qb.getMany()
|
||||
}
|
||||
|
||||
const [results] = await this.findWithRelations(
|
||||
rels,
|
||||
raw.map((i) => i.id)
|
||||
)
|
||||
|
||||
return [results, count]
|
||||
}
|
||||
|
||||
public async listGiftCardsAndCount(
|
||||
inputQuery: ExtendedFindConfig<GiftCard, QuerySelector<GiftCard>>,
|
||||
rels: (keyof GiftCard | string)[] = [],
|
||||
export const GiftCardRepository = dataSource.getRepository(GiftCard).extend({
|
||||
async listGiftCardsAndCount(
|
||||
query: ExtendedFindConfig<GiftCard>,
|
||||
q?: string
|
||||
): Promise<[GiftCard[], number]> {
|
||||
const query = { ...inputQuery }
|
||||
const query_ = { ...query }
|
||||
query_.where = query_.where as FindOptionsWhere<GiftCard>
|
||||
|
||||
if (q) {
|
||||
const where = query.where
|
||||
delete where.id
|
||||
delete query_.where.id
|
||||
|
||||
return await this.queryGiftCards(q, where, rels, true)
|
||||
}
|
||||
return await this.findWithRelations(rels, query)
|
||||
}
|
||||
query_.relations = query_.relations ?? {}
|
||||
query_.relations.order = query_.relations.order ?? true
|
||||
|
||||
public async listGiftCards(
|
||||
query: ExtendedFindConfig<GiftCard, QuerySelector<GiftCard>>,
|
||||
rels: (keyof GiftCard | string)[] = [],
|
||||
q?: string
|
||||
): Promise<GiftCard[]> {
|
||||
if (q) {
|
||||
const where = query.where
|
||||
delete where.id
|
||||
|
||||
const [result] = await this.queryGiftCards(q, where, rels)
|
||||
return result
|
||||
query_.where = query_.where as FindOptionsWhere<GiftCard>[]
|
||||
query_.where = [
|
||||
{
|
||||
...query_.where,
|
||||
code: ILike(`%${q}%`),
|
||||
},
|
||||
{
|
||||
...query_.where,
|
||||
order: {
|
||||
display_id: Raw((alias) => `CAST(${alias} as varchar) ILike :q`, {
|
||||
q: `%${q}%`,
|
||||
}),
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
const [results] = await this.findWithRelations(rels, query)
|
||||
return results
|
||||
}
|
||||
|
||||
public async findOneWithRelations(
|
||||
relations: Array<keyof GiftCard> = [],
|
||||
optionsWithoutRelations: Omit<FindManyOptions<GiftCard>, "relations"> = {}
|
||||
): Promise<GiftCard> {
|
||||
// Limit 1
|
||||
optionsWithoutRelations.take = 1
|
||||
|
||||
const [result] = await this.findWithRelations(
|
||||
relations,
|
||||
optionsWithoutRelations
|
||||
)
|
||||
return result[0]
|
||||
}
|
||||
}
|
||||
return await this.findAndCount(query_)
|
||||
},
|
||||
})
|
||||
export default GiftCardRepository
|
||||
@@ -1,5 +1,5 @@
|
||||
import { EntityRepository, Repository } from "typeorm"
|
||||
import { IdempotencyKey } from "../models/idempotency-key"
|
||||
import { IdempotencyKey } from "../models"
|
||||
import { dataSource } from "../loaders/database"
|
||||
|
||||
@EntityRepository(IdempotencyKey)
|
||||
export class IdempotencyKeyRepository extends Repository<IdempotencyKey> {}
|
||||
export const IdempotencyKeyRepository = dataSource.getRepository(IdempotencyKey)
|
||||
export default IdempotencyKeyRepository
|
||||
@@ -1,9 +1,9 @@
|
||||
import { EntityRepository, In, Repository } from "typeorm"
|
||||
import { Image } from "../models/image"
|
||||
import { Image } from "../models"
|
||||
import { dataSource } from "../loaders/database"
|
||||
import { In } from "typeorm"
|
||||
|
||||
@EntityRepository(Image)
|
||||
export class ImageRepository extends Repository<Image> {
|
||||
public async upsertImages(imageUrls: string[]) {
|
||||
export const ImageRepository = dataSource.getRepository(Image).extend({
|
||||
async upsertImages(imageUrls: string[]) {
|
||||
const existingImages = await this.find({
|
||||
where: {
|
||||
url: In(imageUrls),
|
||||
@@ -27,5 +27,6 @@ export class ImageRepository extends Repository<Image> {
|
||||
}
|
||||
|
||||
return upsertedImgs
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
export default ImageRepository
|
||||
@@ -1,5 +1,5 @@
|
||||
import { EntityRepository, Repository } from "typeorm"
|
||||
import { Invite } from "../models/invite"
|
||||
import { Invite } from "../models"
|
||||
import { dataSource } from "../loaders/database"
|
||||
|
||||
@EntityRepository(Invite)
|
||||
export class InviteRepository extends Repository<Invite> {}
|
||||
export const InviteRepository = dataSource.getRepository(Invite)
|
||||
export default InviteRepository
|
||||
@@ -1,6 +1,6 @@
|
||||
import { EntityRepository, Repository } from "typeorm"
|
||||
import { LineItemAdjustment } from "../models/line-item-adjustment"
|
||||
import { LineItemAdjustment } from "../models"
|
||||
import { dataSource } from "../loaders/database"
|
||||
|
||||
@EntityRepository(LineItemAdjustment)
|
||||
// eslint-disable-next-line max-len
|
||||
export class LineItemAdjustmentRepository extends Repository<LineItemAdjustment> {}
|
||||
export const LineItemAdjustmentRepository =
|
||||
dataSource.getRepository(LineItemAdjustment)
|
||||
export default LineItemAdjustmentRepository
|
||||
@@ -1,38 +1,37 @@
|
||||
import { EntityRepository, Repository } from "typeorm"
|
||||
import { LineItemTaxLine } from "../models/line-item-tax-line"
|
||||
import { LineItemTaxLine } from "../models"
|
||||
import { QueryDeepPartialEntity } from "typeorm/query-builder/QueryPartialEntity"
|
||||
import { dataSource } from "../loaders/database"
|
||||
|
||||
@EntityRepository(LineItemTaxLine)
|
||||
export class LineItemTaxLineRepository extends Repository<LineItemTaxLine> {
|
||||
async upsertLines(lines: LineItemTaxLine[]): Promise<LineItemTaxLine[]> {
|
||||
const insertResult = await this.createQueryBuilder()
|
||||
.insert()
|
||||
.values(lines as QueryDeepPartialEntity<LineItemTaxLine>[])
|
||||
.orUpdate({
|
||||
conflict_target: ["item_id", "code"],
|
||||
overwrite: ["rate", "name", "updated_at"],
|
||||
})
|
||||
.execute()
|
||||
export const LineItemTaxLineRepository = dataSource
|
||||
.getRepository(LineItemTaxLine)
|
||||
.extend({
|
||||
async upsertLines(lines: LineItemTaxLine[]): Promise<LineItemTaxLine[]> {
|
||||
const insertResult = await this.createQueryBuilder()
|
||||
.insert()
|
||||
.values(lines as QueryDeepPartialEntity<LineItemTaxLine>[])
|
||||
.orUpdate(["rate", "name", "updated_at"], ["item_id", "code"])
|
||||
.execute()
|
||||
|
||||
return insertResult.identifiers as LineItemTaxLine[]
|
||||
}
|
||||
return insertResult.identifiers as LineItemTaxLine[]
|
||||
},
|
||||
|
||||
async deleteForCart(cartId: string): Promise<void> {
|
||||
const qb = this.createQueryBuilder("line")
|
||||
.select(["line.id"])
|
||||
.innerJoin("line_item", "i", "i.id = line.item_id")
|
||||
.innerJoin(
|
||||
"cart",
|
||||
"c",
|
||||
"i.cart_id = :cartId AND c.completed_at is NULL",
|
||||
{ cartId }
|
||||
)
|
||||
async deleteForCart(cartId: string): Promise<void> {
|
||||
const qb = this.createQueryBuilder("line")
|
||||
.select(["line.id"])
|
||||
.innerJoin("line_item", "i", "i.id = line.item_id")
|
||||
.innerJoin(
|
||||
"cart",
|
||||
"c",
|
||||
"i.cart_id = :cartId AND c.completed_at is NULL",
|
||||
{ cartId }
|
||||
)
|
||||
|
||||
const toDelete = await qb.getMany()
|
||||
const toDelete = await qb.getMany()
|
||||
|
||||
await this.createQueryBuilder()
|
||||
.delete()
|
||||
.whereInIds(toDelete.map((d) => d.id))
|
||||
.execute()
|
||||
}
|
||||
}
|
||||
await this.createQueryBuilder()
|
||||
.delete()
|
||||
.whereInIds(toDelete.map((d) => d.id))
|
||||
.execute()
|
||||
},
|
||||
})
|
||||
export default LineItemTaxLineRepository
|
||||
@@ -1,9 +1,8 @@
|
||||
import { EntityRepository, Repository } from "typeorm"
|
||||
import { LineItem } from "../models/line-item"
|
||||
import { ReturnItem } from "../models/return-item"
|
||||
import { dataSource } from "../loaders/database"
|
||||
|
||||
@EntityRepository(LineItem)
|
||||
export class LineItemRepository extends Repository<LineItem> {
|
||||
export const LineItemRepository = dataSource.getRepository(LineItem).extend({
|
||||
/**
|
||||
* Finds line items that are to be returned as part of the Return represented
|
||||
* by `returnId`. The function joins the associated ReturnItem and the
|
||||
@@ -27,5 +26,7 @@ export class LineItemRepository extends Repository<LineItem> {
|
||||
.where(`ri.return_id = :returnId`, { returnId })
|
||||
|
||||
return (await qb.getMany()) as (LineItem & { return_item: ReturnItem })[]
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
export default LineItemRepository
|
||||
@@ -1,20 +1,19 @@
|
||||
import partition from "lodash/partition"
|
||||
import {
|
||||
Brackets,
|
||||
EntityRepository,
|
||||
In,
|
||||
IsNull,
|
||||
Not,
|
||||
ObjectLiteral,
|
||||
Repository,
|
||||
WhereExpressionBuilder,
|
||||
} from "typeorm"
|
||||
import { QueryDeepPartialEntity } from "typeorm/query-builder/QueryPartialEntity"
|
||||
import { MoneyAmount } from "../models/money-amount"
|
||||
import { MoneyAmount } from "../models"
|
||||
import {
|
||||
PriceListPriceCreateInput,
|
||||
PriceListPriceUpdateInput,
|
||||
} from "../types/price-list"
|
||||
import { QueryDeepPartialEntity } from "typeorm/query-builder/QueryPartialEntity"
|
||||
import { dataSource } from "../loaders/database"
|
||||
|
||||
type Price = Partial<
|
||||
Omit<MoneyAmount, "created_at" | "updated_at" | "deleted_at">
|
||||
@@ -22,252 +21,253 @@ type Price = Partial<
|
||||
amount: number
|
||||
}
|
||||
|
||||
@EntityRepository(MoneyAmount)
|
||||
export class MoneyAmountRepository extends Repository<MoneyAmount> {
|
||||
/**
|
||||
* Will be removed in a future release.
|
||||
* Use `deleteVariantPricesNotIn` instead.
|
||||
* @deprecated
|
||||
*/
|
||||
public async findVariantPricesNotIn(
|
||||
variantId: string,
|
||||
prices: Price[]
|
||||
): Promise<MoneyAmount[]> {
|
||||
const pricesNotInPricesPayload = await this.createQueryBuilder()
|
||||
.where({
|
||||
export const MoneyAmountRepository = dataSource
|
||||
.getRepository(MoneyAmount)
|
||||
.extend({
|
||||
async findVariantPricesNotIn(
|
||||
variantId: string,
|
||||
prices: Price[]
|
||||
): Promise<MoneyAmount[]> {
|
||||
const pricesNotInPricesPayload = await this.createQueryBuilder()
|
||||
.where({
|
||||
variant_id: variantId,
|
||||
price_list_id: IsNull(),
|
||||
})
|
||||
.andWhere(
|
||||
new Brackets((qb) => {
|
||||
qb.where({
|
||||
currency_code: Not(In(prices.map((p) => p.currency_code))),
|
||||
}).orWhere({ region_id: Not(In(prices.map((p) => p.region_id))) })
|
||||
})
|
||||
)
|
||||
.getMany()
|
||||
return pricesNotInPricesPayload
|
||||
},
|
||||
|
||||
async upsertVariantCurrencyPrice(
|
||||
variantId: string,
|
||||
price: Price
|
||||
): Promise<MoneyAmount> {
|
||||
let moneyAmount = await this.findOne({
|
||||
where: {
|
||||
currency_code: price.currency_code,
|
||||
variant_id: variantId,
|
||||
region_id: IsNull(),
|
||||
price_list_id: IsNull(),
|
||||
},
|
||||
})
|
||||
|
||||
if (!moneyAmount) {
|
||||
moneyAmount = this.create({
|
||||
...price,
|
||||
currency_code: price.currency_code?.toLowerCase(),
|
||||
variant_id: variantId,
|
||||
})
|
||||
} else {
|
||||
moneyAmount.amount = price.amount
|
||||
}
|
||||
|
||||
return await this.save(moneyAmount)
|
||||
},
|
||||
|
||||
async deleteVariantPricesNotIn(
|
||||
variantId: string,
|
||||
prices: Price[]
|
||||
): Promise<void> {
|
||||
const where = {
|
||||
variant_id: variantId,
|
||||
price_list_id: IsNull(),
|
||||
})
|
||||
.andWhere(
|
||||
new Brackets((qb) => {
|
||||
qb.where({
|
||||
currency_code: Not(In(prices.map((p) => p.currency_code))),
|
||||
}).orWhere({ region_id: Not(In(prices.map((p) => p.region_id))) })
|
||||
}
|
||||
|
||||
const orWhere: ObjectLiteral[] = []
|
||||
|
||||
for (const price of prices) {
|
||||
if (price.currency_code) {
|
||||
orWhere.push(
|
||||
{
|
||||
currency_code: Not(price.currency_code),
|
||||
},
|
||||
{
|
||||
region_id: price.region_id ? Not(price.region_id) : Not(IsNull()),
|
||||
currency_code: price.currency_code,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (price.region_id) {
|
||||
orWhere.push({
|
||||
region_id: Not(price.region_id),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
await this.createQueryBuilder()
|
||||
.delete()
|
||||
.where(where)
|
||||
.andWhere(orWhere)
|
||||
.execute()
|
||||
},
|
||||
|
||||
async addPriceListPrices(
|
||||
priceListId: string,
|
||||
prices: PriceListPriceCreateInput[],
|
||||
overrideExisting = false
|
||||
): Promise<MoneyAmount[]> {
|
||||
const toInsert = prices.map((price) =>
|
||||
this.create({
|
||||
...price,
|
||||
price_list_id: priceListId,
|
||||
})
|
||||
)
|
||||
.getMany()
|
||||
return pricesNotInPricesPayload
|
||||
}
|
||||
|
||||
public async deleteVariantPricesNotIn(
|
||||
variantId: string,
|
||||
prices: Price[]
|
||||
): Promise<void> {
|
||||
const where = {
|
||||
variant_id: variantId,
|
||||
price_list_id: IsNull(),
|
||||
}
|
||||
const insertResult = await this.createQueryBuilder()
|
||||
.insert()
|
||||
.orIgnore(true)
|
||||
.into(MoneyAmount)
|
||||
.values(toInsert as QueryDeepPartialEntity<MoneyAmount>[])
|
||||
.execute()
|
||||
|
||||
const orWhere: ObjectLiteral[] = []
|
||||
|
||||
for (const price of prices) {
|
||||
if (price.currency_code) {
|
||||
orWhere.push(
|
||||
{
|
||||
currency_code: Not(price.currency_code),
|
||||
},
|
||||
{
|
||||
region_id: price.region_id ? Not(price.region_id) : Not(IsNull()),
|
||||
currency_code: price.currency_code,
|
||||
}
|
||||
)
|
||||
if (overrideExisting) {
|
||||
await this.createQueryBuilder()
|
||||
.delete()
|
||||
.from(MoneyAmount)
|
||||
.where({
|
||||
price_list_id: priceListId,
|
||||
id: Not(In(insertResult.identifiers.map((ma) => ma.id))),
|
||||
})
|
||||
.execute()
|
||||
}
|
||||
|
||||
if (price.region_id) {
|
||||
orWhere.push({
|
||||
region_id: Not(price.region_id),
|
||||
})
|
||||
}
|
||||
}
|
||||
return await this.manager
|
||||
.createQueryBuilder(MoneyAmount, "ma")
|
||||
.select()
|
||||
.where(insertResult.identifiers)
|
||||
.getMany()
|
||||
},
|
||||
|
||||
await this.createQueryBuilder()
|
||||
.delete()
|
||||
.where(where)
|
||||
.andWhere(orWhere)
|
||||
.execute()
|
||||
}
|
||||
|
||||
public async upsertVariantCurrencyPrice(
|
||||
variantId: string,
|
||||
price: Price
|
||||
): Promise<MoneyAmount> {
|
||||
let moneyAmount = await this.findOne({
|
||||
where: {
|
||||
currency_code: price.currency_code,
|
||||
variant_id: variantId,
|
||||
region_id: IsNull(),
|
||||
price_list_id: IsNull(),
|
||||
},
|
||||
})
|
||||
|
||||
if (!moneyAmount) {
|
||||
moneyAmount = this.create({
|
||||
...price,
|
||||
currency_code: price.currency_code?.toLowerCase(),
|
||||
variant_id: variantId,
|
||||
})
|
||||
} else {
|
||||
moneyAmount.amount = price.amount
|
||||
}
|
||||
|
||||
return await this.save(moneyAmount)
|
||||
}
|
||||
|
||||
public async addPriceListPrices(
|
||||
priceListId: string,
|
||||
prices: PriceListPriceCreateInput[],
|
||||
overrideExisting = false
|
||||
): Promise<MoneyAmount[]> {
|
||||
const toInsert = prices.map((price) =>
|
||||
this.create({
|
||||
...price,
|
||||
price_list_id: priceListId,
|
||||
})
|
||||
)
|
||||
|
||||
const insertResult = await this.createQueryBuilder()
|
||||
.insert()
|
||||
.orIgnore(true)
|
||||
.into(MoneyAmount)
|
||||
.values(toInsert as QueryDeepPartialEntity<MoneyAmount>[])
|
||||
.execute()
|
||||
|
||||
if (overrideExisting) {
|
||||
async deletePriceListPrices(
|
||||
priceListId: string,
|
||||
moneyAmountIds: string[]
|
||||
): Promise<void> {
|
||||
await this.createQueryBuilder()
|
||||
.delete()
|
||||
.from(MoneyAmount)
|
||||
.where({
|
||||
price_list_id: priceListId,
|
||||
id: Not(In(insertResult.identifiers.map((ma) => ma.id))),
|
||||
})
|
||||
.where({ price_list_id: priceListId, id: In(moneyAmountIds) })
|
||||
.execute()
|
||||
}
|
||||
},
|
||||
|
||||
return await this.manager
|
||||
.createQueryBuilder(MoneyAmount, "ma")
|
||||
.select()
|
||||
.where(insertResult.identifiers)
|
||||
.getMany()
|
||||
}
|
||||
async findManyForVariantInPriceList(
|
||||
variant_id: string,
|
||||
price_list_id: string,
|
||||
requiresPriceList = false
|
||||
): Promise<[MoneyAmount[], number]> {
|
||||
const qb = this.createQueryBuilder("ma")
|
||||
.leftJoinAndSelect("ma.price_list", "price_list")
|
||||
.where("ma.variant_id = :variant_id", { variant_id })
|
||||
|
||||
public async deletePriceListPrices(
|
||||
priceListId: string,
|
||||
moneyAmountIds: string[]
|
||||
): Promise<void> {
|
||||
await this.createQueryBuilder()
|
||||
.delete()
|
||||
.from(MoneyAmount)
|
||||
.where({ price_list_id: priceListId, id: In(moneyAmountIds) })
|
||||
.execute()
|
||||
}
|
||||
|
||||
public async findManyForVariantInPriceList(
|
||||
variant_id: string,
|
||||
price_list_id: string,
|
||||
requiresPriceList = false
|
||||
): Promise<[MoneyAmount[], number]> {
|
||||
const qb = this.createQueryBuilder("ma")
|
||||
.leftJoinAndSelect("ma.price_list", "price_list")
|
||||
.where("ma.variant_id = :variant_id", { variant_id })
|
||||
|
||||
const getAndWhere = (subQb): WhereExpressionBuilder => {
|
||||
const andWhere = subQb.where("ma.price_list_id = :price_list_id", {
|
||||
price_list_id,
|
||||
})
|
||||
if (!requiresPriceList) {
|
||||
andWhere.orWhere("ma.price_list_id IS NULL")
|
||||
}
|
||||
return andWhere
|
||||
}
|
||||
|
||||
qb.andWhere(new Brackets(getAndWhere))
|
||||
|
||||
return await qb.getManyAndCount()
|
||||
}
|
||||
|
||||
public async findManyForVariantInRegion(
|
||||
variant_id: string,
|
||||
region_id?: string,
|
||||
currency_code?: string,
|
||||
customer_id?: string,
|
||||
include_discount_prices?: boolean,
|
||||
include_tax_inclusive_pricing = false
|
||||
): Promise<[MoneyAmount[], number]> {
|
||||
const date = new Date()
|
||||
|
||||
const qb = this.createQueryBuilder("ma")
|
||||
.leftJoinAndSelect("ma.price_list", "price_list")
|
||||
.where({ variant_id: variant_id })
|
||||
.andWhere("(ma.price_list_id is null or price_list.status = 'active')")
|
||||
.andWhere("(price_list.ends_at is null OR price_list.ends_at > :date)", {
|
||||
date: date.toUTCString(),
|
||||
})
|
||||
.andWhere(
|
||||
"(price_list.starts_at is null OR price_list.starts_at < :date)",
|
||||
{
|
||||
date: date.toUTCString(),
|
||||
}
|
||||
)
|
||||
|
||||
if (include_tax_inclusive_pricing) {
|
||||
qb.leftJoin("ma.currency", "currency")
|
||||
.leftJoin("ma.region", "region")
|
||||
.addSelect(["currency.includes_tax", "region.includes_tax"])
|
||||
}
|
||||
if (region_id || currency_code) {
|
||||
qb.andWhere(
|
||||
new Brackets((qb) => {
|
||||
if (region_id && !currency_code) {
|
||||
qb.where({ region_id: region_id })
|
||||
}
|
||||
if (!region_id && currency_code) {
|
||||
qb.where({ currency_code: currency_code })
|
||||
}
|
||||
if (currency_code && region_id) {
|
||||
qb.where({ region_id: region_id }).orWhere({
|
||||
currency_code: currency_code,
|
||||
})
|
||||
}
|
||||
const getAndWhere = (subQb): WhereExpressionBuilder => {
|
||||
const andWhere = subQb.where("ma.price_list_id = :price_list_id", {
|
||||
price_list_id,
|
||||
})
|
||||
)
|
||||
} else if (!customer_id && !include_discount_prices) {
|
||||
qb.andWhere("price_list.id IS null")
|
||||
}
|
||||
if (!requiresPriceList) {
|
||||
andWhere.orWhere("ma.price_list_id IS NULL")
|
||||
}
|
||||
return andWhere
|
||||
}
|
||||
|
||||
if (customer_id) {
|
||||
qb.leftJoin("price_list.customer_groups", "cgroup")
|
||||
.leftJoin(
|
||||
"customer_group_customers",
|
||||
"cgc",
|
||||
"cgc.customer_group_id = cgroup.id"
|
||||
qb.andWhere(new Brackets(getAndWhere))
|
||||
|
||||
return await qb.getManyAndCount()
|
||||
},
|
||||
|
||||
async findManyForVariantInRegion(
|
||||
variant_id: string,
|
||||
region_id?: string,
|
||||
currency_code?: string,
|
||||
customer_id?: string,
|
||||
include_discount_prices?: boolean,
|
||||
include_tax_inclusive_pricing = false
|
||||
): Promise<[MoneyAmount[], number]> {
|
||||
const date = new Date()
|
||||
|
||||
const qb = this.createQueryBuilder("ma")
|
||||
.leftJoinAndSelect("ma.price_list", "price_list")
|
||||
.where({ variant_id: variant_id })
|
||||
.andWhere("(ma.price_list_id is null or price_list.status = 'active')")
|
||||
.andWhere(
|
||||
"(price_list.ends_at is null OR price_list.ends_at > :date)",
|
||||
{
|
||||
date: date.toUTCString(),
|
||||
}
|
||||
)
|
||||
.andWhere(
|
||||
"(cgc.customer_group_id is null OR cgc.customer_id = :customer_id)",
|
||||
"(price_list.starts_at is null OR price_list.starts_at < :date)",
|
||||
{
|
||||
customer_id,
|
||||
date: date.toUTCString(),
|
||||
}
|
||||
)
|
||||
} else {
|
||||
qb.leftJoin("price_list.customer_groups", "cgroup").andWhere(
|
||||
"cgroup.id is null"
|
||||
|
||||
if (include_tax_inclusive_pricing) {
|
||||
qb.leftJoin("ma.currency", "currency")
|
||||
.leftJoin("ma.region", "region")
|
||||
.addSelect(["currency.includes_tax", "region.includes_tax"])
|
||||
}
|
||||
if (region_id || currency_code) {
|
||||
qb.andWhere(
|
||||
new Brackets((qb) => {
|
||||
if (region_id && !currency_code) {
|
||||
qb.where({ region_id: region_id })
|
||||
}
|
||||
if (!region_id && currency_code) {
|
||||
qb.where({ currency_code: currency_code })
|
||||
}
|
||||
if (currency_code && region_id) {
|
||||
qb.where({ region_id: region_id }).orWhere({
|
||||
currency_code: currency_code,
|
||||
})
|
||||
}
|
||||
})
|
||||
)
|
||||
} else if (!customer_id && !include_discount_prices) {
|
||||
qb.andWhere("price_list.id IS null")
|
||||
}
|
||||
|
||||
if (customer_id) {
|
||||
qb.leftJoin("price_list.customer_groups", "cgroup")
|
||||
.leftJoin(
|
||||
"customer_group_customers",
|
||||
"cgc",
|
||||
"cgc.customer_group_id = cgroup.id"
|
||||
)
|
||||
.andWhere(
|
||||
"(cgc.customer_group_id is null OR cgc.customer_id = :customer_id)",
|
||||
{
|
||||
customer_id,
|
||||
}
|
||||
)
|
||||
} else {
|
||||
qb.leftJoin("price_list.customer_groups", "cgroup").andWhere(
|
||||
"cgroup.id is null"
|
||||
)
|
||||
}
|
||||
return await qb.getManyAndCount()
|
||||
},
|
||||
|
||||
async updatePriceListPrices(
|
||||
priceListId: string,
|
||||
updates: PriceListPriceUpdateInput[]
|
||||
): Promise<MoneyAmount[]> {
|
||||
const [existingPrices, newPrices] = partition(
|
||||
updates,
|
||||
(update) => update.id
|
||||
)
|
||||
}
|
||||
return await qb.getManyAndCount()
|
||||
}
|
||||
|
||||
public async updatePriceListPrices(
|
||||
priceListId: string,
|
||||
updates: PriceListPriceUpdateInput[]
|
||||
): Promise<MoneyAmount[]> {
|
||||
const [existingPrices, newPrices] = partition(
|
||||
updates,
|
||||
(update) => update.id
|
||||
)
|
||||
const newPriceEntities = newPrices.map((price) =>
|
||||
this.create({ ...price, price_list_id: priceListId })
|
||||
)
|
||||
|
||||
const newPriceEntities = newPrices.map((price) =>
|
||||
this.create({ ...price, price_list_id: priceListId })
|
||||
)
|
||||
return await this.save([...existingPrices, ...newPriceEntities])
|
||||
},
|
||||
})
|
||||
|
||||
return await this.save([...existingPrices, ...newPriceEntities])
|
||||
}
|
||||
}
|
||||
export default MoneyAmountRepository
|
||||
@@ -1,5 +1,5 @@
|
||||
import { EntityRepository, Repository } from "typeorm"
|
||||
import { Note } from "../models/note"
|
||||
import { Note } from "../models"
|
||||
import { dataSource } from "../loaders/database"
|
||||
|
||||
@EntityRepository(Note)
|
||||
export class NoteRepository extends Repository<Note> {}
|
||||
export const NoteRepository = dataSource.getRepository(Note)
|
||||
export default NoteRepository
|
||||
@@ -1,6 +1,6 @@
|
||||
import { EntityRepository, Repository } from "typeorm"
|
||||
import { NotificationProvider } from "../models/notification-provider"
|
||||
import { dataSource } from "../loaders/database"
|
||||
|
||||
@EntityRepository(NotificationProvider)
|
||||
// eslint-disable-next-line max-len
|
||||
export class NotificationProviderRepository extends Repository<NotificationProvider> {}
|
||||
export const NotificationProviderRepository =
|
||||
dataSource.getRepository(NotificationProvider)
|
||||
export default NotificationProviderRepository
|
||||
@@ -1,5 +1,5 @@
|
||||
import { EntityRepository, Repository } from "typeorm"
|
||||
import { Notification } from "../models/notification"
|
||||
import { Notification } from "../models"
|
||||
import { dataSource } from "../loaders/database"
|
||||
|
||||
@EntityRepository(Notification)
|
||||
export class NotificationRepository extends Repository<Notification> {}
|
||||
export const NotificationRepository = dataSource.getRepository(Notification)
|
||||
export default NotificationRepository
|
||||
@@ -1,5 +1,5 @@
|
||||
import { EntityRepository, Repository } from "typeorm"
|
||||
import { Oauth } from "../models/oauth"
|
||||
import { Oauth } from "../models"
|
||||
import { dataSource } from "../loaders/database"
|
||||
|
||||
@EntityRepository(Oauth)
|
||||
export class OauthRepository extends Repository<Oauth> {}
|
||||
export const OauthRepository = dataSource.getRepository(Oauth)
|
||||
export default OauthRepository
|
||||
@@ -1,5 +1,5 @@
|
||||
import { EntityRepository, Repository } from "typeorm"
|
||||
import { OrderEdit } from "../models"
|
||||
import { OrderEdit } from "../models/order-edit"
|
||||
import { dataSource } from "../loaders/database"
|
||||
|
||||
@EntityRepository(OrderEdit)
|
||||
export class OrderEditRepository extends Repository<OrderEdit> {}
|
||||
export const OrderEditRepository = dataSource.getRepository(OrderEdit)
|
||||
export default OrderEditRepository
|
||||
@@ -1,6 +1,6 @@
|
||||
import { EntityRepository, Repository } from "typeorm"
|
||||
import { OrderItemChange } from "../models"
|
||||
import { dataSource } from "../loaders/database"
|
||||
|
||||
import { OrderItemChange } from "../models/order-item-change"
|
||||
|
||||
@EntityRepository(OrderItemChange)
|
||||
export class OrderItemChangeRepository extends Repository<OrderItemChange> {}
|
||||
export const OrderItemChangeRepository =
|
||||
dataSource.getRepository(OrderItemChange)
|
||||
export default OrderItemChangeRepository
|
||||
@@ -1,21 +1,22 @@
|
||||
import { flatten, groupBy, map, merge } from "lodash"
|
||||
import { EntityRepository, FindManyOptions, Repository } from "typeorm"
|
||||
import { FindManyOptions, FindOptionsRelations, In } from "typeorm"
|
||||
import { Order } from "../models"
|
||||
import { buildLegacyFieldsListFrom } from "../utils"
|
||||
import { dataSource } from "../loaders/database"
|
||||
|
||||
const ITEMS_REL_NAME = "items"
|
||||
const REGION_REL_NAME = "region"
|
||||
|
||||
@EntityRepository(Order)
|
||||
export class OrderRepository extends Repository<Order> {
|
||||
public async findWithRelations(
|
||||
relations: string[] = [],
|
||||
export const OrderRepository = dataSource.getRepository(Order).extend({
|
||||
async findWithRelations(
|
||||
relations: FindOptionsRelations<Order> = {},
|
||||
optionsWithoutRelations: Omit<FindManyOptions<Order>, "relations"> = {}
|
||||
): Promise<Order[]> {
|
||||
const entities = await this.find(optionsWithoutRelations)
|
||||
const entitiesIds = entities.map(({ id }) => id)
|
||||
|
||||
const groupedRelations: { [topLevel: string]: string[] } = {}
|
||||
for (const rel of relations) {
|
||||
for (const rel of buildLegacyFieldsListFrom(relations)) {
|
||||
const [topLevel] = rel.split(".")
|
||||
if (groupedRelations[topLevel]) {
|
||||
groupedRelations[topLevel].push(rel)
|
||||
@@ -27,7 +28,8 @@ export class OrderRepository extends Repository<Order> {
|
||||
const entitiesIdsWithRelations = await Promise.all(
|
||||
Object.entries(groupedRelations).map(async ([topLevel, rels]) => {
|
||||
// If top level is region or items then get deleted region as well
|
||||
return this.findByIds(entitiesIds, {
|
||||
return this.find({
|
||||
where: { id: In(entitiesIds) },
|
||||
select: ["id"],
|
||||
relations: rels,
|
||||
withDeleted:
|
||||
@@ -41,10 +43,10 @@ export class OrderRepository extends Repository<Order> {
|
||||
const entitiesAndRelationsById = groupBy(entitiesAndRelations, "id")
|
||||
|
||||
return map(entities, (e) => merge({}, ...entitiesAndRelationsById[e.id]))
|
||||
}
|
||||
},
|
||||
|
||||
public async findOneWithRelations(
|
||||
relations: string[] = [],
|
||||
async findOneWithRelations(
|
||||
relations: FindOptionsRelations<Order> = {},
|
||||
optionsWithoutRelations: Omit<FindManyOptions<Order>, "relations"> = {}
|
||||
): Promise<Order> {
|
||||
// Limit 1
|
||||
@@ -55,5 +57,7 @@ export class OrderRepository extends Repository<Order> {
|
||||
optionsWithoutRelations
|
||||
)
|
||||
return result[0]
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
export default OrderRepository
|
||||
@@ -1,63 +1,65 @@
|
||||
import { MedusaError } from "medusa-core-utils"
|
||||
import { PaymentCollection } from "./../models/payment-collection"
|
||||
import { EntityRepository, Repository } from "typeorm"
|
||||
import { FindConfig } from "../types/common"
|
||||
import { dataSource } from "../loaders/database"
|
||||
|
||||
@EntityRepository(PaymentCollection)
|
||||
// eslint-disable-next-line max-len
|
||||
export class PaymentCollectionRepository extends Repository<PaymentCollection> {
|
||||
async getPaymentCollectionIdBySessionId(
|
||||
sessionId: string,
|
||||
config: FindConfig<PaymentCollection> = {}
|
||||
): Promise<PaymentCollection> {
|
||||
const paymentCollection = await this.find({
|
||||
join: {
|
||||
alias: "payment_col",
|
||||
innerJoin: { payment_sessions: "payment_col.payment_sessions" },
|
||||
},
|
||||
where: (qb) => {
|
||||
qb.where(
|
||||
"payment_col_payment_sessions.payment_session_id = :sessionId",
|
||||
{ sessionId }
|
||||
export const PaymentCollectionRepository = dataSource
|
||||
.getRepository(PaymentCollection)
|
||||
.extend({
|
||||
async getPaymentCollectionIdBySessionId(
|
||||
sessionId: string,
|
||||
config: FindConfig<PaymentCollection> = {}
|
||||
): Promise<PaymentCollection> {
|
||||
const paymentCollection = await this.find({
|
||||
join: {
|
||||
alias: "payment_col",
|
||||
innerJoin: { payment_sessions: "payment_col.payment_sessions" },
|
||||
},
|
||||
where: {
|
||||
payment_sessions: {
|
||||
id: sessionId,
|
||||
},
|
||||
},
|
||||
relations: config.relations,
|
||||
select: config.select,
|
||||
})
|
||||
|
||||
if (!paymentCollection.length) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`Payment collection related to Payment Session id ${sessionId} was not found`
|
||||
)
|
||||
},
|
||||
relations: config.relations,
|
||||
select: config.select,
|
||||
})
|
||||
}
|
||||
|
||||
if (!paymentCollection.length) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`Payment collection related to Payment Session id ${sessionId} was not found`
|
||||
)
|
||||
}
|
||||
return paymentCollection[0]
|
||||
},
|
||||
|
||||
return paymentCollection[0]
|
||||
}
|
||||
async getPaymentCollectionIdByPaymentId(
|
||||
paymentId: string,
|
||||
config: FindConfig<PaymentCollection> = {}
|
||||
): Promise<PaymentCollection> {
|
||||
const paymentCollection = await this.find({
|
||||
join: {
|
||||
alias: "payment_col",
|
||||
innerJoin: { payments: "payment_col.payments" },
|
||||
},
|
||||
where: {
|
||||
payments: {
|
||||
id: paymentId,
|
||||
},
|
||||
},
|
||||
relations: config.relations,
|
||||
select: config.select,
|
||||
})
|
||||
|
||||
async getPaymentCollectionIdByPaymentId(
|
||||
paymentId: string,
|
||||
config: FindConfig<PaymentCollection> = {}
|
||||
): Promise<PaymentCollection> {
|
||||
const paymentCollection = await this.find({
|
||||
join: {
|
||||
alias: "payment_col",
|
||||
innerJoin: { payments: "payment_col.payments" },
|
||||
},
|
||||
where: (qb) => {
|
||||
qb.where("payment_col_payments.payment_id = :paymentId", { paymentId })
|
||||
},
|
||||
relations: config.relations,
|
||||
select: config.select,
|
||||
})
|
||||
if (!paymentCollection.length) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`Payment collection related to Payment id ${paymentId} was not found`
|
||||
)
|
||||
}
|
||||
|
||||
if (!paymentCollection.length) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`Payment collection related to Payment id ${paymentId} was not found`
|
||||
)
|
||||
}
|
||||
|
||||
return paymentCollection[0]
|
||||
}
|
||||
}
|
||||
return paymentCollection[0]
|
||||
},
|
||||
})
|
||||
export default PaymentCollectionRepository
|
||||
@@ -1,5 +1,6 @@
|
||||
import { EntityRepository, Repository } from "typeorm"
|
||||
import { PaymentProvider } from "../models/payment-provider"
|
||||
import { PaymentProvider } from "../models"
|
||||
import { dataSource } from "../loaders/database"
|
||||
|
||||
@EntityRepository(PaymentProvider)
|
||||
export class PaymentProviderRepository extends Repository<PaymentProvider> {}
|
||||
export const PaymentProviderRepository =
|
||||
dataSource.getRepository(PaymentProvider)
|
||||
export default PaymentProviderRepository
|
||||
@@ -1,5 +1,5 @@
|
||||
import { EntityRepository, Repository } from "typeorm"
|
||||
import { PaymentSession } from "../models/payment-session"
|
||||
import { PaymentSession } from "../models"
|
||||
import { dataSource } from "../loaders/database"
|
||||
|
||||
@EntityRepository(PaymentSession)
|
||||
export class PaymentSessionRepository extends Repository<PaymentSession> {}
|
||||
export const PaymentSessionRepository = dataSource.getRepository(PaymentSession)
|
||||
export default PaymentSessionRepository
|
||||
@@ -1,5 +1,5 @@
|
||||
import { EntityRepository, Repository } from "typeorm"
|
||||
import { Payment } from "../models/payment"
|
||||
import { Payment } from "../models"
|
||||
import { dataSource } from "../loaders/database"
|
||||
|
||||
@EntityRepository(Payment)
|
||||
export class PaymentRepository extends Repository<Payment> {}
|
||||
export const PaymentRepository = dataSource.getRepository(Payment)
|
||||
export default PaymentRepository
|
||||
@@ -1,127 +1,40 @@
|
||||
import { groupBy, map } from "lodash"
|
||||
import {
|
||||
Brackets,
|
||||
EntityRepository,
|
||||
FindManyOptions,
|
||||
FindOperator,
|
||||
Repository,
|
||||
} from "typeorm"
|
||||
import { PriceList } from "../models/price-list"
|
||||
import { FindOptionsWhere, ILike } from "typeorm"
|
||||
import { PriceList } from "../models"
|
||||
import { CustomFindOptions, ExtendedFindConfig } from "../types/common"
|
||||
import { FilterablePriceListProps } from "../types/price-list"
|
||||
import { dataSource } from "../loaders/database"
|
||||
|
||||
export type PriceListFindOptions = CustomFindOptions<
|
||||
PriceList,
|
||||
"status" | "type"
|
||||
>
|
||||
|
||||
@EntityRepository(PriceList)
|
||||
export class PriceListRepository extends Repository<PriceList> {
|
||||
public async getFreeTextSearchResultsAndCount(
|
||||
q: string,
|
||||
options: PriceListFindOptions = { where: {} },
|
||||
groups: FindOperator<string[]>,
|
||||
relations: string[] = []
|
||||
export const PriceListRepository = dataSource.getRepository(PriceList).extend({
|
||||
async listAndCount(
|
||||
query: ExtendedFindConfig<PriceList>,
|
||||
q?: string
|
||||
): Promise<[PriceList[], number]> {
|
||||
options.where = options.where ?? {}
|
||||
const query_ = { ...query }
|
||||
|
||||
const qb = this.createQueryBuilder("price_list")
|
||||
.leftJoinAndSelect("price_list.customer_groups", "customer_group")
|
||||
.select(["price_list.id"])
|
||||
.where(options.where)
|
||||
.andWhere(
|
||||
new Brackets((qb) => {
|
||||
qb.where(`price_list.description ILIKE :q`, { q: `%${q}%` })
|
||||
.orWhere(`price_list.name ILIKE :q`, { q: `%${q}%` })
|
||||
.orWhere(`customer_group.name ILIKE :q`, { q: `%${q}%` })
|
||||
})
|
||||
)
|
||||
.skip(options.skip)
|
||||
.take(options.take)
|
||||
if (q) {
|
||||
query_.where = query_.where as FindOptionsWhere<PriceList>
|
||||
delete query_.where.description
|
||||
delete query_.where.name
|
||||
|
||||
if (groups) {
|
||||
qb.andWhere("group.id IN (:...ids)", { ids: groups.value })
|
||||
}
|
||||
|
||||
const [results, count] = await qb.getManyAndCount()
|
||||
|
||||
const price_lists = await this.findWithRelations(
|
||||
relations,
|
||||
results.map((r) => r.id)
|
||||
)
|
||||
|
||||
return [price_lists, count]
|
||||
}
|
||||
|
||||
public async findWithRelations(
|
||||
relations: string[] = [],
|
||||
idsOrOptionsWithoutRelations:
|
||||
| Omit<FindManyOptions<PriceList>, "relations">
|
||||
| string[] = {}
|
||||
): Promise<PriceList[]> {
|
||||
let entities
|
||||
if (Array.isArray(idsOrOptionsWithoutRelations)) {
|
||||
entities = await this.findByIds(idsOrOptionsWithoutRelations)
|
||||
} else {
|
||||
entities = await this.find(idsOrOptionsWithoutRelations)
|
||||
}
|
||||
const groupedRelations: Record<string, string[]> = {}
|
||||
for (const relation of relations) {
|
||||
const [topLevel] = relation.split(".")
|
||||
if (groupedRelations[topLevel]) {
|
||||
groupedRelations[topLevel].push(relation)
|
||||
} else {
|
||||
groupedRelations[topLevel] = [relation]
|
||||
query_.where.description = ILike(`%${q}%`)
|
||||
query_.where.name = ILike(`%${q}%`)
|
||||
query_.where.customer_groups = {
|
||||
name: ILike(`%${q}%`),
|
||||
}
|
||||
}
|
||||
const entitiesIds = entities.map(({ id }) => id)
|
||||
const entitiesIdsWithRelations = await Promise.all(
|
||||
Object.values(groupedRelations).map(async (relations: string[]) => {
|
||||
return this.findByIds(entitiesIds, {
|
||||
select: ["id"],
|
||||
relations: relations as string[],
|
||||
})
|
||||
})
|
||||
).then((entitiesIdsWithRelations) => entitiesIdsWithRelations.flat())
|
||||
const entitiesAndRelations = entitiesIdsWithRelations.concat(entities)
|
||||
|
||||
const entitiesAndRelationsById = groupBy(entitiesAndRelations, "id")
|
||||
return map(entitiesAndRelationsById, (entityAndRelations) =>
|
||||
this.merge(this.create(), ...entityAndRelations)
|
||||
)
|
||||
}
|
||||
return await this.findAndCount({
|
||||
...query_,
|
||||
relations: {
|
||||
customer_groups: true,
|
||||
},
|
||||
relationLoadStrategy: "query",
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
public async findOneWithRelations(
|
||||
relations: (keyof PriceList)[] = [],
|
||||
options: Omit<FindManyOptions<PriceList>, "relations"> = {}
|
||||
): Promise<PriceList | undefined> {
|
||||
options.take = 1
|
||||
|
||||
return (await this.findWithRelations(relations, options))?.pop()
|
||||
}
|
||||
|
||||
async listAndCount(
|
||||
query: ExtendedFindConfig<FilterablePriceListProps>,
|
||||
groups: FindOperator<string[]>
|
||||
): Promise<[PriceList[], number]> {
|
||||
const qb = this.createQueryBuilder("price_list")
|
||||
.where(query.where)
|
||||
.skip(query.skip)
|
||||
.take(query.take)
|
||||
|
||||
if (groups) {
|
||||
qb.leftJoinAndSelect("price_list.customer_groups", "group").andWhere(
|
||||
"group.id IN (:...ids)",
|
||||
{ ids: groups.value }
|
||||
)
|
||||
}
|
||||
|
||||
if (query.relations?.length) {
|
||||
query.relations.forEach((rel) => {
|
||||
qb.leftJoinAndSelect(`price_list.${rel}`, rel)
|
||||
})
|
||||
}
|
||||
|
||||
return await qb.getManyAndCount()
|
||||
}
|
||||
}
|
||||
export default PriceListRepository
|
||||
@@ -1,120 +1,127 @@
|
||||
import {
|
||||
EntityRepository,
|
||||
TreeRepository,
|
||||
Brackets,
|
||||
FindOptionsWhere,
|
||||
ILike,
|
||||
getConnection,
|
||||
DeleteResult,
|
||||
In,
|
||||
} from "typeorm"
|
||||
import { ProductCategory } from "../models/product-category"
|
||||
import { ExtendedFindConfig, Selector, QuerySelector } from "../types/common"
|
||||
import { ExtendedFindConfig, QuerySelector } from "../types/common"
|
||||
import { dataSource } from "../loaders/database"
|
||||
import { buildLegacyFieldsListFrom } from "../utils"
|
||||
|
||||
@EntityRepository(ProductCategory)
|
||||
export class ProductCategoryRepository extends TreeRepository<ProductCategory> {
|
||||
public async getFreeTextSearchResultsAndCount(
|
||||
options: ExtendedFindConfig<ProductCategory, Selector<ProductCategory>> = {
|
||||
where: {},
|
||||
},
|
||||
q: string | undefined,
|
||||
treeScope: QuerySelector<ProductCategory> = {}
|
||||
): Promise<[ProductCategory[], number]> {
|
||||
const entityName = "product_category"
|
||||
const options_ = { ...options }
|
||||
const relations = options_.relations || []
|
||||
export const ProductCategoryRepository = dataSource
|
||||
.getTreeRepository(ProductCategory)
|
||||
.extend({
|
||||
async getFreeTextSearchResultsAndCount(
|
||||
options: ExtendedFindConfig<ProductCategory> = {
|
||||
where: {},
|
||||
},
|
||||
q: string | undefined,
|
||||
treeScope: QuerySelector<ProductCategory> = {}
|
||||
): Promise<[ProductCategory[], number]> {
|
||||
const entityName = "product_category"
|
||||
const options_ = { ...options }
|
||||
options_.where = options_.where as FindOptionsWhere<ProductCategory>
|
||||
|
||||
const selectStatements = (relationName: string): string[] => {
|
||||
const modelColumns = this.manager.connection
|
||||
.getMetadata(ProductCategory)
|
||||
.ownColumns.map((column) => column.propertyName)
|
||||
const legacySelect = buildLegacyFieldsListFrom(options_.select)
|
||||
const legacyRelations = buildLegacyFieldsListFrom(options_.relations)
|
||||
|
||||
return (options_.select || modelColumns).map((column) => {
|
||||
return `${relationName}.${column}`
|
||||
})
|
||||
}
|
||||
|
||||
const queryBuilder = this.createQueryBuilder(entityName)
|
||||
.select(selectStatements(entityName))
|
||||
.skip(options_.skip)
|
||||
.take(options_.take)
|
||||
|
||||
if (q) {
|
||||
delete options_.where?.name
|
||||
delete options_.where?.handle
|
||||
|
||||
queryBuilder.where(
|
||||
new Brackets((bracket) => {
|
||||
bracket
|
||||
.where({ name: ILike(`%${q}%`) })
|
||||
.orWhere({ handle: ILike(`%${q}%`) })
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
queryBuilder.andWhere(options_.where)
|
||||
|
||||
const includedTreeRelations: string[] = relations.filter((rel) =>
|
||||
ProductCategory.treeRelations.includes(rel)
|
||||
)
|
||||
|
||||
includedTreeRelations.forEach((treeRelation) => {
|
||||
const treeWhere = Object.entries(treeScope)
|
||||
.map((entry) => `${treeRelation}.${entry[0]} = :${entry[0]}`)
|
||||
.join(" AND ")
|
||||
|
||||
queryBuilder
|
||||
.leftJoin(
|
||||
`${entityName}.${treeRelation}`,
|
||||
treeRelation,
|
||||
treeWhere,
|
||||
treeScope
|
||||
const selectStatements = (relationName: string): string[] => {
|
||||
const modelColumns = this.metadata.ownColumns.map(
|
||||
(column) => column.propertyName
|
||||
)
|
||||
.addSelect(selectStatements(treeRelation))
|
||||
})
|
||||
const selectColumns = legacySelect.length ? legacySelect : modelColumns
|
||||
|
||||
const nonTreeRelations: string[] = relations.filter(
|
||||
(rel) => !ProductCategory.treeRelations.includes(rel)
|
||||
)
|
||||
return selectColumns.map((column) => {
|
||||
return `${relationName}.${column}`
|
||||
})
|
||||
}
|
||||
|
||||
nonTreeRelations.forEach((relation) => {
|
||||
queryBuilder.leftJoinAndSelect(`${entityName}.${relation}`, relation)
|
||||
})
|
||||
const queryBuilder = this.createQueryBuilder(entityName)
|
||||
.select(selectStatements(entityName))
|
||||
.skip(options_.skip)
|
||||
.take(options_.take)
|
||||
|
||||
if (options_.withDeleted) {
|
||||
queryBuilder.withDeleted()
|
||||
}
|
||||
if (q) {
|
||||
delete options_.where?.name
|
||||
delete options_.where?.handle
|
||||
|
||||
return await queryBuilder.getManyAndCount()
|
||||
}
|
||||
queryBuilder.where(
|
||||
new Brackets((bracket) => {
|
||||
bracket
|
||||
.where({ name: ILike(`%${q}%`) })
|
||||
.orWhere({ handle: ILike(`%${q}%`) })
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
async addProducts(
|
||||
productCategoryId: string,
|
||||
productIds: string[]
|
||||
): Promise<void> {
|
||||
await this.createQueryBuilder()
|
||||
.insert()
|
||||
.into(ProductCategory.productCategoryProductJoinTable)
|
||||
.values(
|
||||
productIds.map((id) => ({
|
||||
product_category_id: productCategoryId,
|
||||
product_id: id,
|
||||
}))
|
||||
queryBuilder.andWhere(options_.where)
|
||||
|
||||
const includedTreeRelations: string[] = legacyRelations.filter((rel) =>
|
||||
ProductCategory.treeRelations.includes(rel)
|
||||
)
|
||||
.orIgnore()
|
||||
.execute()
|
||||
}
|
||||
|
||||
async removeProducts(
|
||||
productCategoryId: string,
|
||||
productIds: string[]
|
||||
): Promise<DeleteResult> {
|
||||
return await this.createQueryBuilder()
|
||||
.delete()
|
||||
.from(ProductCategory.productCategoryProductJoinTable)
|
||||
.where({
|
||||
product_category_id: productCategoryId,
|
||||
product_id: In(productIds),
|
||||
includedTreeRelations.forEach((treeRelation) => {
|
||||
const treeWhere = Object.entries(treeScope)
|
||||
.map((entry) => `${treeRelation}.${entry[0]} = :${entry[0]}`)
|
||||
.join(" AND ")
|
||||
|
||||
queryBuilder
|
||||
.leftJoin(
|
||||
`${entityName}.${treeRelation}`,
|
||||
treeRelation,
|
||||
treeWhere,
|
||||
treeScope
|
||||
)
|
||||
.addSelect(selectStatements(treeRelation))
|
||||
})
|
||||
.execute()
|
||||
}
|
||||
}
|
||||
|
||||
const nonTreeRelations: string[] = legacyRelations.filter(
|
||||
(rel) => !ProductCategory.treeRelations.includes(rel)
|
||||
)
|
||||
|
||||
nonTreeRelations.forEach((relation) => {
|
||||
queryBuilder.leftJoinAndSelect(`${entityName}.${relation}`, relation)
|
||||
})
|
||||
|
||||
if (options_.withDeleted) {
|
||||
queryBuilder.withDeleted()
|
||||
}
|
||||
|
||||
return await queryBuilder.getManyAndCount()
|
||||
},
|
||||
|
||||
async addProducts(
|
||||
productCategoryId: string,
|
||||
productIds: string[]
|
||||
): Promise<void> {
|
||||
await this.createQueryBuilder()
|
||||
.insert()
|
||||
.into(ProductCategory.productCategoryProductJoinTable)
|
||||
.values(
|
||||
productIds.map((id) => ({
|
||||
product_category_id: productCategoryId,
|
||||
product_id: id,
|
||||
}))
|
||||
)
|
||||
.orIgnore()
|
||||
.execute()
|
||||
},
|
||||
|
||||
async removeProducts(
|
||||
productCategoryId: string,
|
||||
productIds: string[]
|
||||
): Promise<DeleteResult> {
|
||||
return await this.createQueryBuilder()
|
||||
.delete()
|
||||
.from(ProductCategory.productCategoryProductJoinTable)
|
||||
.where({
|
||||
product_category_id: productCategoryId,
|
||||
product_id: In(productIds),
|
||||
})
|
||||
.execute()
|
||||
},
|
||||
})
|
||||
|
||||
export default ProductCategoryRepository
|
||||
@@ -1,36 +1,24 @@
|
||||
import { EntityRepository, Repository } from "typeorm"
|
||||
import { ProductCollection } from "../models"
|
||||
import { ExtendedFindConfig, Selector } from "../types/common"
|
||||
import { dataSource } from "../loaders/database"
|
||||
import { ExtendedFindConfig } from "../types/common"
|
||||
|
||||
@EntityRepository(ProductCollection)
|
||||
// eslint-disable-next-line max-len
|
||||
export class ProductCollectionRepository extends Repository<ProductCollection> {
|
||||
async findAndCountByDiscountConditionId(
|
||||
conditionId: string,
|
||||
query: ExtendedFindConfig<ProductCollection, Selector<ProductCollection>>
|
||||
): Promise<[ProductCollection[], number]> {
|
||||
const qb = this.createQueryBuilder("pc")
|
||||
|
||||
if (query?.select) {
|
||||
qb.select(query.select.map((select) => `pc.${select}`))
|
||||
}
|
||||
|
||||
if (query.skip) {
|
||||
qb.skip(query.skip)
|
||||
}
|
||||
|
||||
if (query.take) {
|
||||
qb.take(query.take)
|
||||
}
|
||||
|
||||
return await qb
|
||||
.where(query.where)
|
||||
.innerJoin(
|
||||
"discount_condition_product_collection",
|
||||
"dc_pc",
|
||||
`dc_pc.product_collection_id = pc.id AND dc_pc.condition_id = :dcId`,
|
||||
{ dcId: conditionId }
|
||||
)
|
||||
.getManyAndCount()
|
||||
}
|
||||
}
|
||||
export const ProductCollectionRepository = dataSource
|
||||
.getRepository(ProductCollection)
|
||||
.extend({
|
||||
async findAndCountByDiscountConditionId(
|
||||
conditionId: string,
|
||||
query: ExtendedFindConfig<ProductCollection>
|
||||
): Promise<[ProductCollection[], number]> {
|
||||
return await this.createQueryBuilder("pc")
|
||||
.setFindOptions(query)
|
||||
.innerJoin(
|
||||
"discount_condition_product_collection",
|
||||
"dc_pc",
|
||||
`dc_pc.product_collection_id = pc.id AND dc_pc.condition_id = :dcId`,
|
||||
{ dcId: conditionId }
|
||||
)
|
||||
.getManyAndCount()
|
||||
},
|
||||
})
|
||||
export default ProductCollectionRepository
|
||||
@@ -1,6 +1,6 @@
|
||||
import { EntityRepository, Repository } from "typeorm"
|
||||
import { ProductOptionValue } from "../models/product-option-value"
|
||||
import { ProductOptionValue } from "../models"
|
||||
import { dataSource } from "../loaders/database"
|
||||
|
||||
@EntityRepository(ProductOptionValue)
|
||||
// eslint-disable-next-line max-len
|
||||
export class ProductOptionValueRepository extends Repository<ProductOptionValue> {}
|
||||
export const ProductOptionValueRepository =
|
||||
dataSource.getRepository(ProductOptionValue)
|
||||
export default ProductOptionValueRepository
|
||||
@@ -1,5 +1,5 @@
|
||||
import { EntityRepository, Repository } from "typeorm"
|
||||
import { ProductOption } from "../models/product-option"
|
||||
import { ProductOption } from "../models"
|
||||
import { dataSource } from "../loaders/database"
|
||||
|
||||
@EntityRepository(ProductOption)
|
||||
export class ProductOptionRepository extends Repository<ProductOption> {}
|
||||
export const ProductOptionRepository = dataSource.getRepository(ProductOption)
|
||||
export default ProductOptionRepository
|
||||
@@ -1,6 +1,7 @@
|
||||
import { EntityRepository, In, Repository } from "typeorm"
|
||||
import { In } from "typeorm"
|
||||
import { ProductTag } from "../models/product-tag"
|
||||
import { ExtendedFindConfig, Selector } from "../types/common"
|
||||
import { ExtendedFindConfig } from "../types/common"
|
||||
import { dataSource } from "../loaders/database"
|
||||
|
||||
type UpsertTagsInput = (Partial<ProductTag> & {
|
||||
value: string
|
||||
@@ -12,7 +13,7 @@ type ProductTagSelector = Partial<ProductTag> & {
|
||||
}
|
||||
|
||||
export type DefaultWithoutRelations = Omit<
|
||||
ExtendedFindConfig<ProductTag, ProductTagSelector>,
|
||||
ExtendedFindConfig<ProductTag>,
|
||||
"relations"
|
||||
>
|
||||
|
||||
@@ -22,11 +23,12 @@ export type FindWithoutRelationsOptions = DefaultWithoutRelations & {
|
||||
}
|
||||
}
|
||||
|
||||
@EntityRepository(ProductTag)
|
||||
export class ProductTagRepository extends Repository<ProductTag> {
|
||||
public async listTagsByUsage(count = 10): Promise<ProductTag[]> {
|
||||
return await this.query(
|
||||
`
|
||||
export const ProductTagRepository = dataSource
|
||||
.getRepository(ProductTag)
|
||||
.extend({
|
||||
async listTagsByUsage(count = 10): Promise<ProductTag[]> {
|
||||
return await this.query(
|
||||
`
|
||||
SELECT id, COUNT(pts.product_tag_id) as usage_count, pt.value
|
||||
FROM product_tag pt
|
||||
LEFT JOIN product_tags pts ON pt.id = pts.product_tag_id
|
||||
@@ -34,63 +36,52 @@ export class ProductTagRepository extends Repository<ProductTag> {
|
||||
ORDER BY usage_count DESC
|
||||
LIMIT $1
|
||||
`,
|
||||
[count]
|
||||
)
|
||||
}
|
||||
|
||||
public async upsertTags(tags: UpsertTagsInput): Promise<ProductTag[]> {
|
||||
const tagsValues = tags.map((tag) => tag.value)
|
||||
const existingTags = await this.find({
|
||||
where: {
|
||||
value: In(tagsValues),
|
||||
},
|
||||
})
|
||||
const existingTagsMap = new Map(
|
||||
existingTags.map<[string, ProductTag]>((tag) => [tag.value, tag])
|
||||
)
|
||||
|
||||
const upsertedTags: ProductTag[] = []
|
||||
|
||||
for (const tag of tags) {
|
||||
const aTag = existingTagsMap.get(tag.value)
|
||||
if (aTag) {
|
||||
upsertedTags.push(aTag)
|
||||
} else {
|
||||
const newTag = this.create(tag)
|
||||
const savedTag = await this.save(newTag)
|
||||
upsertedTags.push(savedTag)
|
||||
}
|
||||
}
|
||||
|
||||
return upsertedTags
|
||||
}
|
||||
|
||||
async findAndCountByDiscountConditionId(
|
||||
conditionId: string,
|
||||
query: ExtendedFindConfig<ProductTag, Selector<ProductTag>>
|
||||
) {
|
||||
const qb = this.createQueryBuilder("pt")
|
||||
|
||||
if (query?.select) {
|
||||
qb.select(query.select.map((select) => `pt.${select}`))
|
||||
}
|
||||
|
||||
if (query.skip) {
|
||||
qb.skip(query.skip)
|
||||
}
|
||||
|
||||
if (query.take) {
|
||||
qb.take(query.take)
|
||||
}
|
||||
|
||||
return await qb
|
||||
.where(query.where)
|
||||
.innerJoin(
|
||||
"discount_condition_product_tag",
|
||||
"dc_pt",
|
||||
`dc_pt.product_tag_id = pt.id AND dc_pt.condition_id = :dcId`,
|
||||
{ dcId: conditionId }
|
||||
[count]
|
||||
)
|
||||
.getManyAndCount()
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
async upsertTags(tags: UpsertTagsInput): Promise<ProductTag[]> {
|
||||
const tagsValues = tags.map((tag) => tag.value)
|
||||
const existingTags = await this.find({
|
||||
where: {
|
||||
value: In(tagsValues),
|
||||
},
|
||||
})
|
||||
const existingTagsMap = new Map(
|
||||
existingTags.map<[string, ProductTag]>((tag) => [tag.value, tag])
|
||||
)
|
||||
|
||||
const upsertedTags: ProductTag[] = []
|
||||
|
||||
for (const tag of tags) {
|
||||
const aTag = existingTagsMap.get(tag.value)
|
||||
if (aTag) {
|
||||
upsertedTags.push(aTag)
|
||||
} else {
|
||||
const newTag = this.create(tag)
|
||||
const savedTag = await this.save(newTag)
|
||||
upsertedTags.push(savedTag)
|
||||
}
|
||||
}
|
||||
|
||||
return upsertedTags
|
||||
},
|
||||
|
||||
async findAndCountByDiscountConditionId(
|
||||
conditionId: string,
|
||||
query: ExtendedFindConfig<ProductTag>
|
||||
) {
|
||||
return await this.createQueryBuilder("pt")
|
||||
.where(query.where)
|
||||
.setFindOptions(query)
|
||||
.innerJoin(
|
||||
"discount_condition_product_tag",
|
||||
"dc_pt",
|
||||
`dc_pt.product_tag_id = pt.id AND dc_pt.condition_id = :dcId`,
|
||||
{ dcId: conditionId }
|
||||
)
|
||||
.getManyAndCount()
|
||||
},
|
||||
})
|
||||
|
||||
export default ProductTagRepository
|
||||
@@ -1,5 +1,5 @@
|
||||
import { EntityRepository, Repository } from "typeorm"
|
||||
import { ProductTaxRate } from "../models/product-tax-rate"
|
||||
import { ProductTaxRate } from "../models"
|
||||
import { dataSource } from "../loaders/database"
|
||||
|
||||
@EntityRepository(ProductTaxRate)
|
||||
export class ProductTaxRateRepository extends Repository<ProductTaxRate> {}
|
||||
export const ProductTaxRateRepository = dataSource.getRepository(ProductTaxRate)
|
||||
export default ProductTaxRateRepository
|
||||
@@ -1,58 +1,47 @@
|
||||
import { EntityRepository, Repository } from "typeorm"
|
||||
import { ProductType } from "../models/product-type"
|
||||
import { ExtendedFindConfig, Selector } from "../types/common"
|
||||
import { ExtendedFindConfig } from "../types/common"
|
||||
import { dataSource } from "../loaders/database"
|
||||
|
||||
type UpsertTypeInput = Partial<ProductType> & {
|
||||
value: string
|
||||
}
|
||||
|
||||
@EntityRepository(ProductType)
|
||||
export class ProductTypeRepository extends Repository<ProductType> {
|
||||
async upsertType(type?: UpsertTypeInput): Promise<ProductType | null> {
|
||||
if (!type) {
|
||||
return null
|
||||
}
|
||||
export const ProductTypeRepository = dataSource
|
||||
.getRepository(ProductType)
|
||||
.extend({
|
||||
async upsertType(type?: UpsertTypeInput): Promise<ProductType | null> {
|
||||
if (!type) {
|
||||
return null
|
||||
}
|
||||
|
||||
const existing = await this.findOne({
|
||||
where: { value: type.value },
|
||||
})
|
||||
const existing = await this.findOne({
|
||||
where: { value: type.value },
|
||||
})
|
||||
|
||||
if (existing) {
|
||||
return existing
|
||||
}
|
||||
if (existing) {
|
||||
return existing
|
||||
}
|
||||
|
||||
const created = this.create({
|
||||
value: type.value,
|
||||
})
|
||||
return await this.save(created)
|
||||
}
|
||||
const created = this.create({
|
||||
value: type.value,
|
||||
})
|
||||
return await this.save(created)
|
||||
},
|
||||
|
||||
async findAndCountByDiscountConditionId(
|
||||
conditionId: string,
|
||||
query: ExtendedFindConfig<ProductType, Selector<ProductType>>
|
||||
): Promise<[ProductType[], number]> {
|
||||
const qb = this.createQueryBuilder("pt")
|
||||
|
||||
if (query?.select) {
|
||||
qb.select(query.select.map((select) => `pt.${select}`))
|
||||
}
|
||||
|
||||
if (query.skip) {
|
||||
qb.skip(query.skip)
|
||||
}
|
||||
|
||||
if (query.take) {
|
||||
qb.take(query.take)
|
||||
}
|
||||
|
||||
return await qb
|
||||
.where(query.where)
|
||||
.innerJoin(
|
||||
"discount_condition_product_type",
|
||||
"dc_pt",
|
||||
`dc_pt.product_type_id = pt.id AND dc_pt.condition_id = :dcId`,
|
||||
{ dcId: conditionId }
|
||||
)
|
||||
.getManyAndCount()
|
||||
}
|
||||
}
|
||||
async findAndCountByDiscountConditionId(
|
||||
conditionId: string,
|
||||
query: ExtendedFindConfig<ProductType>
|
||||
): Promise<[ProductType[], number]> {
|
||||
return await this.createQueryBuilder("pt")
|
||||
.where(query.where)
|
||||
.setFindOptions(query)
|
||||
.innerJoin(
|
||||
"discount_condition_product_type",
|
||||
"dc_pt",
|
||||
`dc_pt.product_type_id = pt.id AND dc_pt.condition_id = :dcId`,
|
||||
{ dcId: conditionId }
|
||||
)
|
||||
.getManyAndCount()
|
||||
},
|
||||
})
|
||||
export default ProductTypeRepository
|
||||
@@ -1,254 +1,12 @@
|
||||
import {
|
||||
EntityRepository,
|
||||
FindConditions,
|
||||
FindManyOptions,
|
||||
OrderByCondition,
|
||||
Repository,
|
||||
} from "typeorm"
|
||||
import { flatten, groupBy, map, merge } from "lodash"
|
||||
import { FindManyOptions } from "typeorm"
|
||||
import { ProductVariant } from "../models/product-variant"
|
||||
import { FindOptionsOrder } from "typeorm/find-options/FindOptionsOrder"
|
||||
import { dataSource } from "../loaders/database"
|
||||
|
||||
export type FindWithRelationsOptions = FindManyOptions<ProductVariant> & {
|
||||
order?: OrderByCondition
|
||||
order?: FindOptionsOrder<ProductVariant>
|
||||
withDeleted?: boolean
|
||||
}
|
||||
|
||||
@EntityRepository(ProductVariant)
|
||||
export class ProductVariantRepository extends Repository<ProductVariant> {
|
||||
private mergeEntitiesWithRelations(
|
||||
entitiesAndRelations: Array<Partial<ProductVariant>>
|
||||
): ProductVariant[] {
|
||||
const entitiesAndRelationsById = groupBy(entitiesAndRelations, "id")
|
||||
return map(entitiesAndRelationsById, (entityAndRelations) =>
|
||||
merge({}, ...entityAndRelations)
|
||||
)
|
||||
}
|
||||
|
||||
private async queryProductsVariants(
|
||||
optionsWithoutRelations: FindWithRelationsOptions,
|
||||
shouldCount = false
|
||||
): Promise<[ProductVariant[], number]> {
|
||||
let qb = this.createQueryBuilder("pv")
|
||||
.select(["pv.id"])
|
||||
.skip(optionsWithoutRelations.skip)
|
||||
.take(optionsWithoutRelations.take)
|
||||
|
||||
qb = optionsWithoutRelations.where
|
||||
? qb.where(optionsWithoutRelations.where)
|
||||
: qb
|
||||
|
||||
qb = optionsWithoutRelations.order
|
||||
? qb.orderBy(optionsWithoutRelations.order)
|
||||
: qb
|
||||
|
||||
if (optionsWithoutRelations.withDeleted) {
|
||||
qb = qb.withDeleted()
|
||||
}
|
||||
|
||||
let entities: ProductVariant[]
|
||||
let count = 0
|
||||
if (shouldCount) {
|
||||
const result = await qb.getManyAndCount()
|
||||
entities = result[0]
|
||||
count = result[1]
|
||||
} else {
|
||||
entities = await qb.getMany()
|
||||
}
|
||||
|
||||
return [entities, count]
|
||||
}
|
||||
|
||||
private getGroupedRelations(relations: Array<keyof ProductVariant>): {
|
||||
[toplevel: string]: string[]
|
||||
} {
|
||||
const groupedRelations: { [toplevel: string]: string[] } = {}
|
||||
for (const rel of relations) {
|
||||
const [topLevel] = rel.split(".")
|
||||
if (groupedRelations[topLevel]) {
|
||||
groupedRelations[topLevel].push(rel)
|
||||
} else {
|
||||
groupedRelations[topLevel] = [rel]
|
||||
}
|
||||
}
|
||||
|
||||
return groupedRelations
|
||||
}
|
||||
|
||||
private async queryProductVariantsWithIds(
|
||||
entityIds: string[],
|
||||
groupedRelations: { [toplevel: string]: string[] },
|
||||
withDeleted = false
|
||||
): Promise<ProductVariant[]> {
|
||||
const entitiesIdsWithRelations = await Promise.all(
|
||||
Object.entries(groupedRelations).map(async ([toplevel, rels]) => {
|
||||
const querybuilder = this.createQueryBuilder("pv").leftJoinAndSelect(
|
||||
`pv.${toplevel}`,
|
||||
toplevel
|
||||
)
|
||||
|
||||
for (const rel of rels) {
|
||||
const [_, rest] = rel.split(".")
|
||||
if (!rest) {
|
||||
continue
|
||||
}
|
||||
// Regex matches all '.' except the rightmost
|
||||
querybuilder.leftJoinAndSelect(
|
||||
rel.replace(/\.(?=[^.]*\.)/g, "__"),
|
||||
rel.replace(".", "__")
|
||||
)
|
||||
}
|
||||
|
||||
if (withDeleted) {
|
||||
querybuilder
|
||||
.where("pv.id IN (:...entitiesIds)", {
|
||||
entitiesIds: entityIds,
|
||||
})
|
||||
.withDeleted()
|
||||
} else {
|
||||
querybuilder.where(
|
||||
"pv.deleted_at IS NULL AND pv.id IN (:...entitiesIds)",
|
||||
{
|
||||
entitiesIds: entityIds,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
return querybuilder.getMany()
|
||||
})
|
||||
).then(flatten)
|
||||
|
||||
return entitiesIdsWithRelations
|
||||
}
|
||||
|
||||
public async findWithRelationsAndCount(
|
||||
relations: string[] = [],
|
||||
idsOrOptionsWithoutRelations: FindWithRelationsOptions | string[] = {
|
||||
where: {},
|
||||
},
|
||||
withDeleted?: boolean
|
||||
): Promise<[ProductVariant[], number]> {
|
||||
let count: number
|
||||
let entities: ProductVariant[]
|
||||
if (Array.isArray(idsOrOptionsWithoutRelations)) {
|
||||
entities = await this.findByIds(idsOrOptionsWithoutRelations, {
|
||||
withDeleted: withDeleted ?? false,
|
||||
})
|
||||
count = entities.length
|
||||
} else {
|
||||
const result = await this.queryProductsVariants(
|
||||
idsOrOptionsWithoutRelations,
|
||||
true
|
||||
)
|
||||
entities = result[0]
|
||||
count = result[1]
|
||||
}
|
||||
const entitiesIds = entities.map(({ id }) => id)
|
||||
|
||||
if (entitiesIds.length === 0) {
|
||||
// no need to continue
|
||||
return [[], count]
|
||||
}
|
||||
|
||||
if (relations.length === 0) {
|
||||
const options = { ...idsOrOptionsWithoutRelations }
|
||||
|
||||
// Since we are finding by the ids that have been retrieved above and those ids are already
|
||||
// applying skip/take. Remove those options to avoid getting no results
|
||||
if (typeof options === "object") {
|
||||
delete (options as FindWithRelationsOptions).skip
|
||||
delete (options as FindWithRelationsOptions).take
|
||||
}
|
||||
|
||||
const toReturn = await this.findByIds(
|
||||
entitiesIds,
|
||||
options as FindConditions<ProductVariant>
|
||||
)
|
||||
return [toReturn, count]
|
||||
}
|
||||
|
||||
const groupedRelations = this.getGroupedRelations(
|
||||
relations as (keyof ProductVariant)[]
|
||||
)
|
||||
const entitiesIdsWithRelations = await this.queryProductVariantsWithIds(
|
||||
entitiesIds,
|
||||
groupedRelations,
|
||||
withDeleted
|
||||
)
|
||||
|
||||
const entitiesAndRelations = entitiesIdsWithRelations.concat(entities)
|
||||
const entitiesToReturn =
|
||||
this.mergeEntitiesWithRelations(entitiesAndRelations)
|
||||
|
||||
return [entitiesToReturn, count]
|
||||
}
|
||||
|
||||
public async findWithRelations(
|
||||
relations: string[] = [],
|
||||
idsOrOptionsWithoutRelations: FindWithRelationsOptions | string[] = {},
|
||||
withDeleted = false
|
||||
): Promise<ProductVariant[]> {
|
||||
let entities: ProductVariant[]
|
||||
if (Array.isArray(idsOrOptionsWithoutRelations)) {
|
||||
entities = await this.findByIds(idsOrOptionsWithoutRelations, {
|
||||
withDeleted,
|
||||
})
|
||||
} else {
|
||||
const result = await this.queryProductsVariants(
|
||||
idsOrOptionsWithoutRelations,
|
||||
false
|
||||
)
|
||||
entities = result[0]
|
||||
}
|
||||
const entitiesIds = entities.map(({ id }) => id)
|
||||
|
||||
if (entitiesIds.length === 0) {
|
||||
// no need to continue
|
||||
return []
|
||||
}
|
||||
|
||||
if (relations.length === 0) {
|
||||
const options = { ...idsOrOptionsWithoutRelations }
|
||||
|
||||
// Since we are finding by the ids that have been retrieved above and those ids are already
|
||||
// applying skip/take. Remove those options to avoid getting no results
|
||||
if (typeof options === "object") {
|
||||
delete (options as FindWithRelationsOptions).skip
|
||||
delete (options as FindWithRelationsOptions).take
|
||||
}
|
||||
|
||||
return await this.findByIds(
|
||||
entitiesIds,
|
||||
options as FindConditions<ProductVariant>
|
||||
)
|
||||
}
|
||||
|
||||
const groupedRelations = this.getGroupedRelations(
|
||||
relations as (keyof ProductVariant)[]
|
||||
)
|
||||
const entitiesIdsWithRelations = await this.queryProductVariantsWithIds(
|
||||
entitiesIds,
|
||||
groupedRelations,
|
||||
withDeleted
|
||||
)
|
||||
|
||||
const entitiesAndRelations = entitiesIdsWithRelations.concat(entities)
|
||||
const entitiesToReturn =
|
||||
this.mergeEntitiesWithRelations(entitiesAndRelations)
|
||||
|
||||
return entitiesToReturn
|
||||
}
|
||||
|
||||
public async findOneWithRelations(
|
||||
relations: Array<keyof ProductVariant> = [],
|
||||
optionsWithoutRelations: FindWithRelationsOptions = { where: {} }
|
||||
): Promise<ProductVariant> {
|
||||
// Limit 1
|
||||
optionsWithoutRelations.take = 1
|
||||
|
||||
const result = await this.findWithRelations(
|
||||
relations,
|
||||
optionsWithoutRelations
|
||||
)
|
||||
return result[0]
|
||||
}
|
||||
}
|
||||
export const ProductVariantRepository = dataSource.getRepository(ProductVariant)
|
||||
export default ProductVariantRepository
|
||||
@@ -1,116 +1,209 @@
|
||||
import { flatten, groupBy, map, merge } from "lodash"
|
||||
import {
|
||||
Brackets,
|
||||
EntityRepository,
|
||||
FindOperator,
|
||||
FindOptionsWhere,
|
||||
ILike,
|
||||
In,
|
||||
Repository,
|
||||
SelectQueryBuilder,
|
||||
} from "typeorm"
|
||||
import { PriceList, Product, ProductCategory, SalesChannel } from "../models"
|
||||
import {
|
||||
ExtendedFindConfig,
|
||||
Selector,
|
||||
WithRequiredProperty,
|
||||
} from "../types/common"
|
||||
import { applyOrdering } from "../utils/repository"
|
||||
import { Product, ProductCategory } from "../models"
|
||||
import { ExtendedFindConfig } from "../types/common"
|
||||
import { dataSource } from "../loaders/database"
|
||||
import { isObject } from "../utils"
|
||||
import { ProductFilterOptions } from "../types/product"
|
||||
|
||||
export type ProductSelector = Omit<Selector<Product>, "tags"> & {
|
||||
tags: FindOperator<string[]>
|
||||
}
|
||||
export const ProductRepository = dataSource.getRepository(Product).extend({
|
||||
async bulkAddToCollection(
|
||||
productIds: string[],
|
||||
collectionId: string
|
||||
): Promise<Product[]> {
|
||||
await this.createQueryBuilder()
|
||||
.update(Product)
|
||||
.set({ collection_id: collectionId })
|
||||
.where({ id: In(productIds) })
|
||||
.execute()
|
||||
|
||||
export type DefaultWithoutRelations = Omit<
|
||||
ExtendedFindConfig<Product, ProductSelector>,
|
||||
"relations"
|
||||
>
|
||||
return this.findByIds(productIds)
|
||||
},
|
||||
|
||||
export type FindWithoutRelationsOptions = DefaultWithoutRelations & {
|
||||
where: DefaultWithoutRelations["where"] & {
|
||||
price_list_id?: FindOperator<PriceList>
|
||||
sales_channel_id?: FindOperator<SalesChannel>
|
||||
category_id?: {
|
||||
value: string[]
|
||||
}
|
||||
include_category_children?: boolean
|
||||
discount_condition_id?: string
|
||||
}
|
||||
}
|
||||
async bulkRemoveFromCollection(
|
||||
productIds: string[],
|
||||
collectionId: string
|
||||
): Promise<Product[]> {
|
||||
await this.createQueryBuilder()
|
||||
.update(Product)
|
||||
.set({ collection_id: null })
|
||||
.where({ id: In(productIds), collection_id: collectionId })
|
||||
.execute()
|
||||
|
||||
@EntityRepository(Product)
|
||||
export class ProductRepository extends Repository<Product> {
|
||||
private mergeEntitiesWithRelations(
|
||||
entitiesAndRelations: Array<Partial<Product>>
|
||||
): Product[] {
|
||||
const entitiesAndRelationsById = groupBy(entitiesAndRelations, "id")
|
||||
return map(entitiesAndRelationsById, (entityAndRelations) =>
|
||||
merge({}, ...entityAndRelations)
|
||||
return this.findByIds(productIds)
|
||||
},
|
||||
|
||||
async isProductInSalesChannels(
|
||||
id: string,
|
||||
salesChannelIds: string[]
|
||||
): Promise<boolean> {
|
||||
return (
|
||||
(await this.createQueryBuilder("product")
|
||||
.leftJoin(
|
||||
"product.sales_channels",
|
||||
"sales_channels",
|
||||
"sales_channels.id IN (:...salesChannelIds)",
|
||||
{ salesChannelIds }
|
||||
)
|
||||
.getCount()) > 0
|
||||
)
|
||||
}
|
||||
},
|
||||
|
||||
private async queryProducts(
|
||||
optionsWithoutRelations: FindWithoutRelationsOptions,
|
||||
shouldCount = false
|
||||
async findAndCount(
|
||||
options: ExtendedFindConfig<Product & ProductFilterOptions>,
|
||||
q?: string
|
||||
): Promise<[Product[], number]> {
|
||||
const queryBuilder = await this.prepareQueryBuilder_(options, q)
|
||||
return await queryBuilder.getManyAndCount()
|
||||
},
|
||||
|
||||
async findOne(
|
||||
options: ExtendedFindConfig<Product & ProductFilterOptions>
|
||||
): Promise<Product | null> {
|
||||
const queryBuilder = await this.prepareQueryBuilder_(options)
|
||||
return await queryBuilder.getOne()
|
||||
},
|
||||
|
||||
async prepareQueryBuilder_(
|
||||
options: ExtendedFindConfig<Product & ProductFilterOptions>,
|
||||
q?: string
|
||||
): Promise<SelectQueryBuilder<Product>> {
|
||||
const options_ = { ...options }
|
||||
|
||||
const productAlias = "product"
|
||||
const queryBuilder = this.createQueryBuilder(productAlias)
|
||||
|
||||
const tags = optionsWithoutRelations?.where?.tags
|
||||
delete optionsWithoutRelations?.where?.tags
|
||||
// TODO: https://github.com/typeorm/typeorm/issues/9719 waiting an answer before being able to set it to `query`
|
||||
// Therefore use query when there is only an ordering by the product entity otherwise fallback to join.
|
||||
// In other word, if the order depth is more than 1 then use join otherwise use query
|
||||
/* const orderFieldsCollectionPointSeparated = buildLegacyFieldsListFrom(
|
||||
options.order ?? {}
|
||||
)
|
||||
const isDepth1 = !orderFieldsCollectionPointSeparated.some(
|
||||
(field) => field.indexOf(".") !== -1
|
||||
)
|
||||
options_.relationLoadStrategy = isDepth1 ? "query" : "join"*/
|
||||
options_.relationLoadStrategy = "join"
|
||||
|
||||
const price_lists = optionsWithoutRelations?.where?.price_list_id
|
||||
delete optionsWithoutRelations?.where?.price_list_id
|
||||
options_.relations = options_.relations ?? {}
|
||||
options_.where = options_.where as FindOptionsWhere<Product>
|
||||
|
||||
const sales_channels = optionsWithoutRelations?.where?.sales_channel_id
|
||||
delete optionsWithoutRelations?.where?.sales_channel_id
|
||||
// Add explicit ordering for variant ranking on the variants join directly
|
||||
// The constraint if there is any will be applied by the options_
|
||||
if (options_.relations.variants && !isObject(options_.order?.variants)) {
|
||||
options_.order = options_.order ?? {}
|
||||
options_.order.variants = {
|
||||
variant_rank: "ASC",
|
||||
}
|
||||
/* // The query strategy, as explain at the top of the function, does not select the column from the separated query
|
||||
// It is not possible to order with that strategy at the moment and, we are waiting for an answer from the typeorm team
|
||||
options_.relationLoadStrategy = "join"
|
||||
queryBuilder.leftJoinAndSelect(`${productAlias}.variants`, "variants")
|
||||
|
||||
const categories = optionsWithoutRelations?.where?.category_id
|
||||
delete optionsWithoutRelations?.where?.category_id
|
||||
options_.order = options_.order ?? {}
|
||||
|
||||
const include_category_children =
|
||||
optionsWithoutRelations?.where?.include_category_children
|
||||
delete optionsWithoutRelations?.where?.include_category_children
|
||||
|
||||
const discount_condition_id =
|
||||
optionsWithoutRelations?.where?.discount_condition_id
|
||||
delete optionsWithoutRelations?.where?.discount_condition_id
|
||||
|
||||
const qb = this.createQueryBuilder(productAlias)
|
||||
.select([`${productAlias}.id`])
|
||||
.skip(optionsWithoutRelations.skip)
|
||||
.take(optionsWithoutRelations.take)
|
||||
|
||||
if (optionsWithoutRelations.where) {
|
||||
qb.where(optionsWithoutRelations.where)
|
||||
}
|
||||
|
||||
if (tags) {
|
||||
qb.leftJoin(`${productAlias}.tags`, "tags").andWhere(
|
||||
`tags.id IN (:...tag_ids)`,
|
||||
{
|
||||
tag_ids: tags.value,
|
||||
if (!isObject(options_.order.variants)) {
|
||||
options_.order.variants = {
|
||||
variant_rank: "ASC",
|
||||
}
|
||||
)
|
||||
}*/
|
||||
}
|
||||
|
||||
if (price_lists) {
|
||||
qb.leftJoin(`${productAlias}.variants`, "variants")
|
||||
.leftJoin("variants.prices", "prices")
|
||||
.andWhere("prices.price_list_id IN (:...price_list_ids)", {
|
||||
price_list_ids: price_lists.value,
|
||||
if (options_.where.price_list_id) {
|
||||
/* options_.relations.variants = {
|
||||
...(isObject(options_.relations.variants)
|
||||
? options_.relations.variants
|
||||
: {}),
|
||||
prices: true,
|
||||
}
|
||||
|
||||
const priceListIds = (
|
||||
options_.where.price_list_id as FindOperator<string[]>
|
||||
).value
|
||||
delete options_.where.price_list_id
|
||||
|
||||
options_.where.variants = {
|
||||
...(isObject(options_.where.variants) ? options_.where.variants : {}),
|
||||
prices: [
|
||||
{
|
||||
price_list_id: In(priceListIds),
|
||||
},
|
||||
],
|
||||
}*/
|
||||
const priceListIds = (
|
||||
options_.where.price_list_id as FindOperator<string[]>
|
||||
).value
|
||||
delete options_.where.price_list_id
|
||||
|
||||
queryBuilder
|
||||
.leftJoin(`${productAlias}.variants`, "variants")
|
||||
.leftJoin("variants.prices", "ma")
|
||||
.andWhere("ma.price_list_id IN (:...price_list_ids)", {
|
||||
price_list_ids: priceListIds,
|
||||
})
|
||||
}
|
||||
|
||||
if (sales_channels) {
|
||||
qb.innerJoin(
|
||||
if (options_.where.tags) {
|
||||
const joinMethod = options_.relations.tags
|
||||
? queryBuilder.leftJoinAndSelect.bind(queryBuilder)
|
||||
: queryBuilder.leftJoin.bind(queryBuilder)
|
||||
|
||||
const tagIds = (options_.where.tags as FindOperator<string[]>).value
|
||||
|
||||
// For an unknown reason, the implementation of the SelectQueryBuilder.setFindOptions -> buildWhere
|
||||
// Only check if it is a find operator MoreThan or LessThan. Otherwise, it has to be a relation of
|
||||
// isManyToOne or isOneToOne in order to be valid. Otherwise, it throws `This relation isn't supported by given find operator`
|
||||
// We might need to wait for an update or open a PR around that subject
|
||||
|
||||
joinMethod(`${productAlias}.tags`, "tags").andWhere(
|
||||
`tags.id IN (:...tag_ids)`,
|
||||
{
|
||||
tag_ids: tagIds,
|
||||
}
|
||||
)
|
||||
|
||||
delete options_.where.tags
|
||||
}
|
||||
|
||||
if (options_.where.sales_channel_id) {
|
||||
const joinMethod = options_.relations.sales_channel_id
|
||||
? queryBuilder.innerJoinAndSelect.bind(queryBuilder)
|
||||
: queryBuilder.innerJoin.bind(queryBuilder)
|
||||
|
||||
const scIds = (options_.where.sales_channel_id as FindOperator<string[]>)
|
||||
.value
|
||||
|
||||
// Same comment as in the tags if block above + inner join is only doable using the query builder and not the options
|
||||
|
||||
joinMethod(
|
||||
`${productAlias}.sales_channels`,
|
||||
"sales_channels",
|
||||
"sales_channels.id IN (:...sales_channels_ids)",
|
||||
{ sales_channels_ids: sales_channels.value }
|
||||
{
|
||||
sales_channels_ids: scIds,
|
||||
}
|
||||
)
|
||||
|
||||
delete options_.where.sales_channel_id
|
||||
}
|
||||
|
||||
if (categories) {
|
||||
let categoryIds = categories.value
|
||||
if (options_.where.category_id) {
|
||||
const includeCategoryChildren =
|
||||
options_.where.include_category_children || false
|
||||
const joinMethod = options_.relations.category_id
|
||||
? queryBuilder.innerJoinAndSelect.bind(queryBuilder)
|
||||
: queryBuilder.innerJoin.bind(queryBuilder)
|
||||
|
||||
if (include_category_children) {
|
||||
let categoryIds = (options_.where.category_id as FindOperator<string[]>)
|
||||
.value
|
||||
|
||||
// Same comment as in the tags if block above + inner join is only doable using the query builder and not the options
|
||||
if (includeCategoryChildren) {
|
||||
const categoryRepository =
|
||||
this.manager.getTreeRepository(ProductCategory)
|
||||
const categories = await categoryRepository.find({
|
||||
@@ -139,406 +232,75 @@ export class ProductRepository extends Repository<Product> {
|
||||
}
|
||||
}
|
||||
|
||||
if (categoryIds.length) {
|
||||
qb.innerJoin(
|
||||
`${productAlias}.categories`,
|
||||
"categories",
|
||||
"categories.id IN (:...categoryIds)",
|
||||
{ categoryIds }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (discount_condition_id) {
|
||||
qb.innerJoin(
|
||||
"discount_condition_product",
|
||||
"dc_product",
|
||||
`dc_product.product_id = ${productAlias}.id AND dc_product.condition_id = :dcId`,
|
||||
{ dcId: discount_condition_id }
|
||||
)
|
||||
}
|
||||
|
||||
const joinedWithPriceLists = !!price_lists
|
||||
applyOrdering({
|
||||
repository: this,
|
||||
order: optionsWithoutRelations.order ?? {},
|
||||
qb,
|
||||
alias: productAlias,
|
||||
shouldJoin: (relation) => relation !== "prices" || !joinedWithPriceLists,
|
||||
})
|
||||
|
||||
if (optionsWithoutRelations.withDeleted) {
|
||||
qb.withDeleted()
|
||||
}
|
||||
|
||||
let entities: Product[]
|
||||
let count = 0
|
||||
if (shouldCount) {
|
||||
const result = await qb.getManyAndCount()
|
||||
entities = result[0]
|
||||
count = result[1]
|
||||
} else {
|
||||
entities = await qb.getMany()
|
||||
}
|
||||
|
||||
return [entities, count]
|
||||
}
|
||||
|
||||
private getGroupedRelations(relations: string[]): {
|
||||
[toplevel: string]: string[]
|
||||
} {
|
||||
const groupedRelations: { [toplevel: string]: string[] } = {}
|
||||
for (const rel of relations) {
|
||||
const [topLevel] = rel.split(".")
|
||||
if (groupedRelations[topLevel]) {
|
||||
groupedRelations[topLevel].push(rel)
|
||||
} else {
|
||||
groupedRelations[topLevel] = [rel]
|
||||
}
|
||||
}
|
||||
|
||||
return groupedRelations
|
||||
}
|
||||
|
||||
private async queryProductsWithIds(
|
||||
entityIds: string[],
|
||||
groupedRelations: { [toplevel: string]: string[] },
|
||||
withDeleted = false,
|
||||
select: (keyof Product)[] = [],
|
||||
order: { [column: string]: "ASC" | "DESC" } = {}
|
||||
): Promise<Product[]> {
|
||||
const entitiesIdsWithRelations = await Promise.all(
|
||||
Object.entries(groupedRelations).map(async ([toplevel, rels]) => {
|
||||
let querybuilder = this.createQueryBuilder("products")
|
||||
|
||||
if (select && select.length) {
|
||||
querybuilder.select(select.map((f) => `products.${f}`))
|
||||
}
|
||||
|
||||
if (toplevel === "variants") {
|
||||
querybuilder = querybuilder.leftJoinAndSelect(
|
||||
`products.${toplevel}`,
|
||||
toplevel,
|
||||
"variants.deleted_at IS NULL"
|
||||
)
|
||||
|
||||
if (!Object.keys(order).some((key) => key.startsWith("variants"))) {
|
||||
// variant_rank being select false, apply the filter here directly
|
||||
querybuilder.addOrderBy(`${toplevel}.variant_rank`, "ASC")
|
||||
}
|
||||
} else {
|
||||
querybuilder = querybuilder.leftJoinAndSelect(
|
||||
`products.${toplevel}`,
|
||||
toplevel
|
||||
)
|
||||
}
|
||||
|
||||
for (const rel of rels) {
|
||||
const [_, rest] = rel.split(".")
|
||||
if (!rest) {
|
||||
continue
|
||||
}
|
||||
// Regex matches all '.' except the rightmost
|
||||
querybuilder = querybuilder.leftJoinAndSelect(
|
||||
rel.replace(/\.(?=[^.]*\.)/g, "__"),
|
||||
rel.replace(".", "__")
|
||||
)
|
||||
}
|
||||
|
||||
if (withDeleted) {
|
||||
querybuilder = querybuilder
|
||||
.where("products.id IN (:...entitiesIds)", {
|
||||
entitiesIds: entityIds,
|
||||
})
|
||||
.withDeleted()
|
||||
} else {
|
||||
querybuilder = querybuilder.where(
|
||||
"products.deleted_at IS NULL AND products.id IN (:...entitiesIds)",
|
||||
{
|
||||
entitiesIds: entityIds,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
return querybuilder.getMany()
|
||||
})
|
||||
).then(flatten)
|
||||
|
||||
return entitiesIdsWithRelations
|
||||
}
|
||||
|
||||
public async findWithRelationsAndCount(
|
||||
relations: string[] = [],
|
||||
idsOrOptionsWithoutRelations: FindWithoutRelationsOptions = { where: {} }
|
||||
): Promise<[Product[], number]> {
|
||||
let count: number
|
||||
let entities: Product[]
|
||||
|
||||
if (Array.isArray(idsOrOptionsWithoutRelations)) {
|
||||
entities = await this.findByIds(idsOrOptionsWithoutRelations, {
|
||||
withDeleted: idsOrOptionsWithoutRelations.withDeleted ?? false,
|
||||
})
|
||||
count = entities.length
|
||||
} else {
|
||||
const result = await this.queryProducts(
|
||||
idsOrOptionsWithoutRelations,
|
||||
true
|
||||
)
|
||||
entities = result[0]
|
||||
count = result[1]
|
||||
}
|
||||
const entitiesIds = entities.map(({ id }) => id)
|
||||
|
||||
if (entitiesIds.length === 0) {
|
||||
// no need to continue
|
||||
return [[], count]
|
||||
}
|
||||
|
||||
if (relations.length === 0) {
|
||||
const options = { ...idsOrOptionsWithoutRelations }
|
||||
|
||||
// Since we are finding by the ids that have been retrieved above and those ids are already
|
||||
// applying skip/take. Remove those options to avoid getting no results
|
||||
delete options.skip
|
||||
delete options.take
|
||||
|
||||
const toReturn = await this.findByIds(entitiesIds, options)
|
||||
return [toReturn, toReturn.length]
|
||||
}
|
||||
|
||||
const groupedRelations = this.getGroupedRelations(relations)
|
||||
const entitiesIdsWithRelations = await this.queryProductsWithIds(
|
||||
entitiesIds,
|
||||
groupedRelations,
|
||||
idsOrOptionsWithoutRelations.withDeleted,
|
||||
idsOrOptionsWithoutRelations.select,
|
||||
idsOrOptionsWithoutRelations.order
|
||||
)
|
||||
|
||||
const entitiesAndRelations = groupBy(entitiesIdsWithRelations, "id")
|
||||
const entitiesToReturn = map(entitiesIds, (id) =>
|
||||
merge({}, ...entitiesAndRelations[id])
|
||||
)
|
||||
|
||||
return [entitiesToReturn, count]
|
||||
}
|
||||
|
||||
public async findWithRelations(
|
||||
relations: string[] = [],
|
||||
idsOrOptionsWithoutRelations: FindWithoutRelationsOptions | string[] = {
|
||||
where: {},
|
||||
},
|
||||
withDeleted = false
|
||||
): Promise<Product[]> {
|
||||
let entities: Product[]
|
||||
if (Array.isArray(idsOrOptionsWithoutRelations)) {
|
||||
entities = await this.findByIds(idsOrOptionsWithoutRelations, {
|
||||
withDeleted,
|
||||
})
|
||||
} else {
|
||||
const result = await this.queryProducts(
|
||||
idsOrOptionsWithoutRelations,
|
||||
false
|
||||
)
|
||||
entities = result[0]
|
||||
}
|
||||
const entitiesIds = entities.map(({ id }) => id)
|
||||
|
||||
if (entitiesIds.length === 0) {
|
||||
// no need to continue
|
||||
return []
|
||||
}
|
||||
|
||||
if (
|
||||
relations.length === 0 &&
|
||||
!Array.isArray(idsOrOptionsWithoutRelations)
|
||||
) {
|
||||
return await this.findByIds(entitiesIds, idsOrOptionsWithoutRelations)
|
||||
}
|
||||
|
||||
const groupedRelations = this.getGroupedRelations(relations)
|
||||
const entitiesIdsWithRelations = await this.queryProductsWithIds(
|
||||
entitiesIds,
|
||||
groupedRelations,
|
||||
withDeleted
|
||||
)
|
||||
|
||||
const entitiesAndRelations = entitiesIdsWithRelations.concat(entities)
|
||||
const entitiesToReturn =
|
||||
this.mergeEntitiesWithRelations(entitiesAndRelations)
|
||||
|
||||
return entitiesToReturn
|
||||
}
|
||||
|
||||
public async findOneWithRelations(
|
||||
relations: string[] = [],
|
||||
optionsWithoutRelations: FindWithoutRelationsOptions = { where: {} }
|
||||
): Promise<Product> {
|
||||
// Limit 1
|
||||
optionsWithoutRelations.take = 1
|
||||
|
||||
const result = await this.findWithRelations(
|
||||
relations,
|
||||
optionsWithoutRelations
|
||||
)
|
||||
return result[0]
|
||||
}
|
||||
|
||||
public async bulkAddToCollection(
|
||||
productIds: string[],
|
||||
collectionId: string
|
||||
): Promise<Product[]> {
|
||||
await this.createQueryBuilder()
|
||||
.update(Product)
|
||||
.set({ collection_id: collectionId })
|
||||
.where({ id: In(productIds) })
|
||||
.execute()
|
||||
|
||||
return this.findByIds(productIds)
|
||||
}
|
||||
|
||||
public async bulkRemoveFromCollection(
|
||||
productIds: string[],
|
||||
collectionId: string
|
||||
): Promise<Product[]> {
|
||||
await this.createQueryBuilder()
|
||||
.update(Product)
|
||||
.set({ collection_id: null })
|
||||
.where({ id: In(productIds), collection_id: collectionId })
|
||||
.execute()
|
||||
|
||||
return this.findByIds(productIds)
|
||||
}
|
||||
|
||||
public async getFreeTextSearchResultsAndCount(
|
||||
q: string,
|
||||
options: FindWithoutRelationsOptions = { where: {} },
|
||||
relations: string[] = []
|
||||
): Promise<[Product[], number]> {
|
||||
const productAlias = "product"
|
||||
const pricesAlias = "prices"
|
||||
const variantsAlias = "variants"
|
||||
const collectionAlias = "collection"
|
||||
const tagsAlias = "tags"
|
||||
|
||||
const tags = options.where.tags
|
||||
delete options.where.tags
|
||||
|
||||
const price_lists = options.where.price_list_id
|
||||
delete options.where.price_list_id
|
||||
|
||||
const sales_channels = options.where.sales_channel_id
|
||||
delete options.where.sales_channel_id
|
||||
|
||||
const discount_condition_id = options.where.discount_condition_id
|
||||
delete options.where.discount_condition_id
|
||||
|
||||
const cleanedOptions = this._cleanOptions(options)
|
||||
|
||||
let qb = this.createQueryBuilder(`${productAlias}`)
|
||||
.leftJoinAndSelect(`${productAlias}.variants`, variantsAlias)
|
||||
.leftJoinAndSelect(`${productAlias}.collection`, `${collectionAlias}`)
|
||||
.select([`${productAlias}.id`])
|
||||
.where(cleanedOptions.where)
|
||||
.andWhere(
|
||||
new Brackets((qb) => {
|
||||
qb.where(`${productAlias}.description ILIKE :q`, { q: `%${q}%` })
|
||||
.orWhere(`${productAlias}.title ILIKE :q`, { q: `%${q}%` })
|
||||
.orWhere(`${variantsAlias}.title ILIKE :q`, { q: `%${q}%` })
|
||||
.orWhere(`${variantsAlias}.sku ILIKE :q`, { q: `%${q}%` })
|
||||
.orWhere(`${collectionAlias}.title ILIKE :q`, { q: `%${q}%` })
|
||||
})
|
||||
)
|
||||
.skip(cleanedOptions.skip)
|
||||
.take(cleanedOptions.take)
|
||||
|
||||
if (discount_condition_id) {
|
||||
qb.innerJoin(
|
||||
"discount_condition_product",
|
||||
"dc_product",
|
||||
`dc_product.product_id = ${productAlias}.id AND dc_product.condition_id = :dcId`,
|
||||
{ dcId: discount_condition_id }
|
||||
)
|
||||
}
|
||||
|
||||
if (tags) {
|
||||
qb.leftJoin(`${productAlias}.tags`, tagsAlias).andWhere(
|
||||
`${tagsAlias}.id IN (:...tag_ids)`,
|
||||
joinMethod(
|
||||
`${productAlias}.categories`,
|
||||
"categories",
|
||||
"categories.id IN (:...categoryIds)",
|
||||
{
|
||||
tag_ids: tags.value,
|
||||
categoryIds,
|
||||
}
|
||||
)
|
||||
|
||||
delete options_.where.category_id
|
||||
}
|
||||
|
||||
if (price_lists) {
|
||||
const variantPricesAlias = `${variantsAlias}_prices`
|
||||
qb.leftJoin(`${productAlias}.variants`, variantPricesAlias)
|
||||
.leftJoin(`${variantPricesAlias}.prices`, pricesAlias)
|
||||
.andWhere(`${pricesAlias}.price_list_id IN (:...price_list_ids)`, {
|
||||
price_list_ids: price_lists.value,
|
||||
})
|
||||
}
|
||||
delete options_.where.include_category_children
|
||||
|
||||
if (sales_channels) {
|
||||
qb.innerJoin(
|
||||
`${productAlias}.sales_channels`,
|
||||
"sales_channels",
|
||||
"sales_channels.id IN (:...sales_channels_ids)",
|
||||
{ sales_channels_ids: sales_channels.value }
|
||||
if (options_.where.discount_condition_id) {
|
||||
// inner join is only doable using the query builder and not the options
|
||||
|
||||
queryBuilder.innerJoin(
|
||||
"discount_condition_product",
|
||||
"dc_product",
|
||||
`dc_product.product_id = product.id AND dc_product.condition_id = :dcId`,
|
||||
{ dcId: options_.where.discount_condition_id }
|
||||
)
|
||||
|
||||
delete options_.where.discount_condition_id
|
||||
}
|
||||
// TODO: move back to the service layer
|
||||
if (q) {
|
||||
options_.relations = options_.relations ?? {}
|
||||
options_.relations.variants = options_.relations.variants ?? true
|
||||
options_.relations.collection = options_.relations.collection ?? true
|
||||
|
||||
options_.where = [
|
||||
{
|
||||
...options_.where,
|
||||
description: ILike(`%${q}%`),
|
||||
},
|
||||
{
|
||||
...options_.where,
|
||||
title: ILike(`%${q}%`),
|
||||
},
|
||||
{
|
||||
...options_.where,
|
||||
variants: {
|
||||
title: ILike(`%${q}%`),
|
||||
},
|
||||
},
|
||||
{
|
||||
...options_.where,
|
||||
variants: {
|
||||
sku: ILike(`%${q}%`),
|
||||
},
|
||||
},
|
||||
{
|
||||
...options_.where,
|
||||
collection: {
|
||||
title: ILike(`%${q}%`),
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
const joinedWithTags = !!tags
|
||||
const joinedWithPriceLists = !!price_lists
|
||||
applyOrdering({
|
||||
repository: this,
|
||||
order: options.order ?? {},
|
||||
qb,
|
||||
alias: productAlias,
|
||||
shouldJoin: (relation) =>
|
||||
relation !== variantsAlias &&
|
||||
(relation !== pricesAlias || !joinedWithPriceLists) &&
|
||||
(relation !== tagsAlias || !joinedWithTags),
|
||||
})
|
||||
|
||||
if (cleanedOptions.withDeleted) {
|
||||
qb = qb.withDeleted()
|
||||
if (options_.withDeleted) {
|
||||
queryBuilder.withDeleted()
|
||||
}
|
||||
|
||||
const [results, count] = await qb.getManyAndCount()
|
||||
const orderedResultsSet = new Set(results.map((p) => p.id))
|
||||
|
||||
const products = await this.findWithRelations(
|
||||
relations,
|
||||
[...orderedResultsSet],
|
||||
cleanedOptions.withDeleted
|
||||
)
|
||||
const productsMap = new Map(products.map((p) => [p.id, p]))
|
||||
|
||||
// Looping through the orderedResultsSet in order to maintain the original order and assign the data returned by findWithRelations
|
||||
const orderedProducts: Product[] = []
|
||||
orderedResultsSet.forEach((id) => {
|
||||
orderedProducts.push(productsMap.get(id)!)
|
||||
})
|
||||
|
||||
return [orderedProducts, count]
|
||||
}
|
||||
|
||||
public async isProductInSalesChannels(
|
||||
id: string,
|
||||
salesChannelIds: string[]
|
||||
): Promise<boolean> {
|
||||
return (
|
||||
(await this.createQueryBuilder("product")
|
||||
.leftJoin(
|
||||
"product.sales_channels",
|
||||
"sales_channels",
|
||||
"sales_channels.id IN (:...salesChannelIds)",
|
||||
{ salesChannelIds }
|
||||
)
|
||||
.getCount()) > 0
|
||||
)
|
||||
}
|
||||
queryBuilder.setFindOptions(options_)
|
||||
return queryBuilder
|
||||
},
|
||||
|
||||
/**
|
||||
* Upserts shipping profile for products
|
||||
@@ -546,7 +308,7 @@ export class ProductRepository extends Repository<Product> {
|
||||
* @param shippingProfileId ID of shipping profile to assign to products
|
||||
* @returns updated products
|
||||
*/
|
||||
public async upsertShippingProfile(
|
||||
async upsertShippingProfile(
|
||||
productIds: string[],
|
||||
shippingProfileId: string
|
||||
): Promise<Product[]> {
|
||||
@@ -557,30 +319,7 @@ export class ProductRepository extends Repository<Product> {
|
||||
.execute()
|
||||
|
||||
return await this.findByIds(productIds)
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
private _cleanOptions(
|
||||
options: FindWithoutRelationsOptions
|
||||
): WithRequiredProperty<FindWithoutRelationsOptions, "where"> {
|
||||
const where = options.where ?? {}
|
||||
if ("description" in where) {
|
||||
delete where.description
|
||||
}
|
||||
if ("title" in where) {
|
||||
delete where.title
|
||||
}
|
||||
|
||||
if ("price_list_id" in where) {
|
||||
delete where?.price_list_id
|
||||
}
|
||||
|
||||
if ("discount_condition_id" in where) {
|
||||
delete where?.discount_condition_id
|
||||
}
|
||||
|
||||
return {
|
||||
...options,
|
||||
where,
|
||||
}
|
||||
}
|
||||
}
|
||||
export default ProductRepository
|
||||
@@ -1,91 +1,94 @@
|
||||
import { Brackets, EntityRepository, In, Repository } from "typeorm"
|
||||
import { Brackets, In } from "typeorm"
|
||||
|
||||
import { PublishableApiKeySalesChannel, SalesChannel } from "../models"
|
||||
import { dataSource } from "../loaders/database"
|
||||
|
||||
@EntityRepository(PublishableApiKeySalesChannel)
|
||||
export class PublishableApiKeySalesChannelRepository extends Repository<PublishableApiKeySalesChannel> {
|
||||
/**
|
||||
* Query a list of sales channels that are assigned to the publishable key scope
|
||||
*
|
||||
* @param publishableApiKeyId - id of the key to retrieve channels for
|
||||
* @param config - querying params
|
||||
*/
|
||||
public async findSalesChannels(
|
||||
publishableApiKeyId: string,
|
||||
config?: { q?: string }
|
||||
): Promise<SalesChannel[]> {
|
||||
const query = this.createQueryBuilder("PublishableKeySalesChannel")
|
||||
.select("PublishableKeySalesChannel.sales_channel_id")
|
||||
.innerJoinAndMapOne(
|
||||
"PublishableKeySalesChannel.sales_channel_id",
|
||||
SalesChannel,
|
||||
"SalesChannel",
|
||||
"PublishableKeySalesChannel.sales_channel_id = SalesChannel.id"
|
||||
)
|
||||
.where(
|
||||
"PublishableKeySalesChannel.publishable_key_id = :publishableApiKeyId",
|
||||
{
|
||||
publishableApiKeyId,
|
||||
}
|
||||
export const PublishableApiKeySalesChannelRepository = dataSource
|
||||
.getRepository(PublishableApiKeySalesChannel)
|
||||
.extend({
|
||||
/**
|
||||
* Query a list of sales channels that are assigned to the publishable key scope
|
||||
*
|
||||
* @param publishableApiKeyId - id of the key to retrieve channels for
|
||||
* @param config - querying params
|
||||
*/
|
||||
async findSalesChannels(
|
||||
publishableApiKeyId: string,
|
||||
config?: { q?: string }
|
||||
): Promise<SalesChannel[]> {
|
||||
const query = this.createQueryBuilder("PublishableKeySalesChannel")
|
||||
.select("PublishableKeySalesChannel.sales_channel_id")
|
||||
.innerJoinAndMapOne(
|
||||
"PublishableKeySalesChannel.sales_channel_id",
|
||||
SalesChannel,
|
||||
"SalesChannel",
|
||||
"PublishableKeySalesChannel.sales_channel_id = SalesChannel.id"
|
||||
)
|
||||
.where(
|
||||
"PublishableKeySalesChannel.publishable_key_id = :publishableApiKeyId",
|
||||
{
|
||||
publishableApiKeyId,
|
||||
}
|
||||
)
|
||||
|
||||
if (config?.q) {
|
||||
query.andWhere(
|
||||
new Brackets((qb) => {
|
||||
qb.where(`SalesChannel.description ILIKE :q`, {
|
||||
q: `%${config.q}%`,
|
||||
}).orWhere(`SalesChannel.name ILIKE :q`, { q: `%${config.q}%` })
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
const records = await query.getMany()
|
||||
|
||||
return records.map(
|
||||
(record) => record.sales_channel_id as unknown as SalesChannel
|
||||
)
|
||||
},
|
||||
|
||||
if (config?.q) {
|
||||
query.andWhere(
|
||||
new Brackets((qb) => {
|
||||
qb.where(`SalesChannel.description ILIKE :q`, {
|
||||
q: `%${config.q}%`,
|
||||
}).orWhere(`SalesChannel.name ILIKE :q`, { q: `%${config.q}%` })
|
||||
})
|
||||
)
|
||||
}
|
||||
/**
|
||||
* Assign (multiple) sales channels to the Publishable Key scope
|
||||
*
|
||||
* @param publishableApiKeyId - publishable key id
|
||||
* @param salesChannelIds - an array of SC ids
|
||||
*/
|
||||
async addSalesChannels(
|
||||
publishableApiKeyId: string,
|
||||
salesChannelIds: string[]
|
||||
): Promise<void> {
|
||||
await this.createQueryBuilder()
|
||||
.insert()
|
||||
.into("publishable_api_key_sales_channel")
|
||||
.values(
|
||||
salesChannelIds.map((id) => ({
|
||||
sales_channel_id: id,
|
||||
publishable_key_id: publishableApiKeyId,
|
||||
}))
|
||||
)
|
||||
.orIgnore()
|
||||
.execute()
|
||||
},
|
||||
|
||||
const records = await query.getMany()
|
||||
|
||||
return records.map(
|
||||
(record) => record.sales_channel_id as unknown as SalesChannel
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign (multiple) sales channels to the Publishable Key scope
|
||||
*
|
||||
* @param publishableApiKeyId - publishable key id
|
||||
* @param salesChannelIds - an array of SC ids
|
||||
*/
|
||||
public async addSalesChannels(
|
||||
publishableApiKeyId: string,
|
||||
salesChannelIds: string[]
|
||||
): Promise<void> {
|
||||
await this.createQueryBuilder()
|
||||
.insert()
|
||||
.into("publishable_api_key_sales_channel")
|
||||
.values(
|
||||
salesChannelIds.map((id) => ({
|
||||
sales_channel_id: id,
|
||||
/**
|
||||
* Remove multiple sales channels from the PK scope
|
||||
*
|
||||
* @param publishableApiKeyId -publishable key id
|
||||
* @param salesChannelIds - an array of SC ids
|
||||
*/
|
||||
async removeSalesChannels(
|
||||
publishableApiKeyId: string,
|
||||
salesChannelIds: string[]
|
||||
): Promise<void> {
|
||||
await this.createQueryBuilder()
|
||||
.delete()
|
||||
.from("publishable_api_key_sales_channel")
|
||||
.where({
|
||||
sales_channel_id: In(salesChannelIds),
|
||||
publishable_key_id: publishableApiKeyId,
|
||||
}))
|
||||
)
|
||||
.orIgnore()
|
||||
.execute()
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove multiple sales channels from the PK scope
|
||||
*
|
||||
* @param publishableApiKeyId -publishable key id
|
||||
* @param salesChannelIds - an array of SC ids
|
||||
*/
|
||||
public async removeSalesChannels(
|
||||
publishableApiKeyId: string,
|
||||
salesChannelIds: string[]
|
||||
): Promise<void> {
|
||||
await this.createQueryBuilder()
|
||||
.delete()
|
||||
.from("publishable_api_key_sales_channel")
|
||||
.where({
|
||||
sales_channel_id: In(salesChannelIds),
|
||||
publishable_key_id: publishableApiKeyId,
|
||||
})
|
||||
.execute()
|
||||
}
|
||||
}
|
||||
})
|
||||
.execute()
|
||||
},
|
||||
})
|
||||
export default PublishableApiKeySalesChannelRepository
|
||||
@@ -1,71 +1,6 @@
|
||||
import { flatten, groupBy, merge } from "lodash"
|
||||
import { EntityRepository, FindManyOptions, Repository } from "typeorm"
|
||||
|
||||
import { PublishableApiKey } from "../models"
|
||||
import { dataSource } from "../loaders/database"
|
||||
|
||||
@EntityRepository(PublishableApiKey)
|
||||
export class PublishableApiKeyRepository extends Repository<PublishableApiKey> {
|
||||
public async findWithRelations(
|
||||
relations: (keyof PublishableApiKey | string)[] = [],
|
||||
idsOrOptionsWithoutRelations:
|
||||
| Omit<FindManyOptions<PublishableApiKey>, "relations">
|
||||
| string[] = {}
|
||||
): Promise<[PublishableApiKey[], number]> {
|
||||
let entities: PublishableApiKey[] = []
|
||||
let count = 0
|
||||
if (Array.isArray(idsOrOptionsWithoutRelations)) {
|
||||
entities = await this.findByIds(idsOrOptionsWithoutRelations)
|
||||
count = idsOrOptionsWithoutRelations.length
|
||||
} else {
|
||||
const [results, resultCount] = await this.findAndCount(
|
||||
idsOrOptionsWithoutRelations
|
||||
)
|
||||
entities = results
|
||||
count = resultCount
|
||||
}
|
||||
const entitiesIds = entities.map(({ id }) => id)
|
||||
|
||||
const groupedRelations = {}
|
||||
for (const rel of relations) {
|
||||
const [topLevel] = rel.split(".")
|
||||
if (groupedRelations[topLevel]) {
|
||||
groupedRelations[topLevel].push(rel)
|
||||
} else {
|
||||
groupedRelations[topLevel] = [rel]
|
||||
}
|
||||
}
|
||||
|
||||
const entitiesIdsWithRelations = await Promise.all(
|
||||
Object.entries(groupedRelations).map(async ([_, rels]) => {
|
||||
return this.findByIds(entitiesIds, {
|
||||
select: ["id"],
|
||||
relations: rels as string[],
|
||||
})
|
||||
})
|
||||
).then(flatten)
|
||||
const entitiesAndRelations = entitiesIdsWithRelations.concat(entities)
|
||||
|
||||
const entitiesAndRelationsById = groupBy(entitiesAndRelations, "id")
|
||||
return [
|
||||
Object.values(entitiesAndRelationsById).map((v) => merge({}, ...v)),
|
||||
count,
|
||||
]
|
||||
}
|
||||
|
||||
public async findOneWithRelations(
|
||||
relations: Array<keyof PublishableApiKey> = [],
|
||||
optionsWithoutRelations: Omit<
|
||||
FindManyOptions<PublishableApiKey>,
|
||||
"relations"
|
||||
> = {}
|
||||
): Promise<PublishableApiKey> {
|
||||
// Limit 1
|
||||
optionsWithoutRelations.take = 1
|
||||
|
||||
const [result] = await this.findWithRelations(
|
||||
relations,
|
||||
optionsWithoutRelations
|
||||
)
|
||||
return result[0]
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line max-len
|
||||
export const PublishableApiKeyRepository = dataSource.getRepository(PublishableApiKey)
|
||||
export default PublishableApiKeyRepository
|
||||
@@ -1,5 +1,5 @@
|
||||
import { EntityRepository, Repository } from "typeorm"
|
||||
import { Refund } from "../models/refund"
|
||||
import { Refund } from "../models"
|
||||
import { dataSource } from "../loaders/database"
|
||||
|
||||
@EntityRepository(Refund)
|
||||
export class RefundRepository extends Repository<Refund> {}
|
||||
export const RefundRepository = dataSource.getRepository(Refund)
|
||||
export default RefundRepository
|
||||
@@ -1,5 +1,5 @@
|
||||
import { EntityRepository, Repository } from "typeorm"
|
||||
import { Region } from "../models/region"
|
||||
import { Region } from "../models"
|
||||
import { dataSource } from "../loaders/database"
|
||||
|
||||
@EntityRepository(Region)
|
||||
export class RegionRepository extends Repository<Region> {}
|
||||
export const RegionRepository = dataSource.getRepository(Region)
|
||||
export default RegionRepository
|
||||
@@ -1,5 +1,5 @@
|
||||
import { EntityRepository, Repository } from "typeorm"
|
||||
import { ReturnItem } from "../models/return-item"
|
||||
import { ReturnItem } from "../models"
|
||||
import { dataSource } from "../loaders/database"
|
||||
|
||||
@EntityRepository(ReturnItem)
|
||||
export class ReturnItemRepository extends Repository<ReturnItem> {}
|
||||
export const ReturnItemRepository = dataSource.getRepository(ReturnItem)
|
||||
export default ReturnItemRepository
|
||||
@@ -1,5 +1,5 @@
|
||||
import { EntityRepository, Repository } from "typeorm"
|
||||
import { ReturnReason } from "../models/return-reason"
|
||||
import { ReturnReason } from "../models"
|
||||
import { dataSource } from "../loaders/database"
|
||||
|
||||
@EntityRepository(ReturnReason)
|
||||
export class ReturnReasonRepository extends Repository<ReturnReason> {}
|
||||
export const ReturnReasonRepository = dataSource.getRepository(ReturnReason)
|
||||
export default ReturnReasonRepository
|
||||
@@ -1,5 +1,5 @@
|
||||
import { EntityRepository, Repository } from "typeorm"
|
||||
import { Return } from "../models/return"
|
||||
import { Return } from "../models"
|
||||
import { dataSource } from "../loaders/database"
|
||||
|
||||
@EntityRepository(Return)
|
||||
export class ReturnRepository extends Repository<Return> {}
|
||||
export const ReturnRepository = dataSource.getRepository(Return)
|
||||
export default ReturnRepository
|
||||
@@ -1,139 +1,72 @@
|
||||
import {
|
||||
Brackets,
|
||||
DeleteResult,
|
||||
EntityRepository,
|
||||
FindManyOptions,
|
||||
In,
|
||||
Repository,
|
||||
} from "typeorm"
|
||||
import { Brackets, DeleteResult, FindOptionsWhere, In } from "typeorm"
|
||||
import { SalesChannel } from "../models"
|
||||
import { ExtendedFindConfig, Selector } from "../types/common"
|
||||
import { flatten, groupBy, merge } from "lodash"
|
||||
import { ExtendedFindConfig } from "../types/common"
|
||||
import { dataSource } from "../loaders/database"
|
||||
|
||||
@EntityRepository(SalesChannel)
|
||||
export class SalesChannelRepository extends Repository<SalesChannel> {
|
||||
public async findWithRelations(
|
||||
relations: (keyof SalesChannel | string)[] = [],
|
||||
idsOrOptionsWithoutRelations:
|
||||
| Omit<FindManyOptions<SalesChannel>, "relations">
|
||||
| string[] = {}
|
||||
): Promise<[SalesChannel[], number]> {
|
||||
let entities: SalesChannel[] = []
|
||||
let count = 0
|
||||
if (Array.isArray(idsOrOptionsWithoutRelations)) {
|
||||
entities = await this.findByIds(idsOrOptionsWithoutRelations)
|
||||
count = idsOrOptionsWithoutRelations.length
|
||||
} else {
|
||||
const [results, resultCount] = await this.findAndCount(
|
||||
idsOrOptionsWithoutRelations
|
||||
)
|
||||
entities = results
|
||||
count = resultCount
|
||||
}
|
||||
const entitiesIds = entities.map(({ id }) => id)
|
||||
|
||||
const groupedRelations = {}
|
||||
for (const rel of relations) {
|
||||
const [topLevel] = rel.split(".")
|
||||
if (groupedRelations[topLevel]) {
|
||||
groupedRelations[topLevel].push(rel)
|
||||
} else {
|
||||
groupedRelations[topLevel] = [rel]
|
||||
export const SalesChannelRepository = dataSource
|
||||
.getRepository(SalesChannel)
|
||||
.extend({
|
||||
async getFreeTextSearchResultsAndCount(
|
||||
q: string,
|
||||
options: ExtendedFindConfig<SalesChannel> = {
|
||||
where: {},
|
||||
}
|
||||
}
|
||||
): Promise<[SalesChannel[], number]> {
|
||||
const options_ = { ...options }
|
||||
options_.where = options_.where as FindOptionsWhere<SalesChannel>
|
||||
|
||||
const entitiesIdsWithRelations = await Promise.all(
|
||||
Object.entries(groupedRelations).map(async ([_, rels]) => {
|
||||
return this.findByIds(entitiesIds, {
|
||||
select: ["id"],
|
||||
relations: rels as string[],
|
||||
})
|
||||
})
|
||||
).then(flatten)
|
||||
const entitiesAndRelations = entitiesIdsWithRelations.concat(entities)
|
||||
delete options_?.where?.name
|
||||
delete options_?.where?.description
|
||||
|
||||
const entitiesAndRelationsById = groupBy(entitiesAndRelations, "id")
|
||||
return [
|
||||
Object.values(entitiesAndRelationsById).map((v) => merge({}, ...v)),
|
||||
count,
|
||||
]
|
||||
}
|
||||
let qb = this.createQueryBuilder("sales_channel")
|
||||
.select()
|
||||
.where(options_.where)
|
||||
.andWhere(
|
||||
new Brackets((qb) => {
|
||||
qb.where(`sales_channel.description ILIKE :q`, {
|
||||
q: `%${q}%`,
|
||||
}).orWhere(`sales_channel.name ILIKE :q`, { q: `%${q}%` })
|
||||
})
|
||||
)
|
||||
.skip(options.skip)
|
||||
.take(options.take)
|
||||
|
||||
public async getFreeTextSearchResultsAndCount(
|
||||
q: string,
|
||||
options: ExtendedFindConfig<SalesChannel, Selector<SalesChannel>> = {
|
||||
where: {},
|
||||
}
|
||||
): Promise<[SalesChannel[], number]> {
|
||||
const options_ = { ...options }
|
||||
delete options_?.where?.name
|
||||
delete options_?.where?.description
|
||||
if (options.withDeleted) {
|
||||
qb = qb.withDeleted()
|
||||
}
|
||||
|
||||
let qb = this.createQueryBuilder("sales_channel")
|
||||
.select()
|
||||
.where(options_.where)
|
||||
.andWhere(
|
||||
new Brackets((qb) => {
|
||||
qb.where(`sales_channel.description ILIKE :q`, {
|
||||
q: `%${q}%`,
|
||||
}).orWhere(`sales_channel.name ILIKE :q`, { q: `%${q}%` })
|
||||
})
|
||||
)
|
||||
.skip(options.skip)
|
||||
.take(options.take)
|
||||
return await qb.getManyAndCount()
|
||||
},
|
||||
|
||||
if (options.withDeleted) {
|
||||
qb = qb.withDeleted()
|
||||
}
|
||||
|
||||
return await qb.getManyAndCount()
|
||||
}
|
||||
|
||||
async removeProducts(
|
||||
salesChannelId: string,
|
||||
productIds: string[]
|
||||
): Promise<DeleteResult> {
|
||||
return await this.createQueryBuilder()
|
||||
.delete()
|
||||
.from("product_sales_channel")
|
||||
.where({
|
||||
sales_channel_id: salesChannelId,
|
||||
product_id: In(productIds),
|
||||
})
|
||||
.execute()
|
||||
}
|
||||
|
||||
async addProducts(
|
||||
salesChannelId: string,
|
||||
productIds: string[]
|
||||
): Promise<void> {
|
||||
await this.createQueryBuilder()
|
||||
.insert()
|
||||
.into("product_sales_channel")
|
||||
.values(
|
||||
productIds.map((id) => ({
|
||||
async removeProducts(
|
||||
salesChannelId: string,
|
||||
productIds: string[]
|
||||
): Promise<DeleteResult> {
|
||||
return await this.createQueryBuilder()
|
||||
.delete()
|
||||
.from("product_sales_channel")
|
||||
.where({
|
||||
sales_channel_id: salesChannelId,
|
||||
product_id: id,
|
||||
}))
|
||||
)
|
||||
.orIgnore()
|
||||
.execute()
|
||||
}
|
||||
product_id: In(productIds),
|
||||
})
|
||||
.execute()
|
||||
},
|
||||
|
||||
public async findOneWithRelations(
|
||||
relations: Array<keyof SalesChannel> = [],
|
||||
optionsWithoutRelations: Omit<
|
||||
FindManyOptions<SalesChannel>,
|
||||
"relations"
|
||||
> = {}
|
||||
): Promise<SalesChannel> {
|
||||
// Limit 1
|
||||
optionsWithoutRelations.take = 1
|
||||
|
||||
const [result] = await this.findWithRelations(
|
||||
relations,
|
||||
optionsWithoutRelations
|
||||
)
|
||||
return result[0]
|
||||
}
|
||||
}
|
||||
async addProducts(
|
||||
salesChannelId: string,
|
||||
productIds: string[]
|
||||
): Promise<void> {
|
||||
await this.createQueryBuilder()
|
||||
.insert()
|
||||
.into("product_sales_channel")
|
||||
.values(
|
||||
productIds.map((id) => ({
|
||||
sales_channel_id: salesChannelId,
|
||||
product_id: id,
|
||||
}))
|
||||
)
|
||||
.orIgnore()
|
||||
.execute()
|
||||
},
|
||||
})
|
||||
export default SalesChannelRepository
|
||||
@@ -1,41 +1,43 @@
|
||||
import { EntityRepository, Repository } from "typeorm"
|
||||
import { ShippingMethodTaxLine } from "../models/shipping-method-tax-line"
|
||||
import { ShippingMethodTaxLine } from "../models"
|
||||
import { QueryDeepPartialEntity } from "typeorm/query-builder/QueryPartialEntity"
|
||||
import { dataSource } from "../loaders/database"
|
||||
|
||||
@EntityRepository(ShippingMethodTaxLine)
|
||||
// eslint-disable-next-line max-len
|
||||
export class ShippingMethodTaxLineRepository extends Repository<ShippingMethodTaxLine> {
|
||||
async upsertLines(
|
||||
lines: ShippingMethodTaxLine[]
|
||||
): Promise<ShippingMethodTaxLine[]> {
|
||||
const insertResult = await this.createQueryBuilder()
|
||||
.insert()
|
||||
.values(lines as QueryDeepPartialEntity<ShippingMethodTaxLine>[])
|
||||
.orUpdate({
|
||||
conflict_target: ["shipping_method_id", "code"],
|
||||
overwrite: ["rate", "name", "updated_at"],
|
||||
})
|
||||
.execute()
|
||||
export const ShippingMethodTaxLineRepository = dataSource
|
||||
.getRepository(ShippingMethodTaxLine)
|
||||
.extend({
|
||||
async upsertLines(
|
||||
lines: ShippingMethodTaxLine[]
|
||||
): Promise<ShippingMethodTaxLine[]> {
|
||||
const insertResult = await this.createQueryBuilder()
|
||||
.insert()
|
||||
.values(lines as QueryDeepPartialEntity<ShippingMethodTaxLine>[])
|
||||
.orUpdate({
|
||||
conflict_target: ["shipping_method_id", "code"],
|
||||
overwrite: ["rate", "name", "updated_at"],
|
||||
})
|
||||
.execute()
|
||||
|
||||
return insertResult.identifiers as ShippingMethodTaxLine[]
|
||||
}
|
||||
return insertResult.identifiers as ShippingMethodTaxLine[]
|
||||
},
|
||||
|
||||
async deleteForCart(cartId: string): Promise<void> {
|
||||
const qb = this.createQueryBuilder("line")
|
||||
.select(["line.id"])
|
||||
.innerJoin("shipping_method", "sm", "sm.id = line.shipping_method_id")
|
||||
.innerJoin(
|
||||
"cart",
|
||||
"c",
|
||||
"sm.cart_id = :cartId AND c.completed_at is NULL",
|
||||
{ cartId }
|
||||
)
|
||||
async deleteForCart(cartId: string): Promise<void> {
|
||||
const qb = this.createQueryBuilder("line")
|
||||
.select(["line.id"])
|
||||
.innerJoin("shipping_method", "sm", "sm.id = line.shipping_method_id")
|
||||
.innerJoin(
|
||||
"cart",
|
||||
"c",
|
||||
"sm.cart_id = :cartId AND c.completed_at is NULL",
|
||||
{ cartId }
|
||||
)
|
||||
|
||||
const toDelete = await qb.getMany()
|
||||
const toDelete = await qb.getMany()
|
||||
|
||||
await this.createQueryBuilder()
|
||||
.delete()
|
||||
.whereInIds(toDelete.map((d) => d.id))
|
||||
.execute()
|
||||
}
|
||||
}
|
||||
await this.createQueryBuilder()
|
||||
.delete()
|
||||
.whereInIds(toDelete.map((d) => d.id))
|
||||
.execute()
|
||||
},
|
||||
})
|
||||
|
||||
export default ShippingMethodTaxLineRepository
|
||||
@@ -1,5 +1,5 @@
|
||||
import { EntityRepository, Repository } from "typeorm"
|
||||
import { ShippingMethod } from "../models/shipping-method"
|
||||
import { ShippingMethod } from "../models"
|
||||
import { dataSource } from "../loaders/database"
|
||||
|
||||
@EntityRepository(ShippingMethod)
|
||||
export class ShippingMethodRepository extends Repository<ShippingMethod> {}
|
||||
export const ShippingMethodRepository = dataSource.getRepository(ShippingMethod)
|
||||
export default ShippingMethodRepository
|
||||
@@ -1,6 +1,7 @@
|
||||
import { EntityRepository, Repository } from "typeorm"
|
||||
import { ShippingOptionRequirement } from "../models/shipping-option-requirement"
|
||||
import { ShippingOptionRequirement } from "../models"
|
||||
import { dataSource } from "../loaders/database"
|
||||
|
||||
@EntityRepository(ShippingOptionRequirement)
|
||||
// eslint-disable-next-line max-len
|
||||
export class ShippingOptionRequirementRepository extends Repository<ShippingOptionRequirement> {}
|
||||
export const ShippingOptionRequirementRepository = dataSource.getRepository(
|
||||
ShippingOptionRequirement
|
||||
)
|
||||
export default ShippingOptionRequirementRepository
|
||||
@@ -1,18 +1,22 @@
|
||||
import { EntityRepository, In, Repository } from "typeorm"
|
||||
import { ShippingOption } from "../models/shipping-option"
|
||||
import { ShippingOption } from "../models"
|
||||
import { dataSource } from "../loaders/database"
|
||||
import { In } from "typeorm"
|
||||
|
||||
@EntityRepository(ShippingOption)
|
||||
export class ShippingOptionRepository extends Repository<ShippingOption> {
|
||||
public async upsertShippingProfile(
|
||||
shippingOptionIds: string[],
|
||||
shippingProfileId: string
|
||||
): Promise<ShippingOption[]> {
|
||||
await this.createQueryBuilder()
|
||||
.update(ShippingOption)
|
||||
.set({ profile_id: shippingProfileId })
|
||||
.where({ id: In(shippingOptionIds) })
|
||||
.execute()
|
||||
export const ShippingOptionRepository = dataSource
|
||||
.getRepository(ShippingOption)
|
||||
.extend({
|
||||
async upsertShippingProfile(
|
||||
shippingOptionIds: string[],
|
||||
shippingProfileId: string
|
||||
): Promise<ShippingOption[]> {
|
||||
await this.createQueryBuilder()
|
||||
.update(ShippingOption)
|
||||
.set({ profile_id: shippingProfileId })
|
||||
.where({ id: In(shippingOptionIds) })
|
||||
.execute()
|
||||
|
||||
return this.findByIds(shippingOptionIds)
|
||||
}
|
||||
}
|
||||
return this.findByIds(shippingOptionIds)
|
||||
},
|
||||
})
|
||||
|
||||
export default ShippingOptionRepository
|
||||
Loaded 100 of 275 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user