feat(medusa, medusa-js, medusa-react): Implement item change deletion from an order edit (#2241)
This commit is contained in:
1 parent
eb8034502b
commit
14e808c724
18 files changed
+700
-17
No files matched your search
+51
@@ -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 () => {
|
||||
|
||||
@@ -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 })
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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(
|
||||
|
||||
Reference in new issue
Block a user