Feat/add discount condition batch (#2430)

* feat(medusa): Allow to add items to a discount condition by batch + cleanup of discounts and discount conditions end points

* style(medusa): cleanup catch and log

* feat(medusa-react, medusa-js): Add support to add item batch to discount condition

* cleanup

* cleanup

* rename items to resources

* fix(medusa-js): url

* Create fast-suns-repair.md

* update naming

* tests(integration): Update tests to reflect API changes

* feat(medusa): Delete a condition should be idempotent on discount and condition

* revert
This commit is contained in:
Adrien de Peretti
2022-10-13 16:34:06 +02:00
committed by GitHub
parent 143f8543e5
commit 765a2cccda
36 changed files with 1217 additions and 1272 deletions
+7
View File
@@ -0,0 +1,7 @@
---
"@medusajs/medusa-js": patch
"medusa-react": patch
"@medusajs/medusa": patch
---
Feat(medusa, medusa-js, medusa-react): add resources to discount condition by batch
@@ -58,10 +58,6 @@ Object {
} }
`; `;
exports[`/admin/discounts GET /admin/discounts/:id/conditions/:condition_id throws if condition does not belong to discount: DiscountCondition with id test-condition was not found for Discount test-discount-2 1`] = `"Request failed with status code 404"`;
exports[`/admin/discounts GET /admin/discounts/:id/conditions/:condition_id throws if condition does not exist: DiscountCondition with id test-condition was not found 1`] = `"Request failed with status code 404"`;
exports[`/admin/discounts POST /admin/discounts fails if multiple types of resources are provided on update 1`] = ` exports[`/admin/discounts POST /admin/discounts fails if multiple types of resources are provided on update 1`] = `
Object { Object {
"message": "Only one of products, product_types is allowed, Only one of product_types, products is allowed", "message": "Only one of products, product_types is allowed, Only one of product_types, products is allowed",
@@ -77,8 +73,6 @@ Object {
} }
`; `;
exports[`/admin/discounts POST /admin/discounts/:id/conditions fails if more than one condition type is provided: Only one of products, customer_groups is allowed, Only one of customer_groups, products is allowed 1`] = `"Request failed with status code 400"`;
exports[`/admin/discounts POST /admin/discounts/:id/conditions should create a condition 1`] = ` exports[`/admin/discounts POST /admin/discounts/:id/conditions should create a condition 1`] = `
Object { Object {
"code": "TEST", "code": "TEST",
@@ -124,8 +118,6 @@ Object {
} }
`; `;
exports[`/admin/discounts POST /admin/discounts/:id/conditions throws if discount does not exist: Discount with id does-not-exist was not found 1`] = `"Request failed with status code 404"`;
exports[`/admin/discounts POST /admin/discounts/:id/conditions/:condition_id should update a condition 1`] = ` exports[`/admin/discounts POST /admin/discounts/:id/conditions/:condition_id should update a condition 1`] = `
Object { Object {
"code": "TEST", "code": "TEST",
@@ -197,7 +189,3 @@ Object {
"valid_duration": null, "valid_duration": null,
} }
`; `;
exports[`/admin/discounts POST /admin/discounts/:id/conditions/:condition_id throws if condition does not exist: DiscountCondition with id does-not-exist was not found for Discount test-discount 1`] = `"Request failed with status code 404"`;
exports[`/admin/discounts POST /admin/discounts/:id/conditions/:condition_id throws if discount does not exist: Discount with id does-not-exist was not found 1`] = `"Request failed with status code 404"`;
File diff suppressed because it is too large Load Diff
@@ -7,6 +7,8 @@ import {
AdminGetDiscountsParams, AdminGetDiscountsParams,
AdminPostDiscountsDiscountConditions, AdminPostDiscountsDiscountConditions,
AdminPostDiscountsDiscountConditionsCondition, AdminPostDiscountsDiscountConditionsCondition,
AdminPostDiscountsDiscountConditionsConditionBatchParams,
AdminPostDiscountsDiscountConditionsConditionBatchReq,
AdminPostDiscountsDiscountConditionsConditionParams, AdminPostDiscountsDiscountConditionsConditionParams,
AdminPostDiscountsDiscountConditionsParams, AdminPostDiscountsDiscountConditionsParams,
AdminPostDiscountsDiscountDynamicCodesReq, AdminPostDiscountsDiscountDynamicCodesReq,
@@ -121,7 +123,7 @@ class AdminDiscountsResource extends BaseResource {
if (query) { if (query) {
const queryString = qs.stringify(query) const queryString = qs.stringify(query)
path = `/admin/discounts?${queryString}` path += `?${queryString}`
} }
return this.client.request("GET", path, undefined, {}, customHeaders) return this.client.request("GET", path, undefined, {}, customHeaders)
@@ -152,7 +154,7 @@ class AdminDiscountsResource extends BaseResource {
if (query) { if (query) {
const queryString = qs.stringify(query) const queryString = qs.stringify(query)
path = `/admin/discounts/${discountId}/conditions?${queryString}` path += `?${queryString}`
} }
return this.client.request("POST", path, payload, {}, customHeaders) return this.client.request("POST", path, payload, {}, customHeaders)
@@ -172,7 +174,7 @@ class AdminDiscountsResource extends BaseResource {
if (query) { if (query) {
const queryString = qs.stringify(query) const queryString = qs.stringify(query)
path = `/admin/discounts/${discountId}/conditions/${conditionId}?${queryString}` path += `?${queryString}`
} }
return this.client.request("POST", path, payload, {}, customHeaders) return this.client.request("POST", path, payload, {}, customHeaders)
@@ -203,11 +205,31 @@ class AdminDiscountsResource extends BaseResource {
if (query) { if (query) {
const queryString = qs.stringify(query) const queryString = qs.stringify(query)
path = `/admin/discounts/${discountId}/conditions/${conditionId}?${queryString}` path += `?${queryString}`
} }
return this.client.request("GET", path, undefined, {}, customHeaders) return this.client.request("GET", path, undefined, {}, customHeaders)
} }
/**
* @description Add a batch of items to a discount condition
*/
addConditionResourceBatch(
discountId: string,
conditionId: string,
payload: AdminPostDiscountsDiscountConditionsConditionBatchReq,
query?: AdminPostDiscountsDiscountConditionsConditionBatchParams,
customHeaders: Record<string, any> = {}
): ResponsePromise<AdminDiscountsRes> {
let path = `/admin/discounts/${discountId}/conditions/${conditionId}/batch`
if (query) {
const queryString = qs.stringify(query)
path += `?${queryString}`
}
return this.client.request("POST", path, payload, {}, customHeaders)
}
} }
export default AdminDiscountsResource export default AdminDiscountsResource
@@ -912,6 +912,33 @@ export const adminHandlers = [
} }
), ),
rest.post(
"/admin/discounts/:id/conditions/:conditionId/batch",
(req, res, ctx) => {
return res(
ctx.status(200),
ctx.json({
discount: {
...fixtures.get("discount"),
rule: {
...fixtures.get("discount").rule,
conditions: [
{
...fixtures.get("discount").rule.conditions[0],
products: [
...(fixtures.get("discount").rule.conditions[0]?.products ??
[]),
...(req.body as any).resources,
],
},
],
},
},
})
)
}
),
rest.get("/admin/draft-orders/", (req, res, ctx) => { rest.get("/admin/draft-orders/", (req, res, ctx) => {
return res( return res(
ctx.status(200), ctx.status(200),
@@ -3,16 +3,42 @@ import {
AdminDiscountsRes, AdminDiscountsRes,
AdminPostDiscountsDiscountConditions, AdminPostDiscountsDiscountConditions,
AdminPostDiscountsDiscountConditionsCondition, AdminPostDiscountsDiscountConditionsCondition,
AdminPostDiscountsDiscountConditionsConditionBatchParams,
AdminPostDiscountsDiscountConditionsConditionBatchReq,
AdminPostDiscountsDiscountDynamicCodesReq, AdminPostDiscountsDiscountDynamicCodesReq,
AdminPostDiscountsDiscountReq, AdminPostDiscountsDiscountReq,
AdminPostDiscountsReq AdminPostDiscountsReq,
} from "@medusajs/medusa" } from "@medusajs/medusa"
import { Response } from "@medusajs/medusa-js" import { Response } from "@medusajs/medusa-js"
import { useMutation, UseMutationOptions, useQueryClient } from "react-query" import { useMutation, UseMutationOptions, useQueryClient } from "react-query"
import { useMedusa } from "../../../contexts/medusa" import { useMedusa } from "../../../contexts"
import { buildOptions } from "../../utils/buildOptions" import { buildOptions } from "../../utils/buildOptions"
import { adminDiscountKeys } from "./queries" import { adminDiscountKeys } from "./queries"
export const useAdminAddDiscountConditionResourceBatch = (
discountId: string,
conditionId: string,
query?: AdminPostDiscountsDiscountConditionsConditionBatchParams,
options?: UseMutationOptions<
Response<AdminDiscountsRes>,
Error,
AdminPostDiscountsDiscountConditionsConditionBatchReq
>
) => {
const { client } = useMedusa()
const queryClient = useQueryClient()
return useMutation(
(payload: AdminPostDiscountsDiscountConditionsConditionBatchReq) =>
client.admin.discounts.addConditionResourceBatch(
discountId,
conditionId,
payload,
query
),
buildOptions(queryClient, adminDiscountKeys.detail(discountId), options)
)
}
export const useAdminCreateDiscount = ( export const useAdminCreateDiscount = (
options?: UseMutationOptions< options?: UseMutationOptions<
Response<AdminDiscountsRes>, Response<AdminDiscountsRes>,
@@ -10,7 +10,7 @@ import { Response } from "@medusajs/medusa-js"
import { useQuery } from "react-query" import { useQuery } from "react-query"
import { useMedusa } from "../../../contexts" import { useMedusa } from "../../../contexts"
import { UseQueryOptionsWrapper } from "../../../types" import { UseQueryOptionsWrapper } from "../../../types"
import { queryKeysFactory } from "../../utils/index" import { queryKeysFactory } from "../../utils"
const ADMIN_DISCOUNTS_QUERY_KEY = `admin_discounts` as const const ADMIN_DISCOUNTS_QUERY_KEY = `admin_discounts` as const
@@ -2,6 +2,7 @@ import { DiscountConditionOperator } from "@medusajs/medusa"
import { renderHook } from "@testing-library/react-hooks" import { renderHook } from "@testing-library/react-hooks"
import { fixtures } from "../../../../mocks/data" import { fixtures } from "../../../../mocks/data"
import { import {
useAdminAddDiscountConditionResourceBatch,
useAdminCreateDiscount, useAdminCreateDiscount,
useAdminCreateDynamicDiscountCode, useAdminCreateDynamicDiscountCode,
useAdminDeleteDiscount, useAdminDeleteDiscount,
@@ -15,6 +16,49 @@ import {
} from "../../../../src/" } from "../../../../src/"
import { createWrapper } from "../../../utils" import { createWrapper } from "../../../utils"
describe("useAdminAddDiscountConditionResourceBatch hook", () => {
test("add items to a discount condition and return the discount", async () => {
const resources = [
{
id: fixtures.get("product").id,
},
]
const discountId = fixtures.get("discount").id
const conditionId = fixtures.get("discount").rule.conditions[0].id
const { result, waitFor } = renderHook(
() => useAdminAddDiscountConditionResourceBatch(discountId, conditionId),
{
wrapper: createWrapper(),
}
)
result.current.mutate({ resources })
await waitFor(() => result.current.isSuccess)
expect(result.current.data.response.status).toEqual(200)
expect(result.current.data.discount).toEqual(
expect.objectContaining({
...fixtures.get("discount"),
rule: {
...fixtures.get("discount").rule,
conditions: [
{
...fixtures.get("discount").rule.conditions[0],
products: [
...(fixtures.get("discount").rule.conditions[0]?.products ??
[]),
{ id: fixtures.get("product").id },
],
},
],
},
})
)
})
})
describe("useAdminCreateDiscount hook", () => { describe("useAdminCreateDiscount hook", () => {
test("creates a discount and returns it", async () => { test("creates a discount and returns it", async () => {
const discount = { const discount = {
@@ -0,0 +1,33 @@
import { MedusaError } from "medusa-core-utils"
import { DiscountConditionService, DiscountService } from "../../../services"
export async function doesConditionBelongToDiscount(req, res, next) {
try {
const { discount_id, condition_id } = req.params
const conditionService: DiscountConditionService = req.scope.resolve(
"discountConditionService"
)
const discountService: DiscountService =
req.scope.resolve("discountService")
const discount = await discountService.retrieve(discount_id, {
select: ["id", "rule_id"],
})
const condition = await conditionService.retrieve(condition_id, {
select: ["id", "discount_rule_id"],
})
if (condition.discount_rule_id !== discount.rule_id) {
return next(
new MedusaError(
MedusaError.Types.INVALID_DATA,
`Condition with id ${condition_id} does not belong to Discount with id ${discount_id}`
)
)
}
next()
} catch (e) {
next(e)
}
}
@@ -5,6 +5,7 @@ import { default as wrap } from "./await-middleware"
export { getRequestedBatchJob } from "./batch-job/get-requested-batch-job" export { getRequestedBatchJob } from "./batch-job/get-requested-batch-job"
export { canAccessBatchJob } from "./batch-job/can-access-batch-job" export { canAccessBatchJob } from "./batch-job/can-access-batch-job"
export { doesConditionBelongToDiscount } from "./discount/does-condition-belong-to-discount"
export { transformQuery } from "./transform-query" export { transformQuery } from "./transform-query"
export { transformBody } from "./transform-body" export { transformBody } from "./transform-body"
@@ -0,0 +1,131 @@
import DiscountConditionService from "../../../../services/discount-condition"
import { Request, Response } from "express"
import { EntityManager } from "typeorm"
import { DiscountService } from "../../../../services"
import {
DiscountConditionMapTypeToProperty,
UpsertDiscountConditionInput,
} from "../../../../types/discount"
import { IsArray } from "class-validator"
import { FindParams } from "../../../../types/common"
/**
* @oas [post] /discounts/{discount_id}/conditions/{condition_id}/batch
* operationId: "PostDiscountsDiscountConditionsConditionBatch"
* summary: "Add a batch of resources to a discount condition"
* description: "Add a batch of resources to a discount condition."
* x-authenticated: true
* parameters:
* - (path) discount_id=* {string} The ID of the Product.
* - (path) condition_id=* {string} The ID of the condition on which to add the item.
* - (query) expand {string} (Comma separated) Which fields should be expanded in each discount of the result.
* - (query) fields {string} (Comma separated) Which fields should be included in each discount of the result.
* requestBody:
* content:
* application/json:
* schema:
* required:
* - resources
* properties:
* resources:
* description: The resources to be added to the discount condition
* type: array
* items:
* required:
* - id
* properties:
* id:
* description: The id of the item
* type: string
* x-codeSamples:
* - lang: JavaScript
* label: JS Client
* source: |
* import Medusa from "@medusajs/medusa-js"
* import { DiscountConditionOperator } from "@medusajs/medusa"
* const medusa = new Medusa({ baseUrl: MEDUSA_BACKEND_URL, maxRetries: 3 })
* // must be previously logged in or use api token
* medusa.admin.discounts.addConditionResourceBatch(discount_id, condition_id, {
* resources: [{ id: item_id }]
* })
* .then(({ discount }) => {
* console.log(discount.id);
* });
* - lang: Shell
* label: cURL
* source: |
* curl --location --request POST 'https://medusa-url.com/admin/discounts/{id}/conditions/{condition_id}/batch' \
* --header 'Authorization: Bearer {api_token}' \
* --header 'Content-Type: application/json' \
* --data-raw '{
* "resources": [{ "id": "item_id" }]
* }'
* security:
* - api_token: []
* - cookie_auth: []
* tags:
* - Discount Condition
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* discount:
* $ref: "#/components/schemas/discount"
* "400":
* $ref: "#/components/responses/400_error"
* "401":
* $ref: "#/components/responses/unauthorized"
* "404":
* $ref: "#/components/responses/not_found_error"
* "409":
* $ref: "#/components/responses/invalid_state_error"
* "422":
* $ref: "#/components/responses/invalid_request_error"
* "500":
* $ref: "#/components/responses/500_error"
*/
export default async (req: Request, res: Response) => {
const { discount_id, condition_id } = req.params
const validatedBody =
req.validatedBody as AdminPostDiscountsDiscountConditionsConditionBatchReq
const conditionService: DiscountConditionService = req.scope.resolve(
"discountConditionService"
)
const manager: EntityManager = req.scope.resolve("manager")
const condition = await conditionService.retrieve(condition_id, {
select: ["id", "type", "discount_rule_id"],
})
const updateObj: UpsertDiscountConditionInput = {
id: condition_id,
rule_id: condition.discount_rule_id,
[DiscountConditionMapTypeToProperty[condition.type]]:
validatedBody.resources,
}
await manager.transaction(async (transactionManager) => {
await conditionService
.withTransaction(transactionManager)
.upsertCondition(updateObj, false)
})
const discountService: DiscountService = req.scope.resolve("discountService")
const discount = await discountService.retrieve(
discount_id,
req.retrieveConfig
)
res.status(200).json({ discount })
}
export class AdminPostDiscountsDiscountConditionsConditionBatchReq {
@IsArray()
resources: { id: string }[]
}
export class AdminPostDiscountsDiscountConditionsConditionBatchParams extends FindParams {}
@@ -1,13 +1,12 @@
import { Discount, DiscountConditionOperator } from "../../../../models" import { Request, Response } from "express"
import { IsOptional, IsString } from "class-validator" import { DiscountConditionOperator } from "../../../../models"
import { defaultAdminDiscountsFields, defaultAdminDiscountsRelations } from "." import { IsString } from "class-validator"
import { AdminUpsertConditionsReq } from "../../../../types/discount" import { AdminUpsertConditionsReq } from "../../../../types/discount"
import DiscountConditionService from "../../../../services/discount-condition" import DiscountConditionService from "../../../../services/discount-condition"
import { DiscountService } from "../../../../services" import { DiscountService } from "../../../../services"
import { EntityManager } from "typeorm" import { EntityManager } from "typeorm"
import { getRetrieveConfig } from "../../../../utils/get-query-config" import { FindParams } from "../../../../types/common"
import { validator } from "../../../../utils/validator"
/** /**
* @oas [post] /discounts/{discount_id}/conditions * @oas [post] /discounts/{discount_id}/conditions
@@ -105,19 +104,8 @@ import { validator } from "../../../../utils/validator"
* "500": * "500":
* $ref: "#/components/responses/500_error" * $ref: "#/components/responses/500_error"
*/ */
export default async (req, res) => { export default async (req: Request, res: Response) => {
const { discount_id } = req.params 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( const conditionService: DiscountConditionService = req.scope.resolve(
"discountConditionService" "discountConditionService"
) )
@@ -130,19 +118,12 @@ export default async (req, res) => {
return await conditionService return await conditionService
.withTransaction(transactionManager) .withTransaction(transactionManager)
.upsertCondition({ .upsertCondition({
...validatedCondition, ...(req.validatedBody as AdminPostDiscountsDiscountConditions),
rule_id: discount.rule_id, rule_id: discount.rule_id,
}) })
}) })
const config = getRetrieveConfig<Discount>( discount = await discountService.retrieve(discount.id, req.retrieveConfig)
defaultAdminDiscountsFields,
defaultAdminDiscountsRelations,
validatedParams?.fields?.split(",") as (keyof Discount)[],
validatedParams?.expand?.split(",")
)
discount = await discountService.retrieve(discount.id, config)
res.status(200).json({ discount }) res.status(200).json({ discount })
} }
@@ -153,12 +134,4 @@ export class AdminPostDiscountsDiscountConditions extends AdminUpsertConditionsR
operator: DiscountConditionOperator operator: DiscountConditionOperator
} }
export class AdminPostDiscountsDiscountConditionsParams { export class AdminPostDiscountsDiscountConditionsParams extends FindParams {}
@IsString()
@IsOptional()
expand?: string
@IsString()
@IsOptional()
fields?: string
}
@@ -1,6 +1,5 @@
import { import {
AllocationType, AllocationType,
Discount,
DiscountConditionOperator, DiscountConditionOperator,
DiscountRuleType, DiscountRuleType,
} from "../../../../models" } from "../../../../models"
@@ -17,17 +16,14 @@ import {
IsString, IsString,
ValidateNested, ValidateNested,
} from "class-validator" } from "class-validator"
import { defaultAdminDiscountsFields, defaultAdminDiscountsRelations } from "."
import { AdminPostDiscountsDiscountParams } from "./update-discount"
import { AdminUpsertConditionsReq } from "../../../../types/discount" import { AdminUpsertConditionsReq } from "../../../../types/discount"
import DiscountService from "../../../../services/discount" import DiscountService from "../../../../services/discount"
import { EntityManager } from "typeorm" import { EntityManager } from "typeorm"
import { IsGreaterThan } from "../../../../utils/validators/greater-than" import { IsGreaterThan } from "../../../../utils/validators/greater-than"
import { IsISO8601Duration } from "../../../../utils/validators/iso8601-duration" import { IsISO8601Duration } from "../../../../utils/validators/iso8601-duration"
import { Type } from "class-transformer" import { Type } from "class-transformer"
import { getRetrieveConfig } from "../../../../utils/get-query-config" import { Request, Response } from "express"
import { validator } from "../../../../utils/validator" import { FindParams } from "../../../../types/common"
/** /**
* @oas [post] /discounts * @oas [post] /discounts
@@ -202,32 +198,21 @@ import { validator } from "../../../../utils/validator"
* $ref: "#/components/responses/500_error" * $ref: "#/components/responses/500_error"
*/ */
export default async (req, res) => { export default async (req: Request, res: Response) => {
const validated = await validator(AdminPostDiscountsReq, req.body)
const validatedParams = await validator(
AdminPostDiscountsDiscountParams,
req.query
)
const discountService: DiscountService = req.scope.resolve("discountService") const discountService: DiscountService = req.scope.resolve("discountService")
const manager: EntityManager = req.scope.resolve("manager") const manager: EntityManager = req.scope.resolve("manager")
const created = await manager.transaction(async (transactionManager) => { const created = await manager.transaction(async (transactionManager) => {
return await discountService return await discountService
.withTransaction(transactionManager) .withTransaction(transactionManager)
.create(validated) .create(req.validatedBody as AdminPostDiscountsReq)
}) })
const config = getRetrieveConfig<Discount>( const discount = await discountService.retrieve(
defaultAdminDiscountsFields, created.id,
defaultAdminDiscountsRelations, req.retrieveConfig
validatedParams?.fields?.split(",") as (keyof Discount)[],
validatedParams?.expand?.split(",")
) )
const discount = await discountService.retrieve(created.id, config)
res.status(200).json({ discount }) res.status(200).json({ discount })
} }
@@ -309,12 +294,4 @@ export class AdminCreateCondition extends AdminUpsertConditionsReq {
operator: DiscountConditionOperator operator: DiscountConditionOperator
} }
export class AdminPostDiscountsParams { export class AdminPostDiscountsParams extends FindParams {}
@IsArray()
@IsOptional()
expand?: string[]
@IsArray()
@IsOptional()
fields?: string[]
}
@@ -1,3 +1,4 @@
import { Request, Response } from "express"
import { import {
IsNotEmpty, IsNotEmpty,
IsNumber, IsNumber,
@@ -9,7 +10,6 @@ import { defaultAdminDiscountsFields, defaultAdminDiscountsRelations } from "."
import DiscountService from "../../../../services/discount" import DiscountService from "../../../../services/discount"
import { EntityManager } from "typeorm" import { EntityManager } from "typeorm"
import { validator } from "../../../../utils/validator"
/** /**
* @oas [post] /discounts/{id}/dynamic-codes * @oas [post] /discounts/{id}/dynamic-codes
@@ -72,20 +72,18 @@ import { validator } from "../../../../utils/validator"
* "500": * "500":
* $ref: "#/components/responses/500_error" * $ref: "#/components/responses/500_error"
*/ */
export default async (req, res) => { export default async (req: Request, res: Response) => {
const { discount_id } = req.params const { discount_id } = req.params
const validated = await validator(
AdminPostDiscountsDiscountDynamicCodesReq,
req.body
)
const discountService: DiscountService = req.scope.resolve("discountService") const discountService: DiscountService = req.scope.resolve("discountService")
const manager: EntityManager = req.scope.resolve("manager") const manager: EntityManager = req.scope.resolve("manager")
const created = await manager.transaction(async (transactionManager) => { const created = await manager.transaction(async (transactionManager) => {
return await discountService return await discountService
.withTransaction(transactionManager) .withTransaction(transactionManager)
.createDynamicCode(discount_id, validated) .createDynamicCode(
discount_id,
req.validatedBody as AdminPostDiscountsDiscountDynamicCodesReq
)
}) })
const discount = await discountService.retrieve(created.id, { const discount = await discountService.retrieve(created.id, {
@@ -1,13 +1,8 @@
import { IsOptional, IsString } from "class-validator"
import { defaultAdminDiscountsFields, defaultAdminDiscountsRelations } from "."
import { Discount } from "../../../../models"
import DiscountConditionService from "../../../../services/discount-condition" import DiscountConditionService from "../../../../services/discount-condition"
import { DiscountService } from "../../../../services" import { DiscountService } from "../../../../services"
import { EntityManager } from "typeorm" import { EntityManager } from "typeorm"
import { MedusaError } from "medusa-core-utils" import { MedusaError } from "medusa-core-utils"
import { getRetrieveConfig } from "../../../../utils/get-query-config" import { FindParams } from "../../../../types/common"
import { validator } from "../../../../utils/validator"
/** /**
* @oas [delete] /discounts/{discount_id}/conditions/{condition_id} * @oas [delete] /discounts/{discount_id}/conditions/{condition_id}
@@ -78,39 +73,34 @@ import { validator } from "../../../../utils/validator"
export default async (req, res) => { export default async (req, res) => {
const { discount_id, condition_id } = req.params const { discount_id, condition_id } = req.params
const validatedParams = await validator(
AdminDeleteDiscountsDiscountConditionsConditionParams,
req.query
)
const conditionService: DiscountConditionService = req.scope.resolve( const conditionService: DiscountConditionService = req.scope.resolve(
"discountConditionService" "discountConditionService"
) )
const discountService: DiscountService = req.scope.resolve("discountService")
const condition = await conditionService const condition = await conditionService
.retrieve(condition_id) .retrieve(condition_id)
.catch(() => void 0) .catch(() => void 0)
if (!condition) { if (!condition) {
const discount = await discountService.retrieve(
discount_id,
req.retrieveConfig
)
// resolves idempotently in case of non-existing condition // resolves idempotently in case of non-existing condition
return res.json({ return res.json({
id: condition_id, id: condition_id,
object: "discount-condition", object: "discount-condition",
deleted: true, deleted: true,
discount,
}) })
} }
const discountService: DiscountService = req.scope.resolve("discountService")
let discount = await discountService.retrieve(discount_id, { let discount = await discountService.retrieve(discount_id, {
relations: ["rule", "rule.conditions"], select: ["id", "rule_id"],
}) })
const existsOnDiscount = discount.rule.conditions.some( if (condition.discount_rule_id !== discount.rule_id) {
(c) => c.id === condition_id
)
if (!existsOnDiscount) {
throw new MedusaError( throw new MedusaError(
MedusaError.Types.NOT_FOUND, MedusaError.Types.NOT_FOUND,
`Condition with id ${condition_id} does not belong to Discount with id ${discount_id}` `Condition with id ${condition_id} does not belong to Discount with id ${discount_id}`
@@ -124,14 +114,7 @@ export default async (req, res) => {
.delete(condition_id) .delete(condition_id)
}) })
const config = getRetrieveConfig<Discount>( discount = await discountService.retrieve(discount_id, req.retrieveConfig)
defaultAdminDiscountsFields,
defaultAdminDiscountsRelations,
validatedParams?.fields?.split(",") as (keyof Discount)[],
validatedParams?.expand?.split(",")
)
discount = await discountService.retrieve(discount_id, config)
res.json({ res.json({
id: condition_id, id: condition_id,
@@ -141,12 +124,4 @@ export default async (req, res) => {
}) })
} }
export class AdminDeleteDiscountsDiscountConditionsConditionParams { export class AdminDeleteDiscountsDiscountConditionsConditionParams extends FindParams {}
@IsString()
@IsOptional()
expand?: string
@IsString()
@IsOptional()
fields?: string
}
@@ -1,15 +1,6 @@
import { IsOptional, IsString } from "class-validator" import { Request, Response } from "express"
import {
defaultAdminDiscountConditionFields,
defaultAdminDiscountConditionRelations,
} from "."
import { DiscountCondition } from "../../../../models"
import DiscountConditionService from "../../../../services/discount-condition" import DiscountConditionService from "../../../../services/discount-condition"
import { DiscountService } from "../../../../services" import { FindParams } from "../../../../types/common"
import { MedusaError } from "medusa-core-utils"
import { getRetrieveConfig } from "../../../../utils/get-query-config"
import { validator } from "../../../../utils/validator"
/** /**
* @oas [get] /discounts/{discount_id}/conditions/{condition_id} * @oas [get] /discounts/{discount_id}/conditions/{condition_id}
@@ -66,37 +57,8 @@ import { validator } from "../../../../utils/validator"
* $ref: "#/components/responses/500_error" * $ref: "#/components/responses/500_error"
*/ */
export default async (req, res) => { export default async (req: Request, res: Response) => {
const { discount_id, condition_id } = req.params const { condition_id } = req.params
const validatedParams = await validator(
AdminGetDiscountsDiscountConditionsConditionParams,
req.query
)
const discountService: DiscountService = req.scope.resolve("discountService")
const 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}`
)
}
const config = getRetrieveConfig<DiscountCondition>(
defaultAdminDiscountConditionFields,
defaultAdminDiscountConditionRelations,
validatedParams?.fields?.split(",") as (keyof DiscountCondition)[],
validatedParams?.expand?.split(",")
)
const conditionService: DiscountConditionService = req.scope.resolve( const conditionService: DiscountConditionService = req.scope.resolve(
"discountConditionService" "discountConditionService"
@@ -104,18 +66,10 @@ export default async (req, res) => {
const discountCondition = await conditionService.retrieve( const discountCondition = await conditionService.retrieve(
condition_id, condition_id,
config req.retrieveConfig
) )
res.status(200).json({ discount_condition: discountCondition }) res.status(200).json({ discount_condition: discountCondition })
} }
export class AdminGetDiscountsDiscountConditionsConditionParams { export class AdminGetDiscountsDiscountConditionsConditionParams extends FindParams {}
@IsString()
@IsOptional()
expand?: string
@IsString()
@IsOptional()
fields?: string
}
@@ -1,10 +1,7 @@
import { IsOptional, IsString } from "class-validator" import { Request, Response } from "express"
import { defaultAdminDiscountsFields, defaultAdminDiscountsRelations } from "."
import { Discount } from "../../../../models"
import DiscountService from "../../../../services/discount" import DiscountService from "../../../../services/discount"
import { getRetrieveConfig } from "../../../../utils/get-query-config" import { FindParams } from "../../../../types/common"
import { validator } from "../../../../utils/validator"
/** /**
* @oas [get] /discounts/code/{code} * @oas [get] /discounts/code/{code}
* operationId: "GetDiscountsDiscountCode" * operationId: "GetDiscountsDiscountCode"
@@ -58,33 +55,16 @@ import { validator } from "../../../../utils/validator"
* "500": * "500":
* $ref: "#/components/responses/500_error" * $ref: "#/components/responses/500_error"
*/ */
export default async (req, res) => { export default async (req: Request, res: Response) => {
const { code } = req.params const { code } = req.params
const validated = await validator(
AdminGetDiscountsDiscountCodeParams,
req.query
)
const config = getRetrieveConfig<Discount>(
defaultAdminDiscountsFields,
defaultAdminDiscountsRelations,
validated?.fields?.split(",") as (keyof Discount)[],
validated?.expand?.split(",")
)
const discountService: DiscountService = req.scope.resolve("discountService") const discountService: DiscountService = req.scope.resolve("discountService")
const discount = await discountService.retrieveByCode(code, config) const discount = await discountService.retrieveByCode(
code,
req.retrieveConfig
)
res.status(200).json({ discount }) res.status(200).json({ discount })
} }
export class AdminGetDiscountsDiscountCodeParams { export class AdminGetDiscountsDiscountCodeParams extends FindParams {}
@IsOptional()
@IsString()
expand?: string
@IsOptional()
@IsString()
fields?: string
}
@@ -1,10 +1,7 @@
import { IsOptional, IsString } from "class-validator"
import { defaultAdminDiscountsFields, defaultAdminDiscountsRelations } from "."
import { Discount } from "../../../.."
import DiscountService from "../../../../services/discount" import DiscountService from "../../../../services/discount"
import { getRetrieveConfig } from "../../../../utils/get-query-config" import { Request, Response } from "express"
import { validator } from "../../../../utils/validator" import { FindParams } from "../../../../types/common"
/** /**
* @oas [get] /discounts/{id} * @oas [get] /discounts/{id}
* operationId: "GetDiscountsDiscount" * operationId: "GetDiscountsDiscount"
@@ -58,30 +55,13 @@ import { validator } from "../../../../utils/validator"
* "500": * "500":
* $ref: "#/components/responses/500_error" * $ref: "#/components/responses/500_error"
*/ */
export default async (req, res) => { export default async (req: Request, res: Response) => {
const { discount_id } = req.params const { discount_id } = req.params
const validated = await validator(AdminGetDiscountParams, req.query)
const config = getRetrieveConfig<Discount>(
defaultAdminDiscountsFields,
defaultAdminDiscountsRelations,
validated?.fields?.split(",") as (keyof Discount)[],
validated?.expand?.split(",")
)
const discountService: DiscountService = req.scope.resolve("discountService") const discountService: DiscountService = req.scope.resolve("discountService")
const data = await discountService.retrieve(discount_id, config) const data = await discountService.retrieve(discount_id, req.retrieveConfig)
res.status(200).json({ discount: data }) res.status(200).json({ discount: data })
} }
export class AdminGetDiscountParams { export class AdminGetDiscountParams extends FindParams {}
@IsOptional()
@IsString()
expand?: string
@IsOptional()
@IsString()
fields?: string
}
@@ -3,26 +3,89 @@ import "reflect-metadata"
import { Discount } from "../../../.." import { Discount } from "../../../.."
import { DiscountCondition } from "../../../../models" import { DiscountCondition } from "../../../../models"
import { DeleteResponse, PaginatedResponse } from "../../../../types/common" import { DeleteResponse, PaginatedResponse } from "../../../../types/common"
import middlewares from "../../../middlewares" import middlewares, {
doesConditionBelongToDiscount,
transformBody,
transformQuery,
} from "../../../middlewares"
import {
AdminPostDiscountsDiscountConditionsConditionBatchParams,
AdminPostDiscountsDiscountConditionsConditionBatchReq,
} from "./add-resources-to-condition-batch"
import {
AdminPostDiscountsDiscountConditionsCondition,
AdminPostDiscountsDiscountConditionsConditionParams,
} from "./update-condition"
import {
AdminPostDiscountsDiscountConditions,
AdminPostDiscountsDiscountConditionsParams,
} from "./create-condition"
import { AdminPostDiscountsDiscountDynamicCodesReq } from "./create-dynamic-code"
import {
AdminPostDiscountsDiscountParams,
AdminPostDiscountsDiscountReq,
} from "./update-discount"
import {
AdminPostDiscountsParams,
AdminPostDiscountsReq,
} from "./create-discount"
import { AdminGetDiscountsParams } from "./list-discounts"
import { AdminGetDiscountsDiscountConditionsConditionParams } from "./get-condition"
import { AdminDeleteDiscountsDiscountConditionsConditionParams } from "./delete-condition"
import { AdminGetDiscountsDiscountCodeParams } from "./get-discount-by-code"
import { AdminGetDiscountParams } from "./get-discount"
const route = Router() const route = Router()
export default (app) => { export default (app) => {
app.use("/discounts", route) app.use("/discounts", route)
route.get("/", middlewares.wrap(require("./list-discounts").default)) route.get(
route.post("/", middlewares.wrap(require("./create-discount").default)) "/",
transformQuery(AdminGetDiscountsParams, {
defaultFields: defaultAdminDiscountsFields,
defaultRelations: defaultAdminDiscountsRelations,
isList: true,
}),
middlewares.wrap(require("./list-discounts").default)
)
route.post(
"/",
transformQuery(AdminPostDiscountsParams, {
defaultFields: defaultAdminDiscountsFields,
defaultRelations: defaultAdminDiscountsRelations,
isList: false,
}),
transformBody(AdminPostDiscountsReq),
middlewares.wrap(require("./create-discount").default)
)
route.get( route.get(
"/:discount_id", "/:discount_id",
transformQuery(AdminGetDiscountParams, {
defaultFields: defaultAdminDiscountsFields,
defaultRelations: defaultAdminDiscountsRelations,
isList: false,
}),
middlewares.wrap(require("./get-discount").default) middlewares.wrap(require("./get-discount").default)
) )
route.get( route.get(
"/code/:code", "/code/:code",
transformQuery(AdminGetDiscountsDiscountCodeParams, {
defaultFields: defaultAdminDiscountsFields,
defaultRelations: defaultAdminDiscountsRelations,
isList: false,
}),
middlewares.wrap(require("./get-discount-by-code").default) middlewares.wrap(require("./get-discount-by-code").default)
) )
route.post( route.post(
"/:discount_id", "/:discount_id",
transformQuery(AdminPostDiscountsDiscountParams, {
defaultFields: defaultAdminDiscountsFields,
defaultRelations: defaultAdminDiscountsRelations,
isList: false,
}),
transformBody(AdminPostDiscountsDiscountReq),
middlewares.wrap(require("./update-discount").default) middlewares.wrap(require("./update-discount").default)
) )
route.delete( route.delete(
@@ -33,6 +96,7 @@ export default (app) => {
// Dynamic codes // Dynamic codes
route.post( route.post(
"/:discount_id/dynamic-codes", "/:discount_id/dynamic-codes",
transformBody(AdminPostDiscountsDiscountDynamicCodesReq),
middlewares.wrap(require("./create-dynamic-code").default) middlewares.wrap(require("./create-dynamic-code").default)
) )
route.delete( route.delete(
@@ -51,23 +115,64 @@ export default (app) => {
) )
// Discount condition management // Discount condition management
route.get(
"/:discount_id/conditions/:condition_id",
middlewares.wrap(require("./get-condition").default)
)
route.post(
"/:discount_id/conditions/:condition_id",
middlewares.wrap(require("./update-condition").default)
)
route.post( route.post(
"/:discount_id/conditions", "/:discount_id/conditions",
transformQuery(AdminPostDiscountsDiscountConditionsParams, {
defaultFields: defaultAdminDiscountsFields,
defaultRelations: defaultAdminDiscountsRelations,
isList: false,
}),
transformBody(AdminPostDiscountsDiscountConditions),
middlewares.wrap(require("./create-condition").default) middlewares.wrap(require("./create-condition").default)
) )
route.delete( route.delete(
"/:discount_id/conditions/:condition_id", "/:discount_id/conditions/:condition_id",
transformQuery(AdminDeleteDiscountsDiscountConditionsConditionParams, {
defaultFields: defaultAdminDiscountsFields,
defaultRelations: defaultAdminDiscountsRelations,
isList: false,
}),
middlewares.wrap(require("./delete-condition").default) middlewares.wrap(require("./delete-condition").default)
) )
const conditionRouter = Router({ mergeParams: true })
route.use(
"/:discount_id/conditions/:condition_id",
doesConditionBelongToDiscount,
conditionRouter
)
conditionRouter.get(
"/",
transformQuery(AdminGetDiscountsDiscountConditionsConditionParams, {
defaultFields: defaultAdminDiscountConditionFields,
defaultRelations: defaultAdminDiscountConditionRelations,
isList: false,
}),
middlewares.wrap(require("./get-condition").default)
)
conditionRouter.post(
"/",
transformQuery(AdminPostDiscountsDiscountConditionsConditionParams, {
defaultFields: defaultAdminDiscountsFields,
defaultRelations: defaultAdminDiscountsRelations,
isList: false,
}),
transformBody(AdminPostDiscountsDiscountConditionsCondition),
middlewares.wrap(require("./update-condition").default)
)
conditionRouter.post(
"/batch",
transformQuery(AdminPostDiscountsDiscountConditionsConditionBatchParams, {
defaultFields: defaultAdminDiscountsFields,
defaultRelations: defaultAdminDiscountsRelations,
isList: false,
}),
transformBody(AdminPostDiscountsDiscountConditionsConditionBatchReq),
middlewares.wrap(require("./add-resources-to-condition-batch").default)
)
return app return app
} }
@@ -129,3 +234,4 @@ export * from "./list-discounts"
export * from "./remove-region" export * from "./remove-region"
export * from "./update-condition" export * from "./update-condition"
export * from "./update-discount" export * from "./update-discount"
export * from "./add-resources-to-condition-batch"
@@ -1,20 +1,15 @@
import { import {
IsBoolean, IsBoolean,
IsInt,
IsOptional, IsOptional,
IsString, IsString,
ValidateNested, ValidateNested,
} from "class-validator" } from "class-validator"
import { Transform, Type } from "class-transformer" import { Transform, Type } from "class-transformer"
import _, { pickBy } from "lodash"
import { defaultAdminDiscountsFields, defaultAdminDiscountsRelations } from "."
import { AdminGetDiscountsDiscountRuleParams } from "../../../../types/discount" import { AdminGetDiscountsDiscountRuleParams } from "../../../../types/discount"
import { Discount } from "../../../.." import { extendedFindParamsMixin } from "../../../../types/common"
import DiscountService from "../../../../services/discount" import { Request, Response } from "express"
import { FindConfig } from "../../../../types/common" import { DiscountService } from "../../../../services"
import { validator } from "../../../../utils/validator"
import { isDefined } from "../../../../utils"
/** /**
* @oas [get] /discounts * @oas [get] /discounts
@@ -97,38 +92,29 @@ import { isDefined } from "../../../../utils"
* "500": * "500":
* $ref: "#/components/responses/500_error" * $ref: "#/components/responses/500_error"
*/ */
export default async (req, res) => { export default async (req: Request, res: Response) => {
const validated = await validator(AdminGetDiscountsParams, req.query)
const discountService: DiscountService = req.scope.resolve("discountService") const discountService: DiscountService = req.scope.resolve("discountService")
const relations = const { filterableFields, listConfig } = req
validated.expand?.split(",") ?? defaultAdminDiscountsRelations const { skip, take } = listConfig
const listConfig: FindConfig<Discount> = {
select: defaultAdminDiscountsFields,
relations,
skip: validated.offset,
take: validated.limit,
order: { created_at: "DESC" },
}
const filterableFields = _.omit(validated, ["limit", "offset", "expand"])
const [discounts, count] = await discountService.listAndCount( const [discounts, count] = await discountService.listAndCount(
pickBy(filterableFields, (val) => isDefined(val)), filterableFields,
listConfig listConfig
) )
res.status(200).json({ res.status(200).json({
discounts, discounts,
count, count,
offset: validated.offset, offset: skip,
limit: validated.limit, limit: take,
}) })
} }
export class AdminGetDiscountsParams { export class AdminGetDiscountsParams extends extendedFindParamsMixin({
limit: 20,
offset: 0,
}) {
@ValidateNested() @ValidateNested()
@IsOptional() @IsOptional()
@Type(() => AdminGetDiscountsDiscountRuleParams) @Type(() => AdminGetDiscountsDiscountRuleParams)
@@ -147,18 +133,4 @@ export class AdminGetDiscountsParams {
@IsOptional() @IsOptional()
@Transform(({ value }) => value === "true") @Transform(({ value }) => value === "true")
is_disabled?: boolean is_disabled?: boolean
@IsInt()
@IsOptional()
@Type(() => Number)
limit = 20
@IsInt()
@IsOptional()
@Type(() => Number)
offset = 0
@IsString()
@IsOptional()
expand?: string
} }
@@ -1,13 +1,9 @@
import { IsOptional, IsString } from "class-validator" import { Request, Response } from "express"
import { defaultAdminDiscountsFields, defaultAdminDiscountsRelations } from "."
import { AdminUpsertConditionsReq } from "../../../../types/discount" import { AdminUpsertConditionsReq } from "../../../../types/discount"
import { Discount } from "../../../../models"
import DiscountConditionService from "../../../../services/discount-condition" import DiscountConditionService from "../../../../services/discount-condition"
import { DiscountService } from "../../../../services" import { DiscountService } from "../../../../services"
import { EntityManager } from "typeorm" import { EntityManager } from "typeorm"
import { getRetrieveConfig } from "../../../../utils/get-query-config" import { FindParams } from "../../../../types/common"
import { validator } from "../../../../utils/validator"
/** /**
* @oas [post] /discounts/{discount_id}/conditions/{condition_id} * @oas [post] /discounts/{discount_id}/conditions/{condition_id}
@@ -104,19 +100,9 @@ import { validator } from "../../../../utils/validator"
* $ref: "#/components/responses/500_error" * $ref: "#/components/responses/500_error"
*/ */
export default async (req, res) => { export default async (req: Request, res: Response) => {
const { discount_id, condition_id } = req.params 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( const conditionService: DiscountConditionService = req.scope.resolve(
"discountConditionService" "discountConditionService"
) )
@@ -128,7 +114,7 @@ export default async (req, res) => {
let discount = await discountService.retrieve(discount_id) let discount = await discountService.retrieve(discount_id)
const updateObj = { const updateObj = {
...validatedCondition, ...(req.validatedBody as AdminPostDiscountsDiscountConditionsCondition),
rule_id: discount.rule_id, rule_id: discount.rule_id,
id: condition.id, id: condition.id,
} }
@@ -140,14 +126,7 @@ export default async (req, res) => {
.upsertCondition(updateObj) .upsertCondition(updateObj)
}) })
const config = getRetrieveConfig<Discount>( discount = await discountService.retrieve(discount.id, req.retrieveConfig)
defaultAdminDiscountsFields,
defaultAdminDiscountsRelations,
validatedParams?.fields?.split(",") as (keyof Discount)[],
validatedParams?.expand?.split(",")
)
discount = await discountService.retrieve(discount.id, config)
res.status(200).json({ discount }) res.status(200).json({ discount })
} }
@@ -155,12 +134,4 @@ export default async (req, res) => {
// eslint-disable-next-line max-len // eslint-disable-next-line max-len
export class AdminPostDiscountsDiscountConditionsCondition extends AdminUpsertConditionsReq {} export class AdminPostDiscountsDiscountConditionsCondition extends AdminUpsertConditionsReq {}
export class AdminPostDiscountsDiscountConditionsConditionParams { export class AdminPostDiscountsDiscountConditionsConditionParams extends FindParams {}
@IsString()
@IsOptional()
expand?: string
@IsString()
@IsOptional()
fields?: string
}
@@ -1,4 +1,5 @@
import { Discount, DiscountConditionOperator } from "../../../../models" import { Request, Response } from "express"
import { AllocationType, DiscountConditionOperator } from "../../../../models"
import { import {
IsArray, IsArray,
IsBoolean, IsBoolean,
@@ -12,17 +13,14 @@ import {
IsString, IsString,
ValidateNested, ValidateNested,
} from "class-validator" } from "class-validator"
import { defaultAdminDiscountsFields, defaultAdminDiscountsRelations } from "."
import { AdminUpsertConditionsReq } from "../../../../types/discount" import { AdminUpsertConditionsReq } from "../../../../types/discount"
import { AllocationType } from "../../../../models"
import DiscountService from "../../../../services/discount" import DiscountService from "../../../../services/discount"
import { EntityManager } from "typeorm" import { EntityManager } from "typeorm"
import { IsGreaterThan } from "../../../../utils/validators/greater-than" import { IsGreaterThan } from "../../../../utils/validators/greater-than"
import { IsISO8601Duration } from "../../../../utils/validators/iso8601-duration" import { IsISO8601Duration } from "../../../../utils/validators/iso8601-duration"
import { Type } from "class-transformer" import { Type } from "class-transformer"
import { getRetrieveConfig } from "../../../../utils/get-query-config" import { FindParams } from "../../../../types/common"
import { validator } from "../../../../utils/validator"
/** /**
* @oas [post] /discounts/{id} * @oas [post] /discounts/{id}
@@ -176,34 +174,23 @@ import { validator } from "../../../../utils/validator"
* "500": * "500":
* $ref: "#/components/responses/500_error" * $ref: "#/components/responses/500_error"
*/ */
export default async (req, res) => { export default async (req: Request, res: Response) => {
const { discount_id } = req.params const { discount_id } = req.params
const validated = await validator(AdminPostDiscountsDiscountReq, req.body)
const validatedParams = await validator(
AdminPostDiscountsDiscountParams,
req.query
)
const discountService: DiscountService = req.scope.resolve("discountService") const discountService: DiscountService = req.scope.resolve("discountService")
const manager: EntityManager = req.scope.resolve("manager") const manager: EntityManager = req.scope.resolve("manager")
await manager.transaction(async (transactionManager) => { await manager.transaction(async (transactionManager) => {
return await discountService return await discountService
.withTransaction(transactionManager) .withTransaction(transactionManager)
.update(discount_id, validated) .update(discount_id, req.validatedBody as AdminPostDiscountsDiscountReq)
}) })
const config = getRetrieveConfig<Discount>( const discount = await discountService.retrieve(
defaultAdminDiscountsFields, discount_id,
defaultAdminDiscountsRelations, req.retrieveConfig
validatedParams?.fields?.split(",") as (keyof Discount)[],
validatedParams?.expand?.split(",")
) )
const discount = await discountService.retrieve(discount_id, config)
res.status(200).json({ discount }) res.status(200).json({ discount })
} }
@@ -287,12 +274,4 @@ export class AdminUpsertCondition extends AdminUpsertConditionsReq {
operator: DiscountConditionOperator operator: DiscountConditionOperator
} }
export class AdminPostDiscountsDiscountParams { export class AdminPostDiscountsDiscountParams extends FindParams {}
@IsString()
@IsOptional()
expand?: string
@IsString()
@IsOptional()
fields?: string
}
@@ -7,7 +7,7 @@ import {
SelectQueryBuilder, SelectQueryBuilder,
} from "typeorm" } from "typeorm"
import { CustomerGroup } from "../models" import { CustomerGroup } from "../models"
import { ExtendedFindConfig, Writable } from "../types/common" import { ExtendedFindConfig, Selector } from "../types/common"
import { import {
getGroupedRelations, getGroupedRelations,
mergeEntitiesWithRelations, mergeEntitiesWithRelations,
@@ -16,7 +16,7 @@ import {
} from "../utils/repository" } from "../utils/repository"
export type DefaultWithoutRelations = Omit< export type DefaultWithoutRelations = Omit<
ExtendedFindConfig<CustomerGroup, Partial<Writable<CustomerGroup>>>, ExtendedFindConfig<CustomerGroup, Selector<CustomerGroup>>,
"relations" "relations"
> >
@@ -17,6 +17,7 @@ import { DiscountConditionProduct } from "../models/discount-condition-product"
import { DiscountConditionProductCollection } from "../models/discount-condition-product-collection" import { DiscountConditionProductCollection } from "../models/discount-condition-product-collection"
import { DiscountConditionProductTag } from "../models/discount-condition-product-tag" import { DiscountConditionProductTag } from "../models/discount-condition-product-tag"
import { DiscountConditionProductType } from "../models/discount-condition-product-type" import { DiscountConditionProductType } from "../models/discount-condition-product-type"
import { isString } from "../utils"
export enum DiscountConditionJoinTableForeignKey { export enum DiscountConditionJoinTableForeignKey {
PRODUCT_ID = "product_id", PRODUCT_ID = "product_id",
@@ -150,7 +151,7 @@ export class DiscountConditionRepository extends Repository<DiscountCondition> {
async addConditionResources( async addConditionResources(
conditionId: string, conditionId: string,
resourceIds: string[], resourceIds: (string | { id: string })[],
type: DiscountConditionType, type: DiscountConditionType,
overrideExisting = false overrideExisting = false
): Promise< ): Promise<
@@ -171,7 +172,10 @@ export class DiscountConditionRepository extends Repository<DiscountCondition> {
return Promise.resolve([]) return Promise.resolve([])
} }
toInsert = resourceIds.map((rId) => ({ const idsToInsert = resourceIds.map((rId): string => {
return isString(rId) ? rId : rId.id
})
toInsert = idsToInsert.map((rId) => ({
condition_id: conditionId, condition_id: conditionId,
[joinTableForeignKey]: rId, [joinTableForeignKey]: rId,
})) }))
@@ -189,7 +193,7 @@ export class DiscountConditionRepository extends Repository<DiscountCondition> {
.from(conditionTable) .from(conditionTable)
.where({ .where({
condition_id: conditionId, condition_id: conditionId,
[joinTableForeignKey]: Not(In(resourceIds)), [joinTableForeignKey]: Not(In(idsToInsert)),
}) })
.execute() .execute()
} }
@@ -1,13 +1,13 @@
import { EntityRepository, Repository } from "typeorm" import { EntityRepository, Repository } from "typeorm"
import { ProductCollection } from "../models" import { ProductCollection } from "../models"
import { ExtendedFindConfig } from "../types/common" import { ExtendedFindConfig, Selector } from "../types/common"
@EntityRepository(ProductCollection) @EntityRepository(ProductCollection)
// eslint-disable-next-line max-len // eslint-disable-next-line max-len
export class ProductCollectionRepository extends Repository<ProductCollection> { export class ProductCollectionRepository extends Repository<ProductCollection> {
async findAndCountByDiscountConditionId( async findAndCountByDiscountConditionId(
conditionId: string, conditionId: string,
query: ExtendedFindConfig<ProductCollection, Partial<ProductCollection>> query: ExtendedFindConfig<ProductCollection, Selector<ProductCollection>>
): Promise<[ProductCollection[], number]> { ): Promise<[ProductCollection[], number]> {
const qb = this.createQueryBuilder("pc") const qb = this.createQueryBuilder("pc")
@@ -1,6 +1,6 @@
import { EntityRepository, In, Repository } from "typeorm" import { EntityRepository, In, Repository } from "typeorm"
import { ProductTag } from "../models/product-tag" import { ProductTag } from "../models/product-tag"
import { ExtendedFindConfig } from "../types/common" import { ExtendedFindConfig, Selector } from "../types/common"
type UpsertTagsInput = (Partial<ProductTag> & { type UpsertTagsInput = (Partial<ProductTag> & {
value: string value: string
@@ -67,7 +67,7 @@ export class ProductTagRepository extends Repository<ProductTag> {
async findAndCountByDiscountConditionId( async findAndCountByDiscountConditionId(
conditionId: string, conditionId: string,
query: ExtendedFindConfig<ProductTag, Partial<ProductTag>> query: ExtendedFindConfig<ProductTag, Selector<ProductTag>>
) { ) {
const qb = this.createQueryBuilder("pt") const qb = this.createQueryBuilder("pt")
@@ -1,6 +1,6 @@
import { EntityRepository, Repository } from "typeorm" import { EntityRepository, Repository } from "typeorm"
import { ProductType } from "../models/product-type" import { ProductType } from "../models/product-type"
import { ExtendedFindConfig } from "../types/common" import { ExtendedFindConfig, Selector } from "../types/common"
type UpsertTypeInput = Partial<ProductType> & { type UpsertTypeInput = Partial<ProductType> & {
value: string value: string
@@ -29,7 +29,7 @@ export class ProductTypeRepository extends Repository<ProductType> {
async findAndCountByDiscountConditionId( async findAndCountByDiscountConditionId(
conditionId: string, conditionId: string,
query: ExtendedFindConfig<ProductType, Partial<ProductType>> query: ExtendedFindConfig<ProductType, Selector<ProductType>>
): Promise<[ProductType[], number]> { ): Promise<[ProductType[], number]> {
const qb = this.createQueryBuilder("pt") const qb = this.createQueryBuilder("pt")
@@ -6,6 +6,7 @@ export const discounts = {
code: "Something", code: "Something",
is_dynamic: true, is_dynamic: true,
rule: { rule: {
id: IdMap.getId("dynamic_rule"),
type: "percentage", type: "percentage",
allocation: "total", allocation: "total",
value: 10, value: 10,
@@ -16,6 +17,7 @@ export const discounts = {
id: IdMap.getId("total10"), id: IdMap.getId("total10"),
code: "10%OFF", code: "10%OFF",
rule: { rule: {
id: IdMap.getId("total10_rule"),
type: "percentage", type: "percentage",
allocation: "total", allocation: "total",
value: 10, value: 10,
@@ -26,6 +28,7 @@ export const discounts = {
id: IdMap.getId("item10Percent"), id: IdMap.getId("item10Percent"),
code: "MEDUSA", code: "MEDUSA",
rule: { rule: {
id: IdMap.getId("item10Percent_rule"),
type: "percentage", type: "percentage",
allocation: "item", allocation: "item",
value: 10, value: 10,
@@ -36,6 +39,7 @@ export const discounts = {
id: IdMap.getId("total10Fixed"), id: IdMap.getId("total10Fixed"),
code: "MEDUSA", code: "MEDUSA",
rule: { rule: {
id: IdMap.getId("total10Fixed_rule"),
type: "fixed", type: "fixed",
allocation: "total", allocation: "total",
value: 10, value: 10,
@@ -46,6 +50,7 @@ export const discounts = {
id: IdMap.getId("item9Fixed"), id: IdMap.getId("item9Fixed"),
code: "MEDUSA", code: "MEDUSA",
rule: { rule: {
id: IdMap.getId("item9Fixed_rule"),
type: "fixed", type: "fixed",
allocation: "item", allocation: "item",
value: 9, value: 9,
@@ -56,6 +61,7 @@ export const discounts = {
id: IdMap.getId("item2Fixed"), id: IdMap.getId("item2Fixed"),
code: "MEDUSA", code: "MEDUSA",
rule: { rule: {
id: IdMap.getId("item2Fixed_rule"),
type: "fixed", type: "fixed",
allocation: "item", allocation: "item",
value: 2, value: 2,
@@ -66,6 +72,7 @@ export const discounts = {
id: IdMap.getId("item10FixedNoVariants"), id: IdMap.getId("item10FixedNoVariants"),
code: "MEDUSA", code: "MEDUSA",
rule: { rule: {
id: IdMap.getId("item10FixedNoVariants_rule"),
type: "fixed", type: "fixed",
allocation: "item", allocation: "item",
value: 10, value: 10,
@@ -77,6 +84,7 @@ export const discounts = {
code: "MEDUSA", code: "MEDUSA",
ends_at: new Date("December 17, 1995 03:24:00"), ends_at: new Date("December 17, 1995 03:24:00"),
rule: { rule: {
id: IdMap.getId("expired_rule"),
type: "fixed", type: "fixed",
allocation: "item", allocation: "item",
value: 10, value: 10,
@@ -87,6 +95,7 @@ export const discounts = {
id: IdMap.getId("freeshipping"), id: IdMap.getId("freeshipping"),
code: "FREESHIPPING", code: "FREESHIPPING",
rule: { rule: {
id: IdMap.getId("freeshipping_rule"),
type: "free_shipping", type: "free_shipping",
allocation: "total", allocation: "total",
value: 10, value: 10,
@@ -97,6 +106,7 @@ export const discounts = {
id: IdMap.getId("us-discount"), id: IdMap.getId("us-discount"),
code: "US10", code: "US10",
rule: { rule: {
id: IdMap.getId("us-discount_rule"),
type: "free_shipping", type: "free_shipping",
allocation: "total", allocation: "total",
value: 10, value: 10,
@@ -106,6 +116,7 @@ export const discounts = {
alreadyExists: { alreadyExists: {
code: "ALREADYEXISTS", code: "ALREADYEXISTS",
rule: { rule: {
id: IdMap.getId("ALREADYEXISTS_rule"),
type: "percentage", type: "percentage",
allocation: "total", allocation: "total",
value: 20, value: 20,
@@ -115,7 +126,7 @@ export const discounts = {
} }
export const DiscountServiceMock = { export const DiscountServiceMock = {
withTransaction: function() { withTransaction: function () {
return this return this
}, },
create: jest.fn().mockImplementation((data) => { create: jest.fn().mockImplementation((data) => {
@@ -6,7 +6,7 @@ import {
CustomerGroupRepository, CustomerGroupRepository,
FindWithoutRelationsOptions, FindWithoutRelationsOptions,
} from "../repositories/customer-group" } from "../repositories/customer-group"
import { FindConfig } from "../types/common" import { FindConfig, Selector } from "../types/common"
import { CustomerGroupUpdate } from "../types/customer-groups" import { CustomerGroupUpdate } from "../types/customer-groups"
import { import {
buildQuery, buildQuery,
@@ -196,7 +196,7 @@ class CustomerGroupService extends TransactionBaseService {
* @return the result of the find operation * @return the result of the find operation
*/ */
async list( async list(
selector: Partial<CustomerGroup> & { selector: Selector<CustomerGroup> & {
q?: string q?: string
discount_condition_id?: string discount_condition_id?: string
} = {}, } = {},
@@ -214,7 +214,7 @@ class CustomerGroupService extends TransactionBaseService {
* @return the result of the find operation * @return the result of the find operation
*/ */
async listAndCount( async listAndCount(
selector: Partial<CustomerGroup> & { selector: Selector<CustomerGroup> & {
q?: string q?: string
discount_condition_id?: string discount_condition_id?: string
} = {}, } = {},
@@ -72,7 +72,7 @@ class DiscountConditionService extends TransactionBaseService {
protected static resolveConditionType_(data: UpsertDiscountConditionInput): protected static resolveConditionType_(data: UpsertDiscountConditionInput):
| { | {
type: DiscountConditionType type: DiscountConditionType
resource_ids: string[] resource_ids: (string | { id: string })[]
} }
| undefined { | undefined {
switch (true) { switch (true) {
@@ -107,7 +107,8 @@ class DiscountConditionService extends TransactionBaseService {
} }
async upsertCondition( async upsertCondition(
data: UpsertDiscountConditionInput data: UpsertDiscountConditionInput,
overrideExisting: boolean = true
): Promise< ): Promise<
( (
| DiscountConditionProduct | DiscountConditionProduct
@@ -146,7 +147,7 @@ class DiscountConditionService extends TransactionBaseService {
data.id, data.id,
resolvedConditionType.resource_ids, resolvedConditionType.resource_ids,
resolvedConditionType.type, resolvedConditionType.type,
true overrideExisting
) )
} }
+1 -1
View File
@@ -255,7 +255,7 @@ class DiscountService extends TransactionBaseService {
if (!discount) { if (!discount) {
throw new MedusaError( throw new MedusaError(
MedusaError.Types.NOT_FOUND, MedusaError.Types.NOT_FOUND,
`Discount with ${discountId} was not found` `Discount with id ${discountId} was not found`
) )
} }
+1
View File
@@ -8,6 +8,7 @@ export { default as CustomShippingOptionService } from "./custom-shipping-option
export { default as CustomerGroupService } from "./customer-group" export { default as CustomerGroupService } from "./customer-group"
export { default as CustomerService } from "./customer" export { default as CustomerService } from "./customer"
export { default as DiscountService } from "./discount" export { default as DiscountService } from "./discount"
export { default as DiscountConditionService } from "./discount-condition"
export { default as DraftOrderService } from "./draft-order" export { default as DraftOrderService } from "./draft-order"
export { default as EventBusService } from "./event-bus" export { default as EventBusService } from "./event-bus"
export { default as FulfillmentProviderService } from "./fulfillment-provider" export { default as FulfillmentProviderService } from "./fulfillment-provider"
@@ -4,7 +4,7 @@ import { TransactionBaseService } from "../interfaces"
import { ProductCollection } from "../models" import { ProductCollection } from "../models"
import { ProductRepository } from "../repositories/product" import { ProductRepository } from "../repositories/product"
import { ProductCollectionRepository } from "../repositories/product-collection" import { ProductCollectionRepository } from "../repositories/product-collection"
import { FindConfig } from "../types/common" import { FindConfig, Selector } from "../types/common"
import { import {
CreateProductCollection, CreateProductCollection,
UpdateProductCollection, UpdateProductCollection,
@@ -219,7 +219,7 @@ class ProductCollectionService extends TransactionBaseService {
* @return the result of the find operation * @return the result of the find operation
*/ */
async list( async list(
selector: Partial<ProductCollection> & { selector: Selector<ProductCollection> & {
q?: string q?: string
discount_condition_id?: string discount_condition_id?: string
} = {}, } = {},
@@ -236,7 +236,7 @@ class ProductCollectionService extends TransactionBaseService {
* @return the result of the find operation * @return the result of the find operation
*/ */
async listAndCount( async listAndCount(
selector: Partial<ProductCollection> & { selector: Selector<ProductCollection> & {
q?: string q?: string
discount_condition_id?: string discount_condition_id?: string
} = {}, } = {},
+3 -3
View File
@@ -2,7 +2,7 @@ import { MedusaError } from "medusa-core-utils"
import { EntityManager, ILike } from "typeorm" import { EntityManager, ILike } from "typeorm"
import { ProductTag } from "../models" import { ProductTag } from "../models"
import { ProductTagRepository } from "../repositories/product-tag" import { ProductTagRepository } from "../repositories/product-tag"
import { FindConfig } from "../types/common" import { FindConfig, Selector } from "../types/common"
import { TransactionBaseService } from "../interfaces" import { TransactionBaseService } from "../interfaces"
import { buildQuery, isString } from "../utils" import { buildQuery, isString } from "../utils"
@@ -69,7 +69,7 @@ class ProductTagService extends TransactionBaseService {
* @return the result of the find operation * @return the result of the find operation
*/ */
async list( async list(
selector: Partial<ProductTag> & { selector: Selector<ProductTag> & {
q?: string q?: string
discount_condition_id?: string discount_condition_id?: string
} = {}, } = {},
@@ -86,7 +86,7 @@ class ProductTagService extends TransactionBaseService {
* @return the result of the find operation * @return the result of the find operation
*/ */
async listAndCount( async listAndCount(
selector: Partial<ProductTag> & { selector: Selector<ProductTag> & {
q?: string q?: string
discount_condition_id?: string discount_condition_id?: string
} = {}, } = {},
+3 -3
View File
@@ -2,7 +2,7 @@ import { MedusaError } from "medusa-core-utils"
import { EntityManager, ILike } from "typeorm" import { EntityManager, ILike } from "typeorm"
import { ProductType } from "../models" import { ProductType } from "../models"
import { ProductTypeRepository } from "../repositories/product-type" import { ProductTypeRepository } from "../repositories/product-type"
import { FindConfig } from "../types/common" import { FindConfig, Selector } from "../types/common"
import { TransactionBaseService } from "../interfaces" import { TransactionBaseService } from "../interfaces"
import { buildQuery, isString } from "../utils" import { buildQuery, isString } from "../utils"
@@ -53,7 +53,7 @@ class ProductTypeService extends TransactionBaseService {
* @return the result of the find operation * @return the result of the find operation
*/ */
async list( async list(
selector: Partial<ProductType> & { selector: Selector<ProductType> & {
q?: string q?: string
discount_condition_id?: string discount_condition_id?: string
} = {}, } = {},
@@ -70,7 +70,7 @@ class ProductTypeService extends TransactionBaseService {
* @return the result of the find operation * @return the result of the find operation
*/ */
async listAndCount( async listAndCount(
selector: Partial<ProductType> & { selector: Selector<ProductType> & {
q?: string q?: string
discount_condition_id?: string discount_condition_id?: string
} = {}, } = {},
+20 -8
View File
@@ -8,10 +8,14 @@ import {
Validate, Validate,
ValidateNested, ValidateNested,
} from "class-validator" } from "class-validator"
import { DiscountConditionOperator } from "../models/discount-condition" import {
import { AllocationType, DiscountRuleType } from "../models/discount-rule" AllocationType,
DiscountConditionOperator,
DiscountConditionType,
DiscountRuleType,
Region,
} from "../models"
import { ExactlyOne } from "./validators/exactly-one" import { ExactlyOne } from "./validators/exactly-one"
import { Region } from "../models"
export type QuerySelector = { export type QuerySelector = {
q?: string q?: string
@@ -105,15 +109,23 @@ export class AdminUpsertConditionsReq {
customer_groups?: string[] customer_groups?: string[]
} }
export const DiscountConditionMapTypeToProperty = {
[DiscountConditionType.PRODUCTS]: "products",
[DiscountConditionType.PRODUCT_TYPES]: "product_types",
[DiscountConditionType.PRODUCT_COLLECTIONS]: "product_collections",
[DiscountConditionType.PRODUCT_TAGS]: "product_tags",
[DiscountConditionType.CUSTOMER_GROUPS]: "customer_groups",
}
export type UpsertDiscountConditionInput = { export type UpsertDiscountConditionInput = {
rule_id?: string rule_id?: string
id?: string id?: string
operator?: DiscountConditionOperator operator?: DiscountConditionOperator
products?: string[] products?: (string | { id: string })[]
product_collections?: string[] product_collections?: (string | { id: string })[]
product_types?: string[] product_types?: (string | { id: string })[]
product_tags?: string[] product_tags?: (string | { id: string })[]
customer_groups?: string[] customer_groups?: (string | { id: string })[]
} }
export type CreateDiscountRuleInput = { export type CreateDiscountRuleInput = {