feat(medusa): Add endpoints specific to DiscountConditions (#1355)

This commit is contained in:
Oliver Windall Juhl authored and GitHub committed 2022-05-13 12:42:23 +02:00
1 parent a69b52e031
commit 9ca45ea492
20 files changed
+1433 -159

No files matched your search

@@ -2,8 +2,11 @@ import {
AdminDiscountsDeleteRes,
AdminDiscountsListRes,
AdminDiscountsRes,
AdminGetDiscountParams,
AdminGetDiscountsParams,
AdminPostDiscountsDiscountConditions,
AdminPostDiscountsDiscountConditionsCondition,
AdminPostDiscountsDiscountConditionsConditionParams,
AdminPostDiscountsDiscountConditionsParams,
AdminPostDiscountsDiscountDynamicCodesReq,
AdminPostDiscountsDiscountReq,
AdminPostDiscountsReq,
@@ -133,6 +136,57 @@ class AdminDiscountsResource extends BaseResource {
const path = `/admin/discounts/${id}/regions/${regionId}`
return this.client.request("DELETE", path, {}, {}, customHeaders)
}
/**
* @description creates a discount condition
*/
createCondition(
discountId: string,
payload: AdminPostDiscountsDiscountConditions,
query: AdminPostDiscountsDiscountConditionsParams = {},
customHeaders: Record<string, any> = {}
): ResponsePromise<AdminDiscountsRes> {
let path = `/admin/discounts/${discountId}/conditions`
if (query) {
const queryString = qs.stringify(query)
path = `/admin/discounts/${discountId}/conditions?${queryString}`
}
return this.client.request("POST", path, payload, {}, customHeaders)
}
/**
* @description Updates a discount condition
*/
updateCondition(
discountId: string,
conditionId: string,
payload: AdminPostDiscountsDiscountConditionsCondition,
query: AdminPostDiscountsDiscountConditionsConditionParams = {},
customHeaders: Record<string, any> = {}
): ResponsePromise<AdminDiscountsRes> {
let path = `/admin/discounts/${discountId}/conditions/${conditionId}`
if (query) {
const queryString = qs.stringify(query)
path = `/admin/discounts/${discountId}/conditions/${conditionId}?${queryString}`
}
return this.client.request("POST", path, payload, {}, customHeaders)
}
/**
* @description Removes a condition from a discount
*/
deleteCondition(
discountId: string,
conditionId: string,
customHeaders: Record<string, any> = {}
): ResponsePromise<AdminDiscountsDeleteRes> {
const path = `/admin/discounts/${discountId}/conditions/${conditionId}`
return this.client.request("DELETE", path, {}, {}, customHeaders)
}
}
export default AdminDiscountsResource
@@ -478,7 +478,14 @@
"created_at": "2021-03-16T21:24:16.872Z",
"updated_at": "2021-03-16T21:24:16.872Z",
"deleted_at": null,
"metadata": null
"metadata": null,
"conditions": [
{
"id": "cnd_01F0YESMVJXKX8XZ6Q9Z6Q6Z6",
"type": "products",
"operator": "in"
}
]
},
"is_disabled": false,
"parent_discount_id": null,
@@ -762,6 +762,43 @@ export const adminHandlers = [
)
}),
rest.post("/admin/discounts/:id/conditions", (req, res, ctx) => {
return res(
ctx.status(200),
ctx.json({
discount: {
...fixtures.get("discount"),
},
})
)
}),
rest.post("/admin/discounts/:id/conditions/:conditionId", (req, res, ctx) => {
return res(
ctx.status(200),
ctx.json({
discount: {
...fixtures.get("discount"),
},
})
)
}),
rest.delete(
"/admin/discounts/:id/conditions/:conditionId",
(req, res, ctx) => {
return res(
ctx.status(200),
ctx.json({
id: req.params.conditionId,
object: "discount-condition",
deleted: true,
discount: fixtures.get("discount"),
})
)
}
),
rest.get("/admin/draft-orders/", (req, res, ctx) => {
return res(
ctx.status(200),
@@ -1,9 +1,11 @@
import {
AdminDiscountsDeleteRes,
AdminDiscountsRes,
AdminPostDiscountsDiscountConditions,
AdminPostDiscountsDiscountConditionsCondition,
AdminPostDiscountsDiscountDynamicCodesReq,
AdminPostDiscountsDiscountReq,
AdminPostDiscountsReq,
AdminPostDiscountsReq
} from "@medusajs/medusa"
import { Response } from "@medusajs/medusa-js"
import { useMutation, UseMutationOptions, useQueryClient } from "react-query"
@@ -115,3 +117,51 @@ export const useAdminDeleteDynamicDiscountCode = (
)
)
}
export const useAdminDiscountCreateCondition = (
discountId: string,
options?: UseMutationOptions<
Response<AdminDiscountsRes>,
Error,
AdminPostDiscountsDiscountConditions
>
) => {
const { client } = useMedusa()
const queryClient = useQueryClient()
return useMutation(
(payload: AdminPostDiscountsDiscountConditions) =>
client.admin.discounts.createCondition(discountId, payload),
buildOptions(queryClient, adminDiscountKeys.detail(discountId), options)
)
}
export const useAdminDiscountUpdateCondition = (
discountId: string,
conditionId: string,
options?: UseMutationOptions<
Response<AdminDiscountsRes>,
Error,
AdminPostDiscountsDiscountConditionsCondition
>
) => {
const { client } = useMedusa()
const queryClient = useQueryClient()
return useMutation(
(payload: AdminPostDiscountsDiscountConditionsCondition) =>
client.admin.discounts.updateCondition(discountId, conditionId, payload),
buildOptions(queryClient, adminDiscountKeys.detail(discountId), options)
)
}
export const useAdminDiscountRemoveCondition = (
discountId: string,
options?: UseMutationOptions<Response<AdminDiscountsDeleteRes>, Error, string>
) => {
const { client } = useMedusa()
const queryClient = useQueryClient()
return useMutation(
(conditionId: string) =>
client.admin.discounts.deleteCondition(discountId, conditionId),
buildOptions(queryClient, adminDiscountKeys.detail(discountId), options)
)
}
@@ -1,3 +1,4 @@
import { DiscountConditionOperator } from "@medusajs/medusa"
import { renderHook } from "@testing-library/react-hooks"
import { fixtures } from "../../../../mocks/data"
import {
@@ -6,7 +7,10 @@ import {
useAdminDeleteDiscount,
useAdminDeleteDynamicDiscountCode,
useAdminDiscountAddRegion,
useAdminDiscountCreateCondition,
useAdminDiscountRemoveCondition,
useAdminDiscountRemoveRegion,
useAdminDiscountUpdateCondition,
useAdminUpdateDiscount,
} from "../../../../src/"
import { createWrapper } from "../../../utils"
@@ -184,3 +188,68 @@ describe("useAdminDeleteDynamicDiscountCode hook", () => {
)
})
})
describe("useAdminDiscountCreateCondition hook", () => {
test("Creates a condition from a discount", async () => {
const id = fixtures.get("discount").id
const conditionId = fixtures.get("discount").rule.conditions[0].id
const { result, waitFor } = renderHook(
() => useAdminDiscountCreateCondition(id),
{
wrapper: createWrapper(),
}
)
result.current.mutate({
operator: DiscountConditionOperator.IN,
products: ["test-product"],
})
await waitFor(() => result.current.isSuccess)
expect(result.current.data.response.status).toEqual(200)
expect(result.current.data.discount).toEqual(fixtures.get("discount"))
})
})
describe("useAdminDiscountUpdateCondition hook", () => {
test("Updates condition for discount", async () => {
const id = fixtures.get("discount").id
const conditionId = fixtures.get("discount").rule.conditions[0].id
const { result, waitFor } = renderHook(
() => useAdminDiscountUpdateCondition(id, conditionId),
{
wrapper: createWrapper(),
}
)
result.current.mutate({ products: ["test-product"] })
await waitFor(() => result.current.isSuccess)
expect(result.current.data.response.status).toEqual(200)
expect(result.current.data.discount).toEqual(fixtures.get("discount"))
})
})
describe("useAdminDiscountRemoveCondition hook", () => {
test("removes a condition from a discount", async () => {
const id = fixtures.get("discount").id
const conditionId = fixtures.get("discount").rule.conditions[0].id
const { result, waitFor } = renderHook(
() => useAdminDiscountRemoveCondition(id),
{
wrapper: createWrapper(),
}
)
result.current.mutate(conditionId)
await waitFor(() => result.current.isSuccess)
expect(result.current.data.response.status).toEqual(200)
expect(result.current.data.id).toEqual(conditionId)
})
})
@@ -0,0 +1,107 @@
import { IsOptional, IsString } from "class-validator"
import { defaultAdminDiscountsFields, defaultAdminDiscountsRelations } from "."
import { Discount } from "../../../../models/discount"
import { DiscountConditionOperator } from "../../../../models/discount-condition"
import { DiscountService } from "../../../../services"
import DiscountConditionService from "../../../../services/discount-condition"
import { AdminUpsertConditionsReq } from "../../../../types/discount"
import { getRetrieveConfig } from "../../../../utils/get-query-config"
import { validator } from "../../../../utils/validator"
/**
* @oas [post] /discounts/{discount_id}/conditions
* operationId: "PostDiscountsDiscountConditions"
* summary: "Creates a DiscountCondition"
* x-authenticated: true
* parameters:
* - (path) discount_id=* {string} The id of the Product.
* - (query) expand {string} (Comma separated) Which fields should be expanded in each product of the result.
* - (query) fields {string} (Comma separated) Which fields should be included in each product of the result.
* description: "Creates a DiscountCondition"
* requestBody:
* content:
* application/json:
* schema:
* properties:
* operator:
* description: Operator of the condition
* type: string
* items:
* properties:
* products:
* type: array
* description: list of products
* product_types:
* type: array
* description: list of product types
* product_collections:
* type: array
* description: list of product collections
* product_tags:
* type: array
* description: list of product tags
* customer_groups:
* type: array
* description: list of customer_groups
* tags:
* - Discount
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* discount:
* $ref: "#/components/schemas/discount"
*/
export default async (req, res) => {
const { discount_id } = req.params
const validatedCondition = await validator(
AdminPostDiscountsDiscountConditions,
req.body
)
const validatedParams = await validator(
AdminPostDiscountsDiscountConditionsParams,
req.query
)
const conditionService: DiscountConditionService = req.scope.resolve(
"discountConditionService"
)
const discountService: DiscountService = req.scope.resolve("discountService")
let discount = await discountService.retrieve(discount_id)
await conditionService.upsertCondition({
...validatedCondition,
rule_id: discount.rule_id,
})
const config = getRetrieveConfig<Discount>(
defaultAdminDiscountsFields,
defaultAdminDiscountsRelations,
validatedParams?.fields?.split(",") as (keyof Discount)[],
validatedParams?.expand?.split(",")
)
discount = await discountService.retrieve(discount.id, config)
res.status(200).json({ discount })
}
export class AdminPostDiscountsDiscountConditions extends AdminUpsertConditionsReq {
@IsString()
operator: DiscountConditionOperator
}
export class AdminPostDiscountsDiscountConditionsParams {
@IsString()
@IsOptional()
expand?: string
@IsString()
@IsOptional()
fields?: string
}
@@ -83,8 +83,6 @@ import { AdminPostDiscountsDiscountParams } from "./update-discount"
export default async (req, res) => {
const validated = await validator(AdminPostDiscountsReq, req.body)
console.log(validated.rule.conditions)
const validatedParams = await validator(
AdminPostDiscountsDiscountParams,
req.query
@@ -0,0 +1,109 @@
import { IsOptional, IsString } from "class-validator"
import { MedusaError } from "medusa-core-utils"
import { defaultAdminDiscountsFields, defaultAdminDiscountsRelations } from "."
import { Discount } from "../../../../models"
import { DiscountService } from "../../../../services"
import DiscountConditionService from "../../../../services/discount-condition"
import { getRetrieveConfig } from "../../../../utils/get-query-config"
import { validator } from "../../../../utils/validator"
/**
* @oas [delete] /discounts/{discount_id}/conditions/{condition_id}
* operationId: "DeleteDiscountsDiscountConditionsCondition"
* summary: "Delete a DiscountCondition"
* description: "Deletes a DiscountCondition"
* x-authenticated: true
* parameters:
* - (path) id=* {string} The id of the Discount
* - (path) condition_id=* {string} The id of the DiscountCondition
* tags:
* - Discount
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* id:
* type: string
* description: The id of the deleted DiscountCondition
* object:
* type: string
* description: The type of the object that was deleted.
* deleted:
* type: boolean
* discount:
* type: object
* description: The Discount to which the condition used to belong
*/
export default async (req, res) => {
const { discount_id, condition_id } = req.params
const validatedParams = await validator(
AdminDeleteDiscountsDiscountConditionsConditionParams,
req.query
)
const conditionService: DiscountConditionService = req.scope.resolve(
"discountConditionService"
)
const condition = await conditionService
.retrieve(condition_id)
.catch(() => void 0)
if (!condition) {
// resolves idempotently in case of non-existing condition
return res.json({
id: condition_id,
object: "discount-condition",
deleted: true,
})
}
const discountService: DiscountService = req.scope.resolve("discountService")
let discount = await discountService.retrieve(discount_id, {
relations: ["rule", "rule.conditions"],
})
const existsOnDiscount = discount.rule.conditions.some(
(c) => c.id === condition_id
)
if (!existsOnDiscount) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`Condition with id ${condition_id} does not belong to Discount with id ${discount_id}`
)
}
await conditionService.delete(condition_id)
const config = getRetrieveConfig<Discount>(
defaultAdminDiscountsFields,
defaultAdminDiscountsRelations,
validatedParams?.fields?.split(",") as (keyof Discount)[],
validatedParams?.expand?.split(",")
)
discount = await discountService.retrieve(discount_id, config)
res.json({
id: condition_id,
object: "discount-condition",
deleted: true,
discount,
})
}
export class AdminDeleteDiscountsDiscountConditionsConditionParams {
@IsString()
@IsOptional()
expand?: string
@IsString()
@IsOptional()
fields?: string
}
@@ -49,6 +49,20 @@ export default (app) => {
middlewares.wrap(require("./remove-region").default)
)
// Discount condition management
route.post(
"/:discount_id/conditions/:condition_id",
middlewares.wrap(require("./update-condition").default)
)
route.post(
"/:discount_id/conditions",
middlewares.wrap(require("./create-condition").default)
)
route.delete(
"/:discount_id/conditions/:condition_id",
middlewares.wrap(require("./delete-condition").default)
)
return app
}
@@ -88,12 +102,15 @@ export type AdminDiscountsListRes = PaginatedResponse & {
}
export * from "./add-region"
export * from "./create-condition"
export * from "./create-discount"
export * from "./create-dynamic-code"
export * from "./delete-condition"
export * from "./delete-discount"
export * from "./delete-dynamic-code"
export * from "./get-discount"
export * from "./get-discount-by-code"
export * from "./list-discounts"
export * from "./remove-region"
export * from "./update-condition"
export * from "./update-discount"
@@ -0,0 +1,109 @@
import { IsOptional, IsString } from "class-validator"
import { defaultAdminDiscountsFields, defaultAdminDiscountsRelations } from "."
import { Discount } from "../../../../models/discount"
import { DiscountService } from "../../../../services"
import DiscountConditionService from "../../../../services/discount-condition"
import { AdminUpsertConditionsReq } from "../../../../types/discount"
import { getRetrieveConfig } from "../../../../utils/get-query-config"
import { validator } from "../../../../utils/validator"
/**
* @oas [post] /discounts/{discount_id}/conditions/{condition_id}
* operationId: "PostDiscountsDiscountConditionsCondition"
* summary: "Updates a DiscountCondition"
* x-authenticated: true
* parameters:
* - (path) discount_id=* {string} The id of the Product.
* - (query) expand {string} (Comma separated) Which fields should be expanded in each product of the result.
* - (query) fields {string} (Comma separated) Which fields should be included in each product of the result.
* description: "Updates a DiscountCondition"
* requestBody:
* content:
* application/json:
* required:
* - id
* schema:
* properties:
* items:
* properties:
* products:
* type: array
* description: list of products
* product_types:
* type: array
* description: list of product types
* product_collections:
* type: array
* description: list of product collections
* product_tags:
* type: array
* description: list of product tags
* customer_groups:
* type: array
* description: list of customer_groups
* tags:
* - Discount
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* discount:
* $ref: "#/components/schemas/discount"
*/
export default async (req, res) => {
const { discount_id, condition_id } = req.params
const validatedCondition = await validator(
AdminPostDiscountsDiscountConditionsCondition,
req.body
)
const validatedParams = await validator(
AdminPostDiscountsDiscountConditionsConditionParams,
req.query
)
const conditionService: DiscountConditionService = req.scope.resolve(
"discountConditionService"
)
const condition = await conditionService.retrieve(condition_id)
const discountService: DiscountService = req.scope.resolve("discountService")
let discount = await discountService.retrieve(discount_id)
const updateObj = {
...validatedCondition,
rule_id: discount.rule_id,
id: condition.id,
}
await conditionService.upsertCondition(updateObj)
const config = getRetrieveConfig<Discount>(
defaultAdminDiscountsFields,
defaultAdminDiscountsRelations,
validatedParams?.fields?.split(",") as (keyof Discount)[],
validatedParams?.expand?.split(",")
)
discount = await discountService.retrieve(discount.id, config)
res.status(200).json({ discount })
}
export class AdminPostDiscountsDiscountConditionsCondition extends AdminUpsertConditionsReq {}
export class AdminPostDiscountsDiscountConditionsConditionParams {
@IsString()
@IsOptional()
expand?: string
@IsString()
@IsOptional()
fields?: string
}
@@ -145,6 +145,10 @@ export class DiscountCondition {
@BeforeInsert()
private beforeInsert() {
if (this.id) {
return
}
const id = ulid()
this.id = `discon_${id}`
}
@@ -6,6 +6,7 @@ import {
Not,
Repository,
} from "typeorm"
import { Discount } from "../models"
import {
DiscountCondition,
DiscountConditionOperator,
@@ -35,6 +36,21 @@ type DiscountConditionResourceType = EntityTarget<
@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
}
getJoinTableResourceIdentifiers(type: string): {
joinTable: string
resourceKey: string
+1 -1
View File
@@ -1,5 +1,5 @@
import { flatten, groupBy, map, merge } from "lodash"
import { EntityRepository, Repository, FindManyOptions } from "typeorm"
import { EntityRepository, FindManyOptions, Repository } from "typeorm"
import { Order } from "../models/order"
@EntityRepository(Order)
@@ -0,0 +1,179 @@
import { MedusaError } from "medusa-core-utils"
import { BaseService } from "medusa-interfaces"
import { EntityManager } from "typeorm"
import { EventBusService } from "."
import { DiscountCondition, DiscountConditionType } from "../models"
import { DiscountConditionRepository } from "../repositories/discount-condition"
import { FindConfig } from "../types/common"
import { UpsertDiscountConditionInput } from "../types/discount"
import { PostgresError } from "../utils/exception-formatter"
/**
* Provides layer to manipulate discount conditions.
* @implements {BaseService}
*/
class DiscountConditionService extends BaseService {
protected readonly manager_: EntityManager
protected readonly discountConditionRepository_: typeof DiscountConditionRepository
protected readonly eventBus_: EventBusService
protected transactionManager_?: EntityManager
constructor({ manager, discountConditionRepository, eventBusService }) {
super()
this.manager_ = manager
this.discountConditionRepository_ = discountConditionRepository
this.eventBus_ = eventBusService
}
withTransaction(transactionManager: EntityManager): DiscountConditionService {
if (!transactionManager) {
return this
}
const cloned = new DiscountConditionService({
manager: transactionManager,
discountConditionRepository: this.discountConditionRepository_,
eventBusService: this.eventBus_,
})
cloned.transactionManager_ = transactionManager
return cloned
}
async retrieve(
conditionId: string,
config?: FindConfig<DiscountCondition>
): Promise<DiscountCondition | never> {
return await this.atomicPhase_(async (manager: EntityManager) => {
const conditionRepo = manager.getCustomRepository(
this.discountConditionRepository_
)
const query = this.buildQuery_({ id: conditionId }, config)
const condition = await conditionRepo.findOne(query)
if (!condition) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`DiscountCondition with id ${conditionId} was not found`
)
}
return condition
})
}
protected static resolveConditionType_(data: UpsertDiscountConditionInput):
| {
type: DiscountConditionType
resource_ids: string[]
}
| undefined {
switch (true) {
case !!data.products?.length:
return {
type: DiscountConditionType.PRODUCTS,
resource_ids: data.products!,
}
case !!data.product_collections?.length:
return {
type: DiscountConditionType.PRODUCT_COLLECTIONS,
resource_ids: data.product_collections!,
}
case !!data.product_types?.length:
return {
type: DiscountConditionType.PRODUCT_TYPES,
resource_ids: data.product_types!,
}
case !!data.product_tags?.length:
return {
type: DiscountConditionType.PRODUCT_TAGS,
resource_ids: data.product_tags!,
}
case !!data.customer_groups?.length:
return {
type: DiscountConditionType.CUSTOMER_GROUPS,
resource_ids: data.customer_groups!,
}
default:
return undefined
}
}
async upsertCondition(data: UpsertDiscountConditionInput): Promise<void> {
let resolvedConditionType
return await this.atomicPhase_(
async (manager: EntityManager) => {
resolvedConditionType =
DiscountConditionService.resolveConditionType_(data)
if (!resolvedConditionType) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Missing one of products, collections, tags, types or customer groups in data`
)
}
const discountConditionRepo: DiscountConditionRepository =
manager.getCustomRepository(this.discountConditionRepository_)
if (data.id) {
return await discountConditionRepo.addConditionResources(
data.id,
resolvedConditionType.resource_ids,
resolvedConditionType.type,
true
)
}
const created = discountConditionRepo.create({
discount_rule_id: data.rule_id,
operator: data.operator,
type: resolvedConditionType.type,
})
const discountCondition = await discountConditionRepo.save(created)
return await discountConditionRepo.addConditionResources(
discountCondition.id,
resolvedConditionType.resource_ids,
resolvedConditionType.type
)
},
async (err: { code: string }) => {
if (err.code === PostgresError.DUPLICATE_ERROR) {
// A unique key constraint failed meaning the combination of
// discount rule id, type, and operator already exists in the db.
throw new MedusaError(
MedusaError.Types.DUPLICATE_ERROR,
`Discount Condition with operator '${data.operator}' and type '${resolvedConditionType?.type}' already exist on a Discount Rule`
)
}
}
)
}
async delete(discountConditionId: string): Promise<DiscountCondition> {
return await this.atomicPhase_(async (manager: EntityManager) => {
const conditionRepo = manager.getCustomRepository(
this.discountConditionRepository_
)
const condition = await conditionRepo.findOne({
where: { id: discountConditionId },
})
if (!condition) {
return Promise.resolve()
}
return await conditionRepo.remove(condition)
})
}
}
export default DiscountConditionService
+22 -106
View File
@@ -11,7 +11,6 @@ import {
} from "."
import { Cart } from "../models/cart"
import { Discount } from "../models/discount"
import { DiscountConditionType } from "../models/discount-condition"
import {
AllocationType as DiscountAllocation,
DiscountRule,
@@ -28,10 +27,10 @@ import {
CreateDynamicDiscountInput,
FilterableDiscountProps,
UpdateDiscountInput,
UpsertDiscountConditionInput,
} from "../types/discount"
import { isFuture, isPast } from "../utils/date-helpers"
import { formatException, PostgresError } from "../utils/exception-formatter"
import { formatException } from "../utils/exception-formatter"
import DiscountConditionService from "./discount-condition"
/**
* Provides layer to manipulate discounts.
@@ -43,6 +42,7 @@ class DiscountService extends BaseService {
private discountRuleRepository_: typeof DiscountRuleRepository
private giftCardRepository_: typeof GiftCardRepository
private discountConditionRepository_: typeof DiscountConditionRepository
private discountConditionService_: DiscountConditionService
private totalsService_: TotalsService
private productService_: ProductService
private regionService_: RegionService
@@ -54,6 +54,7 @@ class DiscountService extends BaseService {
discountRuleRepository,
giftCardRepository,
discountConditionRepository,
discountConditionService,
totalsService,
productService,
regionService,
@@ -77,6 +78,9 @@ class DiscountService extends BaseService {
/** @private @const {DiscountConditionRepository} */
this.discountConditionRepository_ = discountConditionRepository
/** @private @const {DiscountConditionRepository} */
this.discountConditionService_ = discountConditionService
/** @private @const {TotalsService} */
this.totalsService_ = totalsService
@@ -104,6 +108,7 @@ class DiscountService extends BaseService {
discountRuleRepository: this.discountRuleRepository_,
giftCardRepository: this.giftCardRepository_,
discountConditionRepository: this.discountConditionRepository_,
discountConditionService: this.discountConditionService_,
totalsService: this.totalsService_,
productService: this.productService_,
regionService: this.regionService_,
@@ -263,9 +268,13 @@ class DiscountService extends BaseService {
const result = await discountRepo.save(created)
if (conditions?.length) {
for (const cond of conditions) {
await this.upsertDiscountCondition_(result.id, cond)
}
await Promise.all(
conditions.map(async (cond) => {
await this.discountConditionService_
.withTransaction(manager)
.upsertCondition({ rule_id: result.rule_id, ...cond })
})
)
}
return result
@@ -382,9 +391,13 @@ class DiscountService extends BaseService {
}
if (conditions?.length) {
for (const cond of conditions) {
await this.upsertDiscountCondition_(discount.id, cond)
}
await Promise.all(
conditions.map(async (cond) => {
await this.discountConditionService_
.withTransaction(manager)
.upsertCondition({ rule_id: discount.rule_id, ...cond })
})
)
}
if (regions) {
@@ -573,103 +586,6 @@ class DiscountService extends BaseService {
})
}
resolveConditionType_(data: UpsertDiscountConditionInput):
| {
type: DiscountConditionType
resource_ids: string[]
}
| undefined {
switch (true) {
case !!data.products?.length:
return {
type: DiscountConditionType.PRODUCTS,
resource_ids: data.products!,
}
case !!data.product_collections?.length:
return {
type: DiscountConditionType.PRODUCT_COLLECTIONS,
resource_ids: data.product_collections!,
}
case !!data.product_types?.length:
return {
type: DiscountConditionType.PRODUCT_TYPES,
resource_ids: data.product_types!,
}
case !!data.product_tags?.length:
return {
type: DiscountConditionType.PRODUCT_TAGS,
resource_ids: data.product_tags!,
}
case !!data.customer_groups?.length:
return {
type: DiscountConditionType.CUSTOMER_GROUPS,
resource_ids: data.customer_groups!,
}
default:
return undefined
}
}
async upsertDiscountCondition_(
discountId: string,
data: UpsertDiscountConditionInput
): Promise<void> {
const resolvedConditionType = this.resolveConditionType_(data)
const res = this.atomicPhase_(
async (manager) => {
const discountConditionRepo: DiscountConditionRepository =
manager.getCustomRepository(this.discountConditionRepository_)
if (!resolvedConditionType) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Missing one of products, collections, tags, types or customer groups in data`
)
}
if (data.id) {
return await discountConditionRepo.addConditionResources(
data.id,
resolvedConditionType.resource_ids,
resolvedConditionType.type,
true
)
}
const discount = await this.retrieve(discountId, {
relations: ["rule", "rule.conditions"],
})
const created = discountConditionRepo.create({
discount_rule_id: discount.rule_id,
operator: data.operator,
type: resolvedConditionType.type,
})
const discountCondition = await discountConditionRepo.save(created)
return await discountConditionRepo.addConditionResources(
discountCondition.id,
resolvedConditionType.resource_ids,
resolvedConditionType.type
)
},
async (err: { code: string }) => {
if (err.code === PostgresError.DUPLICATE_ERROR) {
// A unique key constraint failed meaning the combination of
// discount rule id, type, and operator already exists in the db.
throw new MedusaError(
MedusaError.Types.DUPLICATE_ERROR,
`Discount Condition with operator '${data.operator}' and type '${resolvedConditionType?.type}' already exist on a Discount Rule`
)
}
}
)
return res
}
async validateDiscountForProduct(
discountRuleId: string,
productId: string | undefined
+9 -6
View File
@@ -672,7 +672,7 @@ class ProductService extends BaseService {
)
if (!product) {
return Promise.resolve()
return
}
await productRepo.softRemove(product)
@@ -1069,22 +1069,25 @@ class ProductService extends BaseService {
const productArray = Array.isArray(products) ? products : [products]
const priceSelectionStrategy =
this.priceSelectionStrategy_.withTransaction(manager)
const priceSelectionStrategy = this.priceSelectionStrategy_.withTransaction(
manager
)
const productsWithPrices = await Promise.all(
productArray.map(async (p) => {
if (p.variants?.length) {
p.variants = await Promise.all(
p.variants.map(async (v) => {
const prices =
await priceSelectionStrategy.calculateVariantPrice(v.id, {
const prices = await priceSelectionStrategy.calculateVariantPrice(
v.id,
{
region_id: regionId,
currency_code: currencyCode,
cart_id: cart_id,
customer_id: customer_id,
include_discount_prices,
})
}
)
return {
...v,
+1
View File
@@ -105,6 +105,7 @@ export class AdminUpsertConditionsReq {
}
export type UpsertDiscountConditionInput = {
rule_id?: string
id?: string
operator?: DiscountConditionOperator
products?: string[]