feat(medusa, medusa-js, medusa-react): Implement item change deletion from an order edit (#2241)

This commit is contained in:
Adrien de Peretti authored and GitHub committed 2022-09-22 11:02:58 +02:00
1 parent eb8034502b
commit 14e808c724
18 files changed
+700 -17

No files matched your search

@@ -0,0 +1,51 @@
import { IdMap } from "medusa-test-utils"
import { request } from "../../../../../helpers/test-request"
import OrderEditingFeatureFlag from "../../../../../loaders/feature-flags/order-editing"
import { orderEditServiceMock } from "../../../../../services/__mocks__/order-edit"
describe("DELETE /admin/order-edits/:id/changes/:change_id", () => {
describe("deletes an order edit item change", () => {
const orderEditId = IdMap.getId("test-order-edit")
const orderEditItemChangeId = IdMap.getId("test-order-edit-item-change")
let subject
beforeAll(async () => {
subject = await request(
"DELETE",
`/admin/order-edits/${orderEditId}/changes/${orderEditItemChangeId}`,
{
adminSession: {
jwt: {
userId: IdMap.getId("admin_user"),
},
},
flags: [OrderEditingFeatureFlag],
}
)
})
afterAll(() => {
jest.clearAllMocks()
})
it("calls orderEditService delete", () => {
expect(orderEditServiceMock.deleteItemChange).toHaveBeenCalledTimes(1)
expect(orderEditServiceMock.deleteItemChange).toHaveBeenCalledWith(
orderEditId,
orderEditItemChangeId
)
})
it("returns 200", () => {
expect(subject.status).toEqual(200)
})
it("returns delete result", () => {
expect(subject.body).toEqual({
id: orderEditItemChangeId,
object: "item_change",
deleted: true,
})
})
})
})
@@ -23,7 +23,7 @@ describe("DELETE /admin/order-edits/:id", () => {
jest.clearAllMocks()
})
it("calls orderService retrieve", () => {
it("calls orderEditService delete", () => {
expect(orderEditServiceMock.delete).toHaveBeenCalledTimes(1)
expect(orderEditServiceMock.delete).toHaveBeenCalledWith(orderEditId)
})
@@ -0,0 +1,74 @@
import { EntityManager } from "typeorm"
import { OrderEditService } from "../../../../services"
/**
* @oas [delete] /order-edits/{id}/changes/{change_id}
* operationId: "DeleteOrderEditsOrderEditItemChange"
* summary: "Delete an Order Edit Item Change"
* description: "Deletes an Order Edit Item Change"
* x-authenticated: true
* parameters:
* - (path) id=* {string} The ID of the Order Edit to delete.
* - (path) change_id=* {string} The ID of the Order Edit Item Change to delete.
* x-codeSamples:
* - lang: JavaScript
* label: JS Client
* source: |
* import Medusa from "@medusajs/medusa-js"
* const medusa = new Medusa({ baseUrl: MEDUSA_BACKEND_URL, maxRetries: 3 })
* // must be previously logged in or use api token
* medusa.admin.orderEdits.deleteItemChange(item_change_id, order_edit_id)
* .then(({ id, object, deleted }) => {
* console.log(id);
* });
* - lang: Shell
* label: cURL
* source: |
* curl --location --request DELETE 'https://medusa-url.com/admin/order-edits/{id}/changes/{change_id}' \
* --header 'Authorization: Bearer {api_token}'
* security:
* - api_token: []
* - cookie_auth: []
* tags:
* - OrderEdit
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* id:
* type: string
* description: The ID of the deleted Order Edit Item Change.
* object:
* type: string
* description: The type of the object that was deleted.
* format: item_change
* deleted:
* type: boolean
* description: Whether or not the Order Edit Item Change was deleted.
* default: true
* "400":
* $ref: "#/components/responses/400_error"
*/
export default async (req, res) => {
const { id, change_id } = req.params
const orderEditService: OrderEditService =
req.scope.resolve("orderEditService")
const manager: EntityManager = req.scope.resolve("manager")
await manager.transaction(async (transactionManager) => {
await orderEditService
.withTransaction(transactionManager)
.deleteItemChange(id, change_id)
})
res.status(200).send({
id: change_id,
object: "item_change",
deleted: true,
})
}
@@ -48,6 +48,11 @@ export default (app) => {
route.delete("/:id", middlewares.wrap(require("./delete-order-edit").default))
route.delete(
"/:id/changes/:change_id",
middlewares.wrap(require("./delete-order-edit-item-change").default)
)
return app
}
@@ -55,7 +60,11 @@ export type AdminOrderEditsRes = {
order_edit: OrderEdit
}
export type AdminOrderEditDeleteRes = DeleteResponse
export type AdminOrderEditItemChangeDeleteRes = {
id: string
object: "item_change"
deleted: boolean
}
export * from "./update-order-edit"
export * from "./create-order-edit"
@@ -61,6 +61,7 @@ export const LineItemServiceMock = {
metadata,
})
}),
delete: jest.fn().mockImplementation(() => Promise.resolve()),
}
const mock = jest.fn().mockImplementation(() => {
@@ -0,0 +1,22 @@
export const orderEditItemChangeServiceMock = {
withTransaction: function () {
return this
},
retrieveItemChangeByOrderEdit: jest
.fn()
.mockImplementation((itemChangeId, orderEditId) => {
return Promise.resolve({
id: itemChangeId,
order_edit_id: orderEditId,
})
}),
delete: jest.fn().mockImplementation(() => {
return Promise.resolve()
}),
}
const mock = jest.fn().mockImplementation(() => {
return orderEditItemChangeServiceMock
})
export default mock
@@ -90,6 +90,9 @@ export const orderEditServiceMock = {
...withLineItems,
})
}),
deleteItemChange: jest.fn().mockImplementation((_) => {
return Promise.resolve()
}),
}
const mock = jest.fn().mockImplementation(() => {
@@ -0,0 +1,68 @@
import { IdMap, MockManager, MockRepository } from "medusa-test-utils"
import {
EventBusService,
LineItemService,
OrderEditItemChangeService,
TaxProviderService,
} from "../index"
import { EventBusServiceMock } from "../__mocks__/event-bus"
import { FindManyOptions, In } from "typeorm"
import { LineItemServiceMock } from "../__mocks__/line-item"
const taxProviderServiceMock = {
withTransaction: function () {
return this
},
clearLineItemsTaxLines: jest.fn().mockImplementation(() => Promise.resolve()),
}
describe("OrderEditItemChangeService", () => {
afterEach(() => {
jest.clearAllMocks()
})
const orderItemChangeRepository = MockRepository({
delete: jest.fn().mockImplementation(() => {
return Promise.resolve()
}),
find: jest.fn().mockImplementation((conditions: FindManyOptions) => {
return Promise.resolve(
conditions.where?.id?.value?.map((id) => ({
id,
line_item_id: "li_" + id,
}))
)
}),
})
const orderEditItemChangeService = new OrderEditItemChangeService({
manager: MockManager,
orderItemChangeRepository,
eventBusService: EventBusServiceMock as unknown as EventBusService,
lineItemService: LineItemServiceMock as unknown as LineItemService,
taxProviderService: taxProviderServiceMock as unknown as TaxProviderService,
})
it("should remove a item change", async () => {
const itemChangeId = IdMap.getId("order-edit-item-change-1")
await orderEditItemChangeService.delete(itemChangeId)
expect(orderItemChangeRepository.delete).toHaveBeenCalledTimes(1)
expect(orderItemChangeRepository.delete).toHaveBeenCalledWith({
id: In([itemChangeId]),
})
expect(taxProviderServiceMock.clearLineItemsTaxLines).toHaveBeenCalledTimes(
1
)
expect(taxProviderServiceMock.clearLineItemsTaxLines).toHaveBeenCalledWith([
"li_" + itemChangeId,
])
expect(EventBusServiceMock.emit).toHaveBeenCalledTimes(1)
expect(EventBusServiceMock.emit).toHaveBeenCalledWith(
OrderEditItemChangeService.Events.DELETED,
{ ids: [itemChangeId] }
)
})
})
@@ -2,6 +2,7 @@ import { IdMap, MockManager, MockRepository } from "medusa-test-utils"
import {
EventBusService,
LineItemService,
OrderEditItemChangeService,
OrderEditService,
OrderService,
TotalsService,
@@ -11,6 +12,7 @@ import { OrderServiceMock } from "../__mocks__/order"
import { EventBusServiceMock } from "../__mocks__/event-bus"
import { LineItemServiceMock } from "../__mocks__/line-item"
import { TotalsServiceMock } from "../__mocks__/totals"
import { orderEditItemChangeServiceMock } from "../__mocks__/order-edit-item-change"
const orderEditToUpdate = {
id: IdMap.getId("order-edit-to-update"),
@@ -115,6 +117,8 @@ describe("OrderEditService", () => {
eventBusService: EventBusServiceMock as unknown as EventBusService,
totalsService: TotalsServiceMock as unknown as TotalsService,
lineItemService: lineItemServiceMock as unknown as LineItemService,
orderEditItemChangeService:
orderEditItemChangeServiceMock as unknown as OrderEditItemChangeService,
})
it("should retrieve an order edit and call the repository with the right arguments", async () => {
+1
View File
@@ -22,6 +22,7 @@ export { default as NotificationService } from "./notification"
export { default as OauthService } from "./oauth"
export { default as OrderService } from "./order"
export { default as OrderEditService } from "./order-edit"
export { default as OrderEditItemChangeService } from "./order-edit-item-change"
export { default as PaymentProviderService } from "./payment-provider"
export { default as PricingService } from "./pricing"
export { default as ProductCollectionService } from "./product-collection"
@@ -0,0 +1,108 @@
import { TransactionBaseService } from "../interfaces"
import { OrderItemChangeRepository } from "../repositories/order-item-change"
import { EntityManager, In } from "typeorm"
import { EventBusService, LineItemService } from "./index"
import { FindConfig } from "../types/common"
import { OrderItemChange } from "../models"
import { buildQuery } from "../utils"
import { MedusaError } from "medusa-core-utils"
import TaxProviderService from "./tax-provider"
type InjectedDependencies = {
manager: EntityManager
orderItemChangeRepository: typeof OrderItemChangeRepository
eventBusService: EventBusService
lineItemService: LineItemService
taxProviderService: TaxProviderService
}
export default class OrderEditItemChangeService extends TransactionBaseService {
static readonly Events = {
DELETED: "order-edit-item-change.DELETED",
}
protected manager_: EntityManager
protected transactionManager_: EntityManager | undefined
protected readonly orderItemChangeRepository_: typeof OrderItemChangeRepository
protected readonly eventBus_: EventBusService
protected readonly lineItemService_: LineItemService
protected readonly taxProviderService_: TaxProviderService
constructor({
manager,
orderItemChangeRepository,
eventBusService,
lineItemService,
taxProviderService,
}: InjectedDependencies) {
// @ts-ignore
super(arguments[0])
this.manager_ = manager
this.orderItemChangeRepository_ = orderItemChangeRepository
this.eventBus_ = eventBusService
this.lineItemService_ = lineItemService
this.taxProviderService_ = taxProviderService
}
async retrieve(
id: string,
config: FindConfig<OrderItemChange> = {}
): Promise<OrderItemChange> {
const manager = this.transactionManager_ ?? this.manager_
const orderItemChangeRepo = manager.getCustomRepository(
this.orderItemChangeRepository_
)
const query = buildQuery({ id }, config)
const itemChange = await orderItemChangeRepo.findOne(query)
if (!itemChange) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`Order edit item change ${id} was not found`
)
}
return itemChange
}
async delete(itemChangeIds: string | string[]): Promise<void> {
itemChangeIds = Array.isArray(itemChangeIds)
? itemChangeIds
: [itemChangeIds]
return await this.atomicPhase_(async (manager) => {
const orderItemChangeRepo = manager.getCustomRepository(
this.orderItemChangeRepository_
)
const changes = await orderItemChangeRepo.find({
where: {
id: In(itemChangeIds as string[]),
},
})
const lineItemIdsToRemove = changes
.map((change) => {
return change.line_item_id
})
.filter(Boolean) as string[]
await orderItemChangeRepo.delete({ id: In(itemChangeIds as string[]) })
const lineItemServiceTx = this.lineItemService_.withTransaction(manager)
await Promise.all([
...lineItemIdsToRemove.map((id) => lineItemServiceTx.delete(id)),
this.taxProviderService_
.withTransaction(manager)
.clearLineItemsTaxLines(lineItemIdsToRemove),
])
await this.eventBus_
.withTransaction(manager)
.emit(OrderEditItemChangeService.Events.DELETED, { ids: itemChangeIds })
})
}
}
+38 -2
View File
@@ -14,11 +14,11 @@ import { TransactionBaseService } from "../interfaces"
import {
EventBusService,
LineItemService,
OrderEditItemChangeService,
OrderService,
TotalsService,
} from "./index"
import { CreateOrderEditInput } from "../types/order-edit"
import { UpdateOrderEditInput } from "../types/order-edit"
import { CreateOrderEditInput, UpdateOrderEditInput } from "../types/order-edit"
type InjectedDependencies = {
manager: EntityManager
@@ -27,6 +27,7 @@ type InjectedDependencies = {
eventBusService: EventBusService
totalsService: TotalsService
lineItemService: LineItemService
orderEditItemChangeService: OrderEditItemChangeService
}
export default class OrderEditService extends TransactionBaseService {
@@ -43,6 +44,7 @@ export default class OrderEditService extends TransactionBaseService {
protected readonly lineItemService_: LineItemService
protected readonly eventBusService_: EventBusService
protected readonly totalsService_: TotalsService
protected readonly orderEditItemChangeService_: OrderEditItemChangeService
constructor({
manager,
@@ -51,6 +53,7 @@ export default class OrderEditService extends TransactionBaseService {
lineItemService,
eventBusService,
totalsService,
orderEditItemChangeService,
}: InjectedDependencies) {
// eslint-disable-next-line prefer-rest-params
super(arguments[0])
@@ -61,6 +64,7 @@ export default class OrderEditService extends TransactionBaseService {
this.lineItemService_ = lineItemService
this.eventBusService_ = eventBusService
this.totalsService_ = totalsService
this.orderEditItemChangeService_ = orderEditItemChangeService
}
async retrieve(
@@ -382,4 +386,36 @@ export default class OrderEditService extends TransactionBaseService {
return orderEdit
}
async deleteItemChange(
orderEditId: string,
itemChangeId: string
): Promise<void> {
return await this.atomicPhase_(async (manager) => {
const itemChange = await this.orderEditItemChangeService_.retrieve(
itemChangeId,
{ select: ["id", "order_edit_id"] }
)
const orderEdit = await this.retrieve(orderEditId, {
select: ["id", "confirmed_at", "canceled_at"],
})
if (orderEdit.id !== itemChange.order_edit_id) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`The item change you are trying to delete doesn't belong to the OrderEdit with id: ${orderEditId}.`
)
}
if (orderEdit.confirmed_at !== null || orderEdit.canceled_at !== null) {
throw new MedusaError(
MedusaError.Types.NOT_ALLOWED,
`Cannot delete and item change from a ${orderEdit.status} order edit`
)
}
return await this.orderEditItemChangeService_.delete(itemChangeId)
})
}
}
+17 -7
View File
@@ -1,19 +1,19 @@
import { MedusaError } from "medusa-core-utils"
import { AwilixContainer } from "awilix"
import { EntityManager } from "typeorm"
import { EntityManager, In } from "typeorm"
import Redis from "ioredis"
import { LineItemTaxLineRepository } from "../repositories/line-item-tax-line"
import { ShippingMethodTaxLineRepository } from "../repositories/shipping-method-tax-line"
import { TaxProviderRepository } from "../repositories/tax-provider"
import {
LineItemTaxLine,
TaxProvider,
LineItem,
ShippingMethodTaxLine,
ShippingMethod,
Region,
Cart,
LineItem,
LineItemTaxLine,
Region,
ShippingMethod,
ShippingMethodTaxLine,
TaxProvider,
} from "../models"
import { isCart } from "../types/cart"
import {
@@ -91,6 +91,16 @@ class TaxProviderService extends TransactionBaseService {
return provider
}
async clearLineItemsTaxLines(itemIds: string[]): Promise<void> {
return await this.atomicPhase_(async (transactionManager) => {
const taxLineRepo = transactionManager.getCustomRepository(
this.taxLineRepo_
)
await taxLineRepo.delete({ item_id: In(itemIds) })
})
}
async clearTaxLines(cartId: string): Promise<void> {
return await this.atomicPhase_(async (transactionManager) => {
const taxLineRepo = transactionManager.getCustomRepository(