diff --git a/packages/medusa/src/api/routes/admin/draft-orders/create-draft-order.js b/packages/medusa/src/api/routes/admin/draft-orders/create-draft-order.js index 216618836b..5e1cbe3b51 100644 --- a/packages/medusa/src/api/routes/admin/draft-orders/create-draft-order.js +++ b/packages/medusa/src/api/routes/admin/draft-orders/create-draft-order.js @@ -63,6 +63,9 @@ import { defaultFields, defaultRelations } from "." * customer_id: * description: The id of the customer to add on the draft order * type: string + * no_notification_order: + * description: An optional flag passed to the resulting order to determine use of notifications. + * type: boolean * shipping_methods: * description: The shipping methods for the draft order * type: array @@ -123,6 +126,7 @@ export default async (req, res) => { }) .optional(), customer_id: Validator.string().optional(), + no_notification_order: Validator.boolean().optional(), shipping_methods: Validator.array() .items({ option_id: Validator.string().required(), diff --git a/packages/medusa/src/api/routes/admin/draft-orders/index.js b/packages/medusa/src/api/routes/admin/draft-orders/index.js index 10b9e65f30..d5a08f41ce 100644 --- a/packages/medusa/src/api/routes/admin/draft-orders/index.js +++ b/packages/medusa/src/api/routes/admin/draft-orders/index.js @@ -78,6 +78,7 @@ export const defaultFields = [ "created_at", "updated_at", "metadata", + "no_notification_order", ] export const allowedFields = [ @@ -89,6 +90,7 @@ export const allowedFields = [ "created_at", "updated_at", "metadata", + "no_notification_order", ] export const allowedRelations = ["cart"] diff --git a/packages/medusa/src/api/routes/admin/draft-orders/update-draft-order.js b/packages/medusa/src/api/routes/admin/draft-orders/update-draft-order.js index 6230f553d5..5de8e9545a 100644 --- a/packages/medusa/src/api/routes/admin/draft-orders/update-draft-order.js +++ b/packages/medusa/src/api/routes/admin/draft-orders/update-draft-order.js @@ -35,6 +35,9 @@ import { defaultCartFields, defaultCartRelations, defaultFields } from "." * code: * description: "The code that a Discount is identifed by." * type: string + * no_notification_order: + * description: "An optional flag passed to the resulting order to determine use of notifications." + * type: boolean * customer_id: * description: "The id of the Customer to associate the Draft Order with." * type: string @@ -68,6 +71,7 @@ export default async (req, res) => { }) .optional(), customer_id: Validator.string().optional(), + no_notification_order: Validator.boolean().optional(), }) const { value, error } = schema.validate(req.body) @@ -88,6 +92,13 @@ export default async (req, res) => { ) } + if ("no_notification_order" in value) { + await draftOrderService.update(draftOrder.id, { + no_notification_order: value.no_notification_order, + }) + delete value.no_notification_order + } + await cartService.update(draftOrder.cart_id, value) draftOrder.cart = await cartService.retrieve(draftOrder.cart_id, { diff --git a/packages/medusa/src/api/routes/admin/orders/__tests__/create-fulfillment.js b/packages/medusa/src/api/routes/admin/orders/__tests__/create-fulfillment.js index 971eb227e6..95350aa0d7 100644 --- a/packages/medusa/src/api/routes/admin/orders/__tests__/create-fulfillment.js +++ b/packages/medusa/src/api/routes/admin/orders/__tests__/create-fulfillment.js @@ -42,7 +42,7 @@ describe("POST /admin/orders/:id/fulfillment", () => { quantity: 1, }, ], - undefined + { metadata: undefined, no_notification: undefined } ) }) diff --git a/packages/medusa/src/api/routes/admin/orders/__tests__/get-order.js b/packages/medusa/src/api/routes/admin/orders/__tests__/get-order.js index 5001ed0def..57cb1db7ea 100644 --- a/packages/medusa/src/api/routes/admin/orders/__tests__/get-order.js +++ b/packages/medusa/src/api/routes/admin/orders/__tests__/get-order.js @@ -62,6 +62,7 @@ const defaultFields = [ "total", "paid_total", "refundable_amount", + "no_notification", ] describe("GET /admin/orders", () => { diff --git a/packages/medusa/src/api/routes/admin/orders/__tests__/return-order.js b/packages/medusa/src/api/routes/admin/orders/__tests__/return-order.js index 5a36010d3e..661a5498a9 100644 --- a/packages/medusa/src/api/routes/admin/orders/__tests__/return-order.js +++ b/packages/medusa/src/api/routes/admin/orders/__tests__/return-order.js @@ -1,7 +1,7 @@ import { IdMap } from "medusa-test-utils" import { request } from "../../../../../helpers/test-request" -import { orders } from "../../../../../services/__mocks__/order" import { ReturnService } from "../../../../../services/__mocks__/return" +import { EventBusServiceMock } from "../../../../../services/__mocks__/event-bus" describe("POST /admin/orders/:id/return", () => { describe("successfully returns full order", () => { @@ -21,6 +21,7 @@ describe("POST /admin/orders/:id/return", () => { }, ], refund: 10, + no_notification: true, }, adminSession: { jwt: { @@ -47,6 +48,7 @@ describe("POST /admin/orders/:id/return", () => { }, ], refund_amount: 10, + no_notification: true, shipping_method: undefined, }) }) @@ -69,6 +71,7 @@ describe("POST /admin/orders/:id/return", () => { }, ], refund: -1, + no_notification: true, }, adminSession: { jwt: { @@ -95,6 +98,7 @@ describe("POST /admin/orders/:id/return", () => { }, ], refund_amount: 0, + no_notification: true, shipping_method: undefined, }) }) @@ -118,6 +122,7 @@ describe("POST /admin/orders/:id/return", () => { ], refund: -1, }, + no_notification: true, adminSession: { jwt: { userId: IdMap.getId("admin_user"), @@ -143,6 +148,7 @@ describe("POST /admin/orders/:id/return", () => { }, ], refund_amount: 0, + no_notification: true, shipping_method: undefined, }) }) @@ -165,6 +171,7 @@ describe("POST /admin/orders/:id/return", () => { }, ], refund: 100, + no_notification: true, return_shipping: { option_id: "opt_1234", price: 12, @@ -195,6 +202,7 @@ describe("POST /admin/orders/:id/return", () => { }, ], refund_amount: 100, + no_notification: true, shipping_method: { option_id: "opt_1234", price: 12, @@ -205,4 +213,82 @@ describe("POST /admin/orders/:id/return", () => { expect(ReturnService.fulfill).toHaveBeenCalledWith("return") }) }) + + describe("the api call overrides notification settings of order", () => { + it("eventBus is called with the proper no notification feature", async () => { + jest.clearAllMocks() + const subject = await request( + "POST", + `/admin/orders/${IdMap.getId("test-order")}/return`, + { + payload: { + items: [ + { + item_id: IdMap.getId("existingLine"), + quantity: 10, + }, + ], + refund: 100, + return_shipping: { + option_id: "opt_1234", + price: 12, + }, + no_notification: false, + }, + adminSession: { + jwt: { + userId: IdMap.getId("admin_user"), + }, + }, + } + ) + expect(EventBusServiceMock.emit).toHaveBeenCalledWith( + expect.any(String), + { + id: expect.any(String), + no_notification: false, + return_id: expect.any(String), + } + ) + }) + }) + + describe("the api call inherits notification settings of order", () => { + it("eventBus is called with the proper no notification feature", async () => { + jest.clearAllMocks() + await request( + "POST", + `/admin/orders/${IdMap.getId("test-order")}/return`, + { + payload: { + items: [ + { + item_id: IdMap.getId("existingLine"), + quantity: 10, + }, + ], + refund: 100, + return_shipping: { + option_id: "opt_1234", + price: 12, + }, + }, + adminSession: { + jwt: { + userId: IdMap.getId("admin_user"), + }, + }, + } + ) + + expect(EventBusServiceMock.emit).toHaveBeenCalledWith( + expect.any(String), + { + id: expect.any(String), + no_notification: true, + return_id: expect.any(String), + } + ) + }) + }) }) diff --git a/packages/medusa/src/api/routes/admin/orders/create-claim.js b/packages/medusa/src/api/routes/admin/orders/create-claim.js index ce77c090a4..ade142b418 100644 --- a/packages/medusa/src/api/routes/admin/orders/create-claim.js +++ b/packages/medusa/src/api/routes/admin/orders/create-claim.js @@ -91,6 +91,9 @@ import { defaultRelations, defaultFields } from "./" * refund_amount: * description: The amount to refund the Customer when the Claim type is `refund`. * type: integer + * no_notification: + * description: If set to true no notification will be send related to this Claim. + * type: boolean * metadata: * description: An optional set of key-value pairs to hold additional information. * type: object @@ -108,7 +111,6 @@ import { defaultRelations, defaultFields } from "./" */ export default async (req, res) => { const { id } = req.params - const schema = Validator.object().keys({ type: Validator.string() .valid("replace", "refund") @@ -155,6 +157,7 @@ export default async (req, res) => { .integer() .optional(), shipping_address: Validator.object().optional(), + no_notification: Validator.boolean().optional(), metadata: Validator.object().optional(), }) @@ -162,7 +165,6 @@ export default async (req, res) => { if (error) { throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details) } - const idempotencyKeyService = req.scope.resolve("idempotencyKeyService") const headerKey = req.get("Idempotency-Key") || "" @@ -212,6 +214,7 @@ export default async (req, res) => { return_shipping: value.return_shipping, additional_items: value.additional_items, shipping_methods: value.shipping_methods, + no_notification: value.no_notification, metadata: value.metadata, }) diff --git a/packages/medusa/src/api/routes/admin/orders/create-fulfillment.js b/packages/medusa/src/api/routes/admin/orders/create-fulfillment.js index f8ccab4ac1..11e41323ce 100644 --- a/packages/medusa/src/api/routes/admin/orders/create-fulfillment.js +++ b/packages/medusa/src/api/routes/admin/orders/create-fulfillment.js @@ -24,6 +24,9 @@ import { defaultRelations, defaultFields } from "./" * quantity: * description: The quantity of the Line Item to fulfill. * type: integer + * no_notification: + * description: If set to true no notification will be send related to this Swap. + * type: boolean * metadata: * description: An optional set of key-value pairs to hold additional information. * type: object @@ -49,6 +52,7 @@ export default async (req, res) => { quantity: Validator.number().required(), }) .required(), + no_notification: Validator.boolean().optional(), metadata: Validator.object().optional(), }) @@ -60,7 +64,10 @@ export default async (req, res) => { try { const orderService = req.scope.resolve("orderService") - await orderService.createFulfillment(id, value.items, value.metadata) + await orderService.createFulfillment(id, value.items, { + metadata: value.metadata, + no_notification: value.no_notification, + }) const order = await orderService.retrieve(id, { select: defaultFields, diff --git a/packages/medusa/src/api/routes/admin/orders/create-order.js b/packages/medusa/src/api/routes/admin/orders/create-order.js index e6ff1b36df..5adce02ad8 100644 --- a/packages/medusa/src/api/routes/admin/orders/create-order.js +++ b/packages/medusa/src/api/routes/admin/orders/create-order.js @@ -27,6 +27,7 @@ export default async (req, res) => { items: Validator.array().optional(), }) .required(), + no_notification: Validator.boolean().optional(), metadata: Validator.object().optional(), }) diff --git a/packages/medusa/src/api/routes/admin/orders/create-shipment.js b/packages/medusa/src/api/routes/admin/orders/create-shipment.js index a371ef244f..915e15f26d 100644 --- a/packages/medusa/src/api/routes/admin/orders/create-shipment.js +++ b/packages/medusa/src/api/routes/admin/orders/create-shipment.js @@ -21,6 +21,9 @@ import { defaultRelations, defaultFields } from "./" * type: array * items: * type: string + * no_notification: + * description: If set to true no notification will be send related to this Shipment. + * type: boolean * tags: * - Order * responses: @@ -41,6 +44,7 @@ export default async (req, res) => { tracking_numbers: Validator.array() .items(Validator.string()) .optional(), + no_notification: Validator.boolean().optional(), }) const { value, error } = schema.validate(req.body) @@ -54,7 +58,8 @@ export default async (req, res) => { await orderService.createShipment( id, value.fulfillment_id, - value.tracking_numbers.map(n => ({ tracking_number: n })) + value.tracking_numbers.map(n => ({ tracking_number: n })), + { no_notification: value.no_notification } ) const order = await orderService.retrieve(id, { diff --git a/packages/medusa/src/api/routes/admin/orders/create-swap-shipment.js b/packages/medusa/src/api/routes/admin/orders/create-swap-shipment.js index 702d886cb5..edf8572f51 100644 --- a/packages/medusa/src/api/routes/admin/orders/create-swap-shipment.js +++ b/packages/medusa/src/api/routes/admin/orders/create-swap-shipment.js @@ -22,6 +22,9 @@ import { defaultFields, defaultRelations } from "./" * type: array * items: * type: string + * no_notification: + * description: If set to true no notification will be send related to this Claim. + * type: boolean * tags: * - Order * responses: @@ -42,6 +45,7 @@ export default async (req, res) => { tracking_numbers: Validator.array() .items(Validator.string()) .optional(), + no_notification: Validator.boolean().optional(), }) const { value, error } = schema.validate(req.body) @@ -56,7 +60,8 @@ export default async (req, res) => { await swapService.createShipment( swap_id, value.fulfillment_id, - value.tracking_numbers.map(n => ({ tracking_number: n })) + value.tracking_numbers.map(n => ({ tracking_number: n })), + { no_notification: value.no_notification } ) const order = await orderService.retrieve(id, { diff --git a/packages/medusa/src/api/routes/admin/orders/create-swap.js b/packages/medusa/src/api/routes/admin/orders/create-swap.js index 7782400eaa..25ad45604e 100644 --- a/packages/medusa/src/api/routes/admin/orders/create-swap.js +++ b/packages/medusa/src/api/routes/admin/orders/create-swap.js @@ -45,6 +45,9 @@ import { defaultFields, defaultRelations } from "./" * quantity: * description: The quantity of the Product Variant to ship. * type: integer + * no_notification: + * description: If set to true no notification will be send related to this Swap. + * type: boolean * tags: * - Order * responses: @@ -79,6 +82,7 @@ export default async (req, res) => { variant_id: Validator.string().required(), quantity: Validator.number().required(), }), + no_notification: Validator.boolean().optional(), }) const { value, error } = schema.validate(req.body) @@ -134,7 +138,10 @@ export default async (req, res) => { value.return_items, value.additional_items, value.return_shipping, - { idempotency_key: idempotencyKey.idempotency_key } + { + idempotency_key: idempotencyKey.idempotency_key, + no_notification: value.no_notification, + } ) await swapService.withTransaction(manager).createCart(swap.id) diff --git a/packages/medusa/src/api/routes/admin/orders/fulfill-claim.js b/packages/medusa/src/api/routes/admin/orders/fulfill-claim.js index 50668a16c2..6c017c8517 100644 --- a/packages/medusa/src/api/routes/admin/orders/fulfill-claim.js +++ b/packages/medusa/src/api/routes/admin/orders/fulfill-claim.js @@ -17,6 +17,9 @@ import { defaultRelations, defaultFields } from "./" * metadata: * description: An optional set of key-value pairs to hold additional information. * type: object + * no_notification: + * description: If set to true no notification will be send related to this Claim. + * type: boolean * tags: * - Order * responses: @@ -34,6 +37,7 @@ export default async (req, res) => { const schema = Validator.object().keys({ metadata: Validator.object().optional(), + no_notification: Validator.boolean().optional(), }) const { value, error } = schema.validate(req.body) @@ -47,9 +51,10 @@ export default async (req, res) => { const entityManager = req.scope.resolve("manager") await entityManager.transaction(async manager => { - await claimService - .withTransaction(manager) - .createFulfillment(claim_id, value.metadata) + await claimService.withTransaction(manager).createFulfillment(claim_id, { + metadata: value.metadata, + no_notification: value.no_notification, + }) }) const order = await orderService.retrieve(id, { diff --git a/packages/medusa/src/api/routes/admin/orders/fulfill-swap.js b/packages/medusa/src/api/routes/admin/orders/fulfill-swap.js index 7212614f49..aa8f680464 100644 --- a/packages/medusa/src/api/routes/admin/orders/fulfill-swap.js +++ b/packages/medusa/src/api/routes/admin/orders/fulfill-swap.js @@ -17,6 +17,9 @@ import { defaultRelations, defaultFields } from "./" * metadata: * description: An optional set of key-value pairs to hold additional information. * type: object + * no_notification: + * description: If set to true no notification will be send related to this Claim. + * type: boolean * tags: * - Order * responses: @@ -34,6 +37,7 @@ export default async (req, res) => { const schema = Validator.object().keys({ metadata: Validator.object().optional(), + no_notification: Validator.boolean().optional, }) const { value, error } = schema.validate(req.body) @@ -47,9 +51,10 @@ export default async (req, res) => { const entityManager = req.scope.resolve("manager") await entityManager.transaction(async manager => { - await swapService - .withTransaction(manager) - .createFulfillment(swap_id, value.metadata) + await swapService.withTransaction(manager).createFulfillment(swap_id, { + metadata: value.metadata, + no_notification: value.no_notification, + }) const order = await orderService.withTransaction(manager).retrieve(id, { select: defaultFields, diff --git a/packages/medusa/src/api/routes/admin/orders/index.js b/packages/medusa/src/api/routes/admin/orders/index.js index 98e72e599c..29ce86e109 100644 --- a/packages/medusa/src/api/routes/admin/orders/index.js +++ b/packages/medusa/src/api/routes/admin/orders/index.js @@ -237,6 +237,7 @@ export const defaultFields = [ "total", "paid_total", "refundable_amount", + "no_notification", ] export const allowedFields = [ @@ -265,6 +266,7 @@ export const allowedFields = [ "total", "paid_total", "refundable_amount", + "no_notification", ] export const allowedRelations = [ diff --git a/packages/medusa/src/api/routes/admin/orders/refund-payment.js b/packages/medusa/src/api/routes/admin/orders/refund-payment.js index c4629e0987..b0bc76bf9d 100644 --- a/packages/medusa/src/api/routes/admin/orders/refund-payment.js +++ b/packages/medusa/src/api/routes/admin/orders/refund-payment.js @@ -25,6 +25,9 @@ import { defaultRelations, defaultFields } from "./" * note: * description: A not with additional details about the Refund. * type: string + * no_notification: + * description: If set to true no notification will be send related to this Refund. + * type: boolean * tags: * - Order * responses: @@ -47,9 +50,11 @@ export default async (req, res) => { note: Validator.string() .allow("") .optional(), + no_notification: Validator.boolean().optional(), }) const { value, error } = schema.validate(req.body) + if (error) { throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details) } @@ -57,7 +62,13 @@ export default async (req, res) => { try { const orderService = req.scope.resolve("orderService") - await orderService.createRefund(id, value.amount, value.reason, value.note) + await orderService.createRefund( + id, + value.amount, + value.reason, + value.note, + { no_notification: value.no_notification } + ) const order = await orderService.retrieve(id, { select: defaultFields, diff --git a/packages/medusa/src/api/routes/admin/orders/request-return.js b/packages/medusa/src/api/routes/admin/orders/request-return.js index 3c0b49ed9f..1d3e0c0da0 100644 --- a/packages/medusa/src/api/routes/admin/orders/request-return.js +++ b/packages/medusa/src/api/routes/admin/orders/request-return.js @@ -43,6 +43,9 @@ import { defaultRelations, defaultFields } from "./" * receive_now: * description: A flag to indicate if the Return should be registerd as received immediately. * type: boolean + * no_notification: + * description: A flag to indicate if no notifications should be emitted related to the requested Return. + * type: boolean * refund: * description: The amount to refund. * type: integer @@ -79,6 +82,7 @@ export default async (req, res) => { }) .optional(), receive_now: Validator.boolean().default(false), + no_notification: Validator.boolean().optional(), refund: Validator.number() .integer() .optional(), @@ -141,6 +145,13 @@ export default async (req, res) => { } } + let order = await orderService + .withTransaction(manager) + .retrieve(id) + + const evaluatedNoNotification = value.no_notification !== undefined ? value.no_notification : order.no_notification + returnObj.no_notification = evaluatedNoNotification + const createdReturn = await returnService .withTransaction(manager) .create(returnObj) @@ -150,12 +161,13 @@ export default async (req, res) => { .withTransaction(manager) .fulfill(createdReturn.id) } - + await eventBus .withTransaction(manager) .emit("order.return_requested", { id, return_id: createdReturn.id, + no_notification: evaluatedNoNotification }) return { diff --git a/packages/medusa/src/api/routes/admin/orders/update-claim.js b/packages/medusa/src/api/routes/admin/orders/update-claim.js index ffbd4eae46..90346be45c 100644 --- a/packages/medusa/src/api/routes/admin/orders/update-claim.js +++ b/packages/medusa/src/api/routes/admin/orders/update-claim.js @@ -62,6 +62,9 @@ import { defaultRelations, defaultFields } from "./" * price: * description: The price to charge for the Shipping Method * type: integer + * no_notification: + * description: If set to true no notification will be send related to this Swap. + * type: boolean * metadata: * description: An optional set of key-value pairs to hold additional information. * type: object @@ -106,6 +109,7 @@ export default async (req, res) => { .optional(), }) .optional(), + no_notification: Validator.boolean().optional(), metadata: Validator.object().optional(), }) diff --git a/packages/medusa/src/api/routes/admin/orders/update-order.js b/packages/medusa/src/api/routes/admin/orders/update-order.js index 37e321024b..582b1b9a81 100644 --- a/packages/medusa/src/api/routes/admin/orders/update-order.js +++ b/packages/medusa/src/api/routes/admin/orders/update-order.js @@ -23,6 +23,7 @@ export default async (req, res) => { data: Validator.object(), items: Validator.array(), }), + no_notification: Validator.boolean(), }) const { value, error } = schema.validate(req.body) diff --git a/packages/medusa/src/api/routes/admin/returns/receive-return.js b/packages/medusa/src/api/routes/admin/returns/receive-return.js index dce7cf18f4..5baadcc5d6 100644 --- a/packages/medusa/src/api/routes/admin/returns/receive-return.js +++ b/packages/medusa/src/api/routes/admin/returns/receive-return.js @@ -54,6 +54,7 @@ export default async (req, res) => { }) const { value, error } = schema.validate(req.body) + if (error) { throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details) } diff --git a/packages/medusa/src/index.js b/packages/medusa/src/index.js index c5c3367cd6..e9088f2917 100644 --- a/packages/medusa/src/index.js +++ b/packages/medusa/src/index.js @@ -43,3 +43,4 @@ export { Swap } from "./models/swap" export { User } from "./models/user" export { DraftOrder } from "./models/draft-order" export { ReturnReason } from "./models/return-reason" + diff --git a/packages/medusa/src/migrations/1623231564533-no_notification.ts b/packages/medusa/src/migrations/1623231564533-no_notification.ts new file mode 100644 index 0000000000..dcd56690b0 --- /dev/null +++ b/packages/medusa/src/migrations/1623231564533-no_notification.ts @@ -0,0 +1,25 @@ +import {MigrationInterface, QueryRunner} from "typeorm"; + +export class noNotification1623231564533 implements MigrationInterface { + name = 'noNotification1623231564533' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "return" ADD "no_notification" boolean`); + await queryRunner.query(`ALTER TABLE "claim_order" ADD "no_notification" boolean`); + await queryRunner.query(`ALTER TABLE "swap" ADD "no_notification" boolean`); + await queryRunner.query(`ALTER TABLE "order" ADD "no_notification" boolean`); + await queryRunner.query(`ALTER TABLE "draft_order" ADD "no_notification_order" boolean`); + await queryRunner.query(`ALTER TABLE "fulfillment" ADD "no_notification" boolean`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "fulfillment" DROP COLUMN "no_notification"`); + await queryRunner.query(`ALTER TABLE "draft_order" DROP COLUMN "no_notification_order"`); + await queryRunner.query(`ALTER TABLE "order" DROP COLUMN "no_notification"`); + await queryRunner.query(`ALTER TABLE "swap" DROP COLUMN "no_notification"`); + await queryRunner.query(`ALTER TABLE "claim_order" DROP COLUMN "no_notification"`); + await queryRunner.query(`ALTER TABLE "return" DROP COLUMN "no_notification"`); + + } + +} diff --git a/packages/medusa/src/models/cart.ts b/packages/medusa/src/models/cart.ts index dbb5f53b53..3190685171 100644 --- a/packages/medusa/src/models/cart.ts +++ b/packages/medusa/src/models/cart.ts @@ -246,7 +246,7 @@ export class Cart { @Column({ type: "jsonb", nullable: true }) context: any - + // Total fields shipping_total: number discount_total: number diff --git a/packages/medusa/src/models/claim-order.ts b/packages/medusa/src/models/claim-order.ts index 547245544f..c5c8ec430b 100644 --- a/packages/medusa/src/models/claim-order.ts +++ b/packages/medusa/src/models/claim-order.ts @@ -134,6 +134,9 @@ export class ClaimOrder { @DeleteDateColumn({ type: "timestamptz" }) deleted_at: Date + @Column({ type: "boolean", nullable: true}) + no_notification: Boolean + @Column({ type: "jsonb", nullable: true }) metadata: any @@ -223,6 +226,9 @@ export class ClaimOrder { * deleted_at: * type: string * format: date-time + * no_notification: + * description: "Flag for describing whether or not notifications related to this should be send." + * type: boolean * metadata: * type: object */ diff --git a/packages/medusa/src/models/draft-order.ts b/packages/medusa/src/models/draft-order.ts index 7430e6ee07..7c49919069 100644 --- a/packages/medusa/src/models/draft-order.ts +++ b/packages/medusa/src/models/draft-order.ts @@ -61,6 +61,9 @@ export class DraftOrder { @Column({ type: "timestamptz", nullable: true }) completed_at: Date + @Column({ nullable: true}) + no_notification_order: boolean + @Column({ type: "jsonb", nullable: true }) metadata: any @@ -115,6 +118,8 @@ export class DraftOrder { * completed_at: * type: string * format: date-time + * no_notification_order: + * type: boolean * metadata: * type: object * idempotency_key: diff --git a/packages/medusa/src/models/fulfillment.ts b/packages/medusa/src/models/fulfillment.ts index 4f14943e90..30fc17986e 100644 --- a/packages/medusa/src/models/fulfillment.ts +++ b/packages/medusa/src/models/fulfillment.ts @@ -62,6 +62,9 @@ export class Fulfillment { @JoinColumn({ name: "order_id" }) order: Order + @Column({ type: "boolean", nullable: true}) + no_notification: Boolean + @Index() @Column() provider_id: string @@ -157,6 +160,9 @@ export class Fulfillment { * description: "The date with timezone at which the Fulfillment was shipped." * type: string * format: date-time + * no_notification: + * description: "Flag for describing whether or not notifications related to this should be send." + * type: boolean * canceled_at: * description: "The date with timezone at which the Fulfillment was canceled." * type: string diff --git a/packages/medusa/src/models/order.ts b/packages/medusa/src/models/order.ts index a6d7be6ff0..6aab13bc40 100644 --- a/packages/medusa/src/models/order.ts +++ b/packages/medusa/src/models/order.ts @@ -245,6 +245,9 @@ export class Order { @Column({ type: "jsonb", nullable: true }) metadata: any + @Column({ type: "boolean", nullable: true}) + no_notification: Boolean + @Column({ nullable: true }) idempotency_key: string @@ -414,4 +417,7 @@ export class Order { * type: integer * paid_total: * type: integer + * no_notification: + * description: "Flag for describing whether or not notifications related to this should be send." + * type: boolean */ diff --git a/packages/medusa/src/models/return.ts b/packages/medusa/src/models/return.ts index 77cd1d2837..382f308937 100644 --- a/packages/medusa/src/models/return.ts +++ b/packages/medusa/src/models/return.ts @@ -99,6 +99,9 @@ export class Return { @UpdateDateColumn({ type: "timestamptz" }) updated_at: Date + @Column({ type: "boolean", nullable: true}) + no_notification: Boolean + @Column({ type: "jsonb", nullable: true }) metadata: any @@ -165,6 +168,9 @@ export class Return { * description: "The date with timezone at which the resource was last updated." * type: string * format: date-time + * no_notification: + * description: "When set to true, no notification will be sent related to this return." + * type: boolean * metadata: * description: "An optional key-value map with additional information." * type: object diff --git a/packages/medusa/src/models/swap.ts b/packages/medusa/src/models/swap.ts index 606bdfade7..8bac4bda79 100644 --- a/packages/medusa/src/models/swap.ts +++ b/packages/medusa/src/models/swap.ts @@ -130,6 +130,9 @@ export class Swap { @DeleteDateColumn({ type: "timestamptz" }) deleted_at: Date + @Column({ type: "boolean", nullable: true}) + no_notification: Boolean + @Column({ type: "jsonb", nullable: true }) metadata: any @@ -224,6 +227,9 @@ export class Swap { * description: "The date with timezone at which the resource was last updated." * type: string * format: date-time + * no_notification: + * description: "If set to true, no notification will be sent related to this swap" + * type: boolean * metadata: * description: "An optional key-value map with additional information." * type: object diff --git a/packages/medusa/src/services/__mocks__/order.js b/packages/medusa/src/services/__mocks__/order.js index bd8d0d6c05..5779f64e3b 100644 --- a/packages/medusa/src/services/__mocks__/order.js +++ b/packages/medusa/src/services/__mocks__/order.js @@ -47,6 +47,7 @@ export const orders = { providerid: "default_provider", data: {}, }, + no_notification: true, shipping_method: [ { providerid: "default_provider", @@ -99,6 +100,7 @@ export const orders = { payment_method: { providerid: "default_provider", }, + no_notification: false, shipping_methods: [ { id: IdMap.getId("expensiveShipping"), diff --git a/packages/medusa/src/services/__tests__/claim.js b/packages/medusa/src/services/__tests__/claim.js index cedc534746..feae278cb3 100644 --- a/packages/medusa/src/services/__tests__/claim.js +++ b/packages/medusa/src/services/__tests__/claim.js @@ -22,6 +22,7 @@ describe("ClaimService", () => { order: { id: "1234", region_id: "order_region", + no_notification: true, items: [ { id: "itm_1", @@ -113,6 +114,7 @@ describe("ClaimService", () => { quantity: 1, }, ], + no_notification: true, }) expect(withTransactionMock).toHaveBeenCalledWith("lineItem") @@ -138,6 +140,7 @@ describe("ClaimService", () => { expect(claimRepo.create).toHaveBeenCalledTimes(1) expect(claimRepo.create).toHaveBeenCalledWith({ payment_status: "not_refunded", + no_notification: true, refund_amount: 1000, type: "refund", order_id: "1234", @@ -156,6 +159,7 @@ describe("ClaimService", () => { expect(eventBusService.emit).toHaveBeenCalledTimes(1) expect(eventBusService.emit).toHaveBeenCalledWith("claim.created", { id: "claim_134", + no_notification: true, }) }) @@ -208,6 +212,25 @@ describe("ClaimService", () => { }) ).rejects.toThrow(`Claims must have at least one claim item.`) }) + + it.each( + [ + [false, false], + [undefined, true], + ], + "passes correct no_notification status to event bus", + async (input, expected) => { + await claimService.create({ + ...testClaim, + no_notification: input, + }) + + expect(eventBusService.emit).toHaveBeenCalledWith(expect.any(String), { + id: expect.any(String), + no_notification: expected, + }) + } + ) }) describe("retrieve", () => { @@ -294,7 +317,9 @@ describe("ClaimService", () => { }) it("successfully creates fulfillment", async () => { - await claimService.createFulfillment("claim_id", { meta: "data" }) + await claimService.createFulfillment("claim_id", { + metadata: { meta: "data" }, + }) expect(withTransactionMock).toHaveBeenCalledTimes(3) expect(withTransactionMock).toHaveBeenCalledWith("eventBus") @@ -414,7 +439,10 @@ describe("ClaimService", () => { ) await claimService.createShipment("claim", "ful_123", ["track1234"], { - meta: "data", + metadata: { + meta: "data", + }, + no_notification: false, }) expect(withTransactionMock).toHaveBeenCalledTimes(3) @@ -426,7 +454,12 @@ describe("ClaimService", () => { expect(fulfillmentService.createShipment).toHaveBeenCalledWith( "ful_123", ["track1234"], - { meta: "data" } + { + metadata: { + meta: "data", + }, + no_notification: false, + } ) expect(lineItemService.update).toHaveBeenCalledTimes(1) diff --git a/packages/medusa/src/services/__tests__/draft-order.js b/packages/medusa/src/services/__tests__/draft-order.js index ab5460f553..dceedd6091 100644 --- a/packages/medusa/src/services/__tests__/draft-order.js +++ b/packages/medusa/src/services/__tests__/draft-order.js @@ -1,5 +1,6 @@ import _ from "lodash" -import { IdMap, MockRepository, MockManager } from "medusa-test-utils" +import { MockRepository, MockManager } from "medusa-test-utils" +import { EventBusServiceMock } from "../__mocks__/event-bus" import DraftOrderService from "../draft-order" const eventBusService = { @@ -205,4 +206,93 @@ describe("DraftOrderService", () => { } }) }) + + describe("update", () => { + const testOrder = { + region_id: "test-region", + shipping_address_id: "test-shipping", + billing_address_id: "test-billing", + customer_id: "test-customer", + items: [{ variant_id: "test-variant", quantity: 2, metadata: {} }], + shipping_methods: [ + { + option_id: "test-option", + data: {}, + }, + ], + } + + const completedOrder = { + status: "completed", + ...testOrder, + } + + const draftOrderRepository = MockRepository({ + create: d => ({ + ...d, + }), + save: d => ({ + id: "test-draft-order", + ...d, + }), + findOne: q => { + switch (q.where.id) { + case "completed": + return Promise.resolve(completedOrder) + default: + return Promise.resolve(testOrder) + } + }, + }) + + const draftOrderService = new DraftOrderService({ + manager: MockManager, + regionService: undefined, + cartService: undefined, + shippingOptionService: undefined, + lineItemService: undefined, + productVariantService: undefined, + draftOrderRepository, + addressRepository: undefined, + eventBusService: EventBusServiceMock, + }) + + beforeEach(async () => { + jest.clearAllMocks() + }) + + it("calls draftOrder model functions", async () => { + await draftOrderService.update("test-draft-order", { + no_notification_order: true, + }) + + expect(draftOrderRepository.save).toHaveBeenCalledTimes(1) + expect(draftOrderRepository.save).toHaveBeenCalledWith({ + no_notification_order: true, + billing_address_id: "test-billing", + customer_id: "test-customer", + items: [ + { + metadata: {}, + quantity: 2, + variant_id: "test-variant", + }, + ], + region_id: "test-region", + shipping_address_id: "test-shipping", + shipping_methods: [ + { + data: {}, + option_id: "test-option", + }, + ], + }) + }) + + it("fails to update draftOrder when already complete", async () => { + await expect(draftOrderService.update("completed", {})).rejects.toThrow( + "Can't update a draft order which is complete" + ) + }) + }) }) diff --git a/packages/medusa/src/services/__tests__/notification.js b/packages/medusa/src/services/__tests__/notification.js index f26ab26d15..64b6361b65 100644 --- a/packages/medusa/src/services/__tests__/notification.js +++ b/packages/medusa/src/services/__tests__/notification.js @@ -1,30 +1,31 @@ import NotificationService from "../notification" -import { IdMap, MockManager, MockRepository } from "medusa-test-utils" +import { MockManager, MockRepository } from "medusa-test-utils" describe("NotificationService", () => { - describe("send", () => { - const notificationRepository = MockRepository({ create: c => c }) + const notificationRepository = MockRepository({ create: c => c }) - const container = { - manager: MockManager, - notificationRepository, - noti_test: { - sendNotification: jest.fn(() => - Promise.resolve({ - to: "test@mail.com", - data: { id: "something" }, - }) - ), - }, - } + const container = { + manager: MockManager, + notificationRepository, + noti_test: { + sendNotification: jest.fn(() => + Promise.resolve({ + to: "test@mail.com", + data: { id: "something" }, + }) + ), + }, + } - const notificationService = new NotificationService(container) + beforeEach(() => { + jest.clearAllMocks() + }) - beforeEach(() => { - jest.clearAllMocks() - }) + describe("send", () =>{ it("successfully calls provider and saves noti", async () => { + const notificationService = new NotificationService(container) + await notificationService.send("event.test", { id: "test" }, "test") expect(container.noti_test.sendNotification).toHaveBeenCalledTimes(1) @@ -51,4 +52,30 @@ describe("NotificationService", () => { expect(notificationRepository.save).toHaveBeenCalledWith(constructed) }) }) + + describe("handleEvent", () => { + + it("cancels notification if no_notification is set", async () => { + const notificationService = new NotificationService(container) + const event = "event.test" + notificationService.subscribe(event, "test") + + await notificationService.handleEvent(event, {id: "id", + return_id: "id", + no_notification: true}) + + expect(container.noti_test.sendNotification).not.toHaveBeenCalled() + }) + + it("if no_notification is not set notification is send", async () => { + const notificationService = new NotificationService(container) + const event = "event.test" + notificationService.subscribe(event, "test") + + await notificationService.handleEvent(event, {id: "id", return_id: "id"}) + + expect(container.noti_test.sendNotification).toHaveBeenCalledTimes(1) + }) + + }) }) diff --git a/packages/medusa/src/services/__tests__/order.js b/packages/medusa/src/services/__tests__/order.js index 6e9755094a..e9667752ba 100644 --- a/packages/medusa/src/services/__tests__/order.js +++ b/packages/medusa/src/services/__tests__/order.js @@ -711,6 +711,7 @@ describe("OrderService", () => { const order = { fulfillments: [], shipping_methods: [{ id: "ship" }], + no_notification: true, items: [ { id: "item_1", @@ -859,6 +860,31 @@ describe("OrderService", () => { fulfillment_status: "partially_fulfilled", }) }) + + it.each([ + [true, true], + [false, false], + [undefined, true], + ])( + "emits correct no_notification option with '%s'", + async (input, expected) => { + await orderService.createFulfillment( + "test-order", + [ + { + item_id: "item_1", + quantity: 1, + }, + ], + { no_notification: input } + ) + + expect(eventBusService.emit).toHaveBeenCalledWith(expect.any(String), { + id: expect.any(String), + no_notification: expected, + }) + } + ) }) describe("registerReturnReceived", () => { @@ -975,6 +1001,7 @@ describe("OrderService", () => { fulfilled_quantity: 0, }, ], + no_notification: true, } const orderRepo = MockRepository({ @@ -996,7 +1023,11 @@ describe("OrderService", () => { } const fulfillmentService = { - retrieve: () => Promise.resolve({ order_id: IdMap.getId("test") }), + retrieve: () => + Promise.resolve({ + order_id: IdMap.getId("test"), + no_notification: true, + }), createShipment: jest .fn() .mockImplementation((shipmentId, tracking, meta) => { @@ -1036,10 +1067,12 @@ describe("OrderService", () => { ) expect(fulfillmentService.createShipment).toHaveBeenCalledTimes(1) - expect(fulfillmentService.createShipment).toHaveBeenCalledWith( + expect( + fulfillmentService.createShipment + ).toHaveBeenCalledWith( IdMap.getId("fulfillment"), [{ tracking_number: "1234" }, { tracking_number: "2345" }], - {} + { metadata: undefined, no_notification: true } ) expect(orderRepo.save).toHaveBeenCalledTimes(1) @@ -1048,6 +1081,27 @@ describe("OrderService", () => { fulfillment_status: "shipped", }) }) + + it.each([ + [true, true], + [false, false], + [undefined, true], + ])( + "emits correct no_notification option with '%s'", + async (input, expected) => { + await orderService.createShipment( + IdMap.getId("test"), + IdMap.getId("fulfillment"), + [{ tracking_number: "1234" }, { tracking_number: "2345" }], + { no_notification: input } + ) + + expect(eventBusService.emit).toHaveBeenCalledWith(expect.any(String), { + id: expect.any(String), + no_notification: expected, + }) + } + ) }) describe("createRefund", () => { @@ -1081,6 +1135,7 @@ describe("OrderService", () => { paid_total: 100, refundable_amount: 100, refunded_total: 0, + no_notification: true, }) }, }) @@ -1129,5 +1184,27 @@ describe("OrderService", () => { ) ).rejects.toThrow("Cannot refund more than the original order amount") }) + + it.each([ + [false, false], + [undefined, true], + ])( + "emits correct no_notification option with '%s'", + async (input, expected) => { + await orderService.createRefund( + IdMap.getId("order_123"), + 100, + "discount", + "note", + { no_notification: input } + ) + + expect(eventBusService.emit).toHaveBeenCalledWith(expect.any(String), { + id: expect.any(String), + no_notification: expected, + refund_id: expect.any(String), + }) + } + ) }) }) diff --git a/packages/medusa/src/services/__tests__/swap.js b/packages/medusa/src/services/__tests__/swap.js index cce77b2d7c..da17fad783 100644 --- a/packages/medusa/src/services/__tests__/swap.js +++ b/packages/medusa/src/services/__tests__/swap.js @@ -57,6 +57,7 @@ const testOrder = generateOrder( currency_code: "dkk", region_id: IdMap.getId("region"), tax_rate: 0, + no_notification: true, shipping_address: { first_name: "test", last_name: "testson", @@ -329,6 +330,7 @@ describe("SwapService", () => { order_id: IdMap.getId("test"), fulfillment_status: "not_fulfilled", payment_status: "not_paid", + no_notification: true, additional_items: [ { unit_price: 100, @@ -340,6 +342,31 @@ describe("SwapService", () => { expect(returnService.create).toHaveBeenCalledTimes(1) }) + + it.each([ + [true, true], + [false, false], + [undefined, true], + ])( + "passes correct no_notification to eventBus with %s", + async (input, expected) => { + await swapService.create( + testOrder, + [{ item_id: IdMap.getId("line"), quantity: 1 }], + [{ variant_id: IdMap.getId("new-variant"), quantity: 1 }], + { + id: IdMap.getId("return-shipping"), + price: 20, + }, + { no_notification: input } + ) + + expect(eventBusService.emit).toHaveBeenCalledWith( + expect.any(String), + { id: undefined, no_notification: expected } + ) + } + ) }) }) diff --git a/packages/medusa/src/services/claim.js b/packages/medusa/src/services/claim.js index 758586815b..72cb5491ce 100644 --- a/packages/medusa/src/services/claim.js +++ b/packages/medusa/src/services/claim.js @@ -101,7 +101,7 @@ class ClaimService extends BaseService { const claimRepo = manager.getCustomRepository(this.claimRepository_) const claim = await this.retrieve(id, { relations: ["shipping_methods"] }) - const { claim_items, shipping_methods, metadata } = data + const { claim_items, shipping_methods, metadata, no_notification } = data if (metadata) { claim.metadata = this.setMetadata_(claim, metadata) @@ -135,6 +135,11 @@ class ClaimService extends BaseService { } } + if (no_notification !== undefined) { + claim.no_notification = no_notification + await claimRepo.save(claim) + } + if (claim_items) { for (const i of claim_items) { if (i.id) { @@ -149,6 +154,7 @@ class ClaimService extends BaseService { .withTransaction(manager) .emit(ClaimService.Events.UPDATED, { id: claim.id, + no_notification: claim.no_notification, }) return claim @@ -174,6 +180,7 @@ class ClaimService extends BaseService { refund_amount, shipping_address, shipping_address_id, + no_notification, ...rest } = data @@ -233,6 +240,9 @@ class ClaimService extends BaseService { ) ) + const evaluatedNoNotification = + no_notification !== undefined ? no_notification : order.no_notification + const created = claimRepo.create({ shipping_address_id: addressId, payment_status: type === "refund" ? "not_refunded" : "na", @@ -241,6 +251,7 @@ class ClaimService extends BaseService { type, additional_items: newItems, order_id: order.id, + no_notification: evaluatedNoNotification, }) const result = await claimRepo.save(created) @@ -281,6 +292,7 @@ class ClaimService extends BaseService { metadata: ci.metadata, })), shipping_method: return_shipping, + no_notification: evaluatedNoNotification, }) } @@ -288,13 +300,22 @@ class ClaimService extends BaseService { .withTransaction(manager) .emit(ClaimService.Events.CREATED, { id: result.id, + no_notification: result.no_notification, }) return result }) } - createFulfillment(id, metadata = {}) { + createFulfillment( + id, + config = { + metadata: {}, + no_notification: undefined, + } + ) { + const { metadata, no_notification } = config + return this.atomicPhase_(async manager => { const claim = await this.retrieve(id, { relations: [ @@ -331,6 +352,9 @@ class ClaimService extends BaseService { ) } + const evaluatedNoNotification = + no_notification !== undefined ? no_notification : claim.no_notification + const fulfillments = await this.fulfillmentService_ .withTransaction(manager) .createFulfillment( @@ -347,6 +371,7 @@ class ClaimService extends BaseService { items: claim.additional_items, shipping_methods: claim.shipping_methods, is_claim: true, + no_notification: evaluatedNoNotification, }, claim.additional_items.map(i => ({ item_id: i.id, @@ -395,6 +420,7 @@ class ClaimService extends BaseService { .emit(ClaimService.Events.FULFILLMENT_CREATED, { id: id, fulfillment_id: fulfillment.id, + no_notification: claim.no_notification, }) } @@ -430,21 +456,38 @@ class ClaimService extends BaseService { .withTransaction(manager) .emit(ClaimService.Events.REFUND_PROCESSED, { id, + no_notification: result.no_notification, }) return result }) } - async createShipment(id, fulfillmentId, trackingLinks, metadata = []) { + async createShipment( + id, + fulfillmentId, + trackingLinks, + config = { + metadata: {}, + no_notification: undefined, + } + ) { + const { metadata, no_notification } = config + return this.atomicPhase_(async manager => { const claim = await this.retrieve(id, { relations: ["additional_items"], }) + const evaluatedNoNotification = + no_notification !== undefined ? no_notification : claim.no_notification + const shipment = await this.fulfillmentService_ .withTransaction(manager) - .createShipment(fulfillmentId, trackingLinks, metadata) + .createShipment(fulfillmentId, trackingLinks, { + metadata, + no_notification: evaluatedNoNotification, + }) claim.fulfillment_status = "shipped" @@ -474,6 +517,7 @@ class ClaimService extends BaseService { .emit(ClaimService.Events.SHIPMENT_CREATED, { id, fulfillment_id: shipment.id, + no_notification: evaluatedNoNotification, }) return result @@ -524,6 +568,7 @@ class ClaimService extends BaseService { .withTransaction(manager) .emit(ClaimService.Events.CANCELED, { id: result.id, + no_notification: result.no_notification, }) return result diff --git a/packages/medusa/src/services/draft-order.js b/packages/medusa/src/services/draft-order.js index e04f3ad794..d16044b43b 100644 --- a/packages/medusa/src/services/draft-order.js +++ b/packages/medusa/src/services/draft-order.js @@ -10,6 +10,7 @@ import { Brackets } from "typeorm" class DraftOrderService extends BaseService { static Events = { CREATED: "draft_order.created", + UPDATED: "draft_order.updated", } constructor({ @@ -248,7 +249,13 @@ class DraftOrderService extends BaseService { ) } - const { items, shipping_methods, discounts, ...rest } = data + const { + items, + shipping_methods, + discounts, + no_notification_order, + ...rest + } = data if (discounts) { for (const { code } of discounts) { @@ -263,7 +270,10 @@ class DraftOrderService extends BaseService { .withTransaction(manager) .create({ type: "draft_order", ...rest }) - const draftOrder = draftOrderRepo.create({ cart_id: createdCart.id }) + const draftOrder = draftOrderRepo.create({ + cart_id: createdCart.id, + no_notification_order, + }) const result = await draftOrderRepo.save(draftOrder) await this.eventBus_ @@ -335,6 +345,44 @@ class DraftOrderService extends BaseService { await draftOrderRepo.save(draftOrder) }) } + + /** + * Updates a draft order with the given data + * @param {String} doId - id of the draft order + * @param {DraftOrder} data - values to update the order with + * @returns {Promise} the updated draft order + */ + async update(doId, data) { + return this.atomicPhase_(async manager => { + const doRepo = manager.getCustomRepository(this.draftOrderRepository_) + const draftOrder = await this.retrieve(doId) + let touched = false + + if (draftOrder.status === "completed") { + throw new MedusaError( + MedusaError.Types.NOT_ALLOWED, + "Can't update a draft order which is complete" + ) + } + + if (data.no_notification_order !== undefined) { + touched = true + draftOrder.no_notification_order = data.no_notification_order + } + + if (touched) { + doRepo.save(draftOrder) + + await this.eventBus_ + .withTransaction(manager) + .emit(DraftOrderService.Events.UPDATED, { + id: draftOrder.id, + }) + } + + return draftOrder + }) + } } export default DraftOrderService diff --git a/packages/medusa/src/services/fulfillment.js b/packages/medusa/src/services/fulfillment.js index b05363978d..64183e6aca 100644 --- a/packages/medusa/src/services/fulfillment.js +++ b/packages/medusa/src/services/fulfillment.js @@ -241,10 +241,19 @@ class FulfillmentService extends BaseService { * tracking numbers and potentially more metadata. * @param {Order} fulfillmentId - the fulfillment to ship * @param {TrackingLink[]} trackingNumbers - tracking numbers for the shipment - * @param {object} metadata - potential metadata to add + * @param {object} config - potential configuration settings, such as no_notification and metadata * @return {Fulfillment} the shipped fulfillment */ - async createShipment(fulfillmentId, trackingLinks, metadata) { + async createShipment( + fulfillmentId, + trackingLinks, + config = { + metadata: {}, + no_notification: undefined, + } + ) { + const { metadata, no_notification } = config + return this.atomicPhase_(async manager => { const fulfillmentRepository = manager.getCustomRepository( this.fulfillmentRepository_ @@ -264,6 +273,10 @@ class FulfillmentService extends BaseService { trackingLinkRepo.create(tl) ) + if (no_notification) { + fulfillment.no_notification = no_notification + } + fulfillment.metadata = { ...fulfillment.metadata, ...metadata, diff --git a/packages/medusa/src/services/notification.js b/packages/medusa/src/services/notification.js index 0c4a5eb747..8dcf7962a0 100644 --- a/packages/medusa/src/services/notification.js +++ b/packages/medusa/src/services/notification.js @@ -164,6 +164,9 @@ class NotificationService extends BaseService { if (!subs) { return Promise.resolve() } + if (data["no_notification"] === true) { + return + } return Promise.all( subs.map(async providerId => { diff --git a/packages/medusa/src/services/order.js b/packages/medusa/src/services/order.js index f6e91bd37d..7cf6f42858 100644 --- a/packages/medusa/src/services/order.js +++ b/packages/medusa/src/services/order.js @@ -333,7 +333,6 @@ class OrderService extends BaseService { const rels = query.relations delete query.relations const raw = await orderRepo.findOneWithRelations(rels, query) - if (!raw) { throw new MedusaError( MedusaError.Types.NOT_FOUND, @@ -408,6 +407,7 @@ class OrderService extends BaseService { OrderService.Events.COMPLETED, { id: orderId, + no_notification: order.no_notification, } ) @@ -507,6 +507,7 @@ class OrderService extends BaseService { .retrieveByCartId(cart.id) toCreate.draft_order_id = draft.id + toCreate.no_notification = draft.no_notification_order } const o = await orderRepo.create(toCreate) @@ -553,6 +554,7 @@ class OrderService extends BaseService { .withTransaction(manager) .emit(OrderService.Events.PLACED, { id: result.id, + no_notification: result.no_notification, }) return result @@ -571,7 +573,17 @@ class OrderService extends BaseService { * the fulfillment * @return {order} the resulting order following the update. */ - async createShipment(orderId, fulfillmentId, trackingLinks, metadata = {}) { + async createShipment( + orderId, + fulfillmentId, + trackingLinks, + config = { + metadata: {}, + no_notification: undefined, + } + ) { + const { metadata, no_notification } = config + return this.atomicPhase_(async manager => { const order = await this.retrieve(orderId, { relations: ["items"] }) const shipment = await this.fulfillmentService_.retrieve(fulfillmentId) @@ -583,9 +595,17 @@ class OrderService extends BaseService { ) } + const evaluatedNoNotification = + no_notification !== undefined + ? no_notification + : shipment.no_notification + const shipmentRes = await this.fulfillmentService_ .withTransaction(manager) - .createShipment(fulfillmentId, trackingLinks, metadata) + .createShipment(fulfillmentId, trackingLinks, { + metadata, + no_notification: evaluatedNoNotification, + }) order.fulfillment_status = "shipped" for (const item of order.items) { @@ -614,6 +634,7 @@ class OrderService extends BaseService { .emit(OrderService.Events.SHIPMENT_CREATED, { id: orderId, fulfillment_id: shipmentRes.id, + no_notification: evaluatedNoNotification, }) return result @@ -634,6 +655,7 @@ class OrderService extends BaseService { .withTransaction(manager) .emit(OrderService.Events.PLACED, { id: result.id, + no_notification: order.no_notification, }) return result }) @@ -801,6 +823,10 @@ class OrderService extends BaseService { await this.updateBillingAddress_(order, update.billing_address) } + if ("no_notification" in update) { + order.no_notification = update.no_notification + } + if ("items" in update) { for (const item of update.items) { await this.lineItemService_.withTransaction(manager).create({ @@ -821,6 +847,7 @@ class OrderService extends BaseService { .withTransaction(manager) .emit(OrderService.Events.UPDATED, { id: orderId, + no_notification: order.no_notification, }) return result }) @@ -871,6 +898,7 @@ class OrderService extends BaseService { .withTransaction(manager) .emit(OrderService.Events.CANCELED, { id: order.id, + no_notification: order.no_notification, }) return result }) @@ -899,6 +927,7 @@ class OrderService extends BaseService { id: orderId, payment_id: p.id, error: err, + no_notification: order.no_notification, }) }) @@ -924,6 +953,7 @@ class OrderService extends BaseService { .withTransaction(manager) .emit(OrderService.Events.PAYMENT_CAPTURED, { id: result.id, + no_notification: order.no_notification, }) } @@ -970,7 +1000,16 @@ class OrderService extends BaseService { * @param {string} orderId - id of order to cancel. * @return {Promise} result of the update operation. */ - async createFulfillment(orderId, itemsToFulfill, metadata = {}) { + async createFulfillment( + orderId, + itemsToFulfill, + config = { + no_notification: undefined, + metadata: {}, + } + ) { + const { metadata, no_notification } = config + return this.atomicPhase_(async manager => { const order = await this.retrieve(orderId, { select: [ @@ -979,6 +1018,8 @@ class OrderService extends BaseService { "discount_total", "tax_total", "gift_card_total", + "no_notification", + "id", "total", ], relations: [ @@ -1007,6 +1048,7 @@ class OrderService extends BaseService { .withTransaction(manager) .createFulfillment(order, itemsToFulfill, { metadata, + no_notification: no_notification, order_id: orderId, }) let successfullyFulfilled = [] @@ -1046,12 +1088,16 @@ class OrderService extends BaseService { order.fulfillments = [...order.fulfillments, ...fulfillments] const result = await orderRepo.save(order) + const evaluatedNoNotification = + no_notification !== undefined ? no_notification : order.no_notification + for (const fulfillment of fulfillments) { await this.eventBus_ .withTransaction(manager) .emit(OrderService.Events.FULFILLMENT_CREATED, { id: orderId, fulfillment_id: fulfillment.id, + no_notification: evaluatedNoNotification, }) } @@ -1107,7 +1153,17 @@ class OrderService extends BaseService { /** * Refunds a given amount back to the customer. */ - async createRefund(orderId, refundAmount, reason, note) { + async createRefund( + orderId, + refundAmount, + reason, + note, + config = { + no_notification: undefined, + } + ) { + const { no_notification } = config + return this.atomicPhase_(async manager => { const order = await this.retrieve(orderId, { select: ["refundable_amount", "total", "refunded_total"], @@ -1126,9 +1182,14 @@ class OrderService extends BaseService { .refundPayment(order.payments, refundAmount, reason, note) const result = await this.retrieve(orderId) + + const evaluatedNoNotification = + no_notification !== undefined ? no_notification : order.no_notification + this.eventBus_.emit(OrderService.Events.REFUND_CREATED, { id: result.id, refund_id: refund.id, + no_notification: evaluatedNoNotification, }) return result }) @@ -1232,6 +1293,7 @@ class OrderService extends BaseService { .emit(OrderService.Events.RETURN_ACTION_REQUIRED, { id: result.id, return_id: receivedReturn.id, + no_notification: receivedReturn.no_notification, }) return result } @@ -1263,6 +1325,7 @@ class OrderService extends BaseService { .emit(OrderService.Events.ITEMS_RETURNED, { id: order.id, return_id: receivedReturn.id, + no_notification: receivedReturn.no_notification, }) return result }) diff --git a/packages/medusa/src/services/return.js b/packages/medusa/src/services/return.js index c3399d8718..c7ae661064 100644 --- a/packages/medusa/src/services/return.js +++ b/packages/medusa/src/services/return.js @@ -330,6 +330,7 @@ class ReturnService extends BaseService { reason_id: i.reason_id, note: i.note, metadata: i.metadata, + no_notification: data.no_notification, }) ) diff --git a/packages/medusa/src/services/swap.js b/packages/medusa/src/services/swap.js index ce149eb325..b12cf4477e 100644 --- a/packages/medusa/src/services/swap.js +++ b/packages/medusa/src/services/swap.js @@ -16,6 +16,7 @@ class SwapService extends BaseService { PAYMENT_CAPTURE_FAILED: "swap.payment_capture_failed", PROCESS_REFUND_FAILED: "swap.process_refund_failed", REFUND_PROCESSED: "swap.refund_processed", + FULFILLMENT_CREATED: "swap.fulfillment_created", } constructor({ @@ -210,6 +211,9 @@ class SwapService extends BaseService { * the customer. * @param {ReturnShipping?} returnShipping - an optional shipping method for * returning the returnItems. + * @param {Object} custom - contains relevant custom information. This object may + * include no_notification which will disable sending notification when creating + * swap. If set, it overrules the attribute inherited from the order. * @returns {Promise} the newly created swap. */ async create( @@ -217,8 +221,11 @@ class SwapService extends BaseService { returnItems, additionalItems, returnShipping, - custom = {} + custom = { + no_notification: undefined, + } ) { + const { no_notification, ...rest } = custom return this.atomicPhase_(async manager => { if ( order.fulfillment_status === "not_fulfilled" || @@ -240,13 +247,17 @@ class SwapService extends BaseService { }) ) + const evaluatedNoNotification = + no_notification !== undefined ? no_notification : order.no_notification + const swapRepo = manager.getCustomRepository(this.swapRepository_) const created = swapRepo.create({ - ...custom, + ...rest, fulfillment_status: "not_fulfilled", payment_status: "not_paid", order_id: order.id, additional_items: newItems, + no_notification: evaluatedNoNotification, }) const result = await swapRepo.save(created) @@ -256,12 +267,14 @@ class SwapService extends BaseService { order_id: order.id, items: returnItems, shipping_method: returnShipping, + no_notification: evaluatedNoNotification, }) await this.eventBus_ .withTransaction(manager) .emit(SwapService.Events.CREATED, { id: result.id, + no_notification: evaluatedNoNotification, }) return result @@ -298,9 +311,14 @@ class SwapService extends BaseService { } catch (err) { swap.payment_status = "requires_action" const result = await swapRepo.save(swap) + await this.eventBus_ .withTransaction(manager) - .emit(SwapService.Events.PROCESS_REFUND_FAILED, result) + .emit(SwapService.Events.PROCESS_REFUND_FAILED, { + id: result.id, + no_notification: swap.no_notification, + }) + return result } @@ -310,7 +328,11 @@ class SwapService extends BaseService { await this.eventBus_ .withTransaction(manager) - .emit(SwapService.Events.REFUND_PROCESSED, result) + .emit(SwapService.Events.REFUND_PROCESSED, { + id: result.id, + no_notification: swap.no_notification, + }) + return result } else if (swap.difference_due === 0) { if (swap.payment_status === "difference_refunded") { @@ -320,9 +342,14 @@ class SwapService extends BaseService { swap.payment_status = "difference_refunded" const result = await swapRepo.save(swap) + await this.eventBus_ .withTransaction(manager) - .emit(SwapService.Events.REFUND_PROCESSED, result) + .emit(SwapService.Events.REFUND_PROCESSED, { + id: result.id, + no_notification: swap.no_notification, + }) + return result } @@ -337,18 +364,28 @@ class SwapService extends BaseService { } catch (err) { swap.payment_status = "requires_action" const result = await swapRepo.save(swap) + await this.eventBus_ .withTransaction(manager) - .emit(SwapService.Events.PAYMENT_CAPTURE_FAILED, result) + .emit(SwapService.Events.PAYMENT_CAPTURE_FAILED, { + id: swap.id, + no_notification: swap.no_notification, + }) + return result } swap.payment_status = "captured" const result = await swapRepo.save(swap) + await this.eventBus_ .withTransaction(manager) - .emit(SwapService.Events.PAYMENT_CAPTURED, result) + .emit(SwapService.Events.PAYMENT_CAPTURED, { + id: result.id, + no_notification: swap.no_notification, + }) + return result }) } @@ -361,6 +398,10 @@ class SwapService extends BaseService { swap.metadata = this.setMetadata_(swap, update.metadata) } + if ("no_notification" in update) { + swap.no_notification = update.no_notification + } + if ("shipping_address" in update) { await this.updateShippingAddress_(swap, update.shipping_address) } @@ -562,6 +603,7 @@ class SwapService extends BaseService { .withTransaction(manager) .emit(SwapService.Events.PAYMENT_COMPLETED, { id: swap.id, + no_notification: swap.no_notification, }) return result @@ -609,10 +651,18 @@ class SwapService extends BaseService { * Fulfills the addtional items associated with the swap. Will call the * fulfillment providers associated with the shipping methods. * @param {string} swapId - the id of the swap to fulfill, - * @param {object} metadata - optional metadata to attach to the fulfillment. + * @param {object} config - optional configurations, includes optional metadata to attach to the shipment, and a no_notification flag. * @returns {Promise} the updated swap with new status and fulfillments. */ - async createFulfillment(swapId, metadata = {}) { + async createFulfillment( + swapId, + config = { + metadata: {}, + no_notification: undefined, + } + ) { + const { metadata, no_notification } = config + return this.atomicPhase_(async manager => { const swap = await this.retrieve(swapId, { relations: [ @@ -642,6 +692,9 @@ class SwapService extends BaseService { ) } + const evaluatedNoNotification = + no_notification !== undefined ? no_notification : swap.no_notification + swap.fulfillments = await this.fulfillmentService_ .withTransaction(manager) .createFulfillment( @@ -658,6 +711,7 @@ class SwapService extends BaseService { items: swap.additional_items, shipping_methods: swap.shipping_methods, is_swap: true, + no_notification: evaluatedNoNotification, }, swap.additional_items.map(i => ({ item_id: i.id, @@ -700,6 +754,17 @@ class SwapService extends BaseService { const swapRepo = manager.getCustomRepository(this.swapRepository_) const result = await swapRepo.save(swap) + + await this.eventBus_.withTransaction(manager).emit( + SwapService.Events.FULFILLMENT_CREATED, + + { + id: swapId, + fulfillment_id: result.id, + no_notification: evaluatedNoNotification, + } + ) + return result }) } @@ -711,19 +776,35 @@ class SwapService extends BaseService { * has been shipped * @param {TrackingLink[]} trackingLinks - the tracking numbers associated * with the shipment - * @param {object} metadata - optional metadata to attach to the shipment. + * @param {object} config - optional configurations, includes optional metadata to attach to the shipment, and a noNotification flag. * @returns {Promise} the updated swap with new fulfillments and status. */ - async createShipment(swapId, fulfillmentId, trackingLinks, metadata = {}) { + async createShipment( + swapId, + fulfillmentId, + trackingLinks, + config = { + metadata: {}, + no_notification: undefined, + } + ) { + const { metadata, no_notification } = config + return this.atomicPhase_(async manager => { const swap = await this.retrieve(swapId, { relations: ["additional_items"], }) + const evaluatedNoNotification = + no_notification !== undefined ? no_notification : swap.no_notification + // Update the fulfillment to register const shipment = await this.fulfillmentService_ .withTransaction(manager) - .createShipment(fulfillmentId, trackingLinks, metadata) + .createShipment(fulfillmentId, trackingLinks, { + metadata, + no_notification: evaluatedNoNotification, + }) swap.fulfillment_status = "shipped" @@ -753,6 +834,7 @@ class SwapService extends BaseService { .emit(SwapService.Events.SHIPMENT_CREATED, { id: swapId, fulfillment_id: shipment.id, + no_notification: swap.no_notification, }) return result }) @@ -809,6 +891,7 @@ class SwapService extends BaseService { .emit(SwapService.Events.RECEIVED, { id: id, order_id: result.order_id, + no_notification: swap.no_notification, }) return result