feat: add a line item to an order edit (#2243)
**What**
- Implement adding a line item to order (edit)
**How**
- _by implementing the following "flow"_
- generate a line item
- computing line item adjustments for that line item
- creating tax lines
- creating a change record
**Testing**
- **_integration tests_**
- check if line item and order item change objects are created (with correct tax lines)
- line item adjustments are generated if
- fixed discount is applied to cart
- percentage discount is applied
- **_unit tests_**
- ensure that methods from Inventory, LineItem, LineItemAdjustment etc. services are called
---
RESOLVES CORE-495
This commit is contained in:
@@ -17,8 +17,8 @@ const {
|
|||||||
simpleProductFactory,
|
simpleProductFactory,
|
||||||
simpleOrderFactory,
|
simpleOrderFactory,
|
||||||
simpleDiscountFactory,
|
simpleDiscountFactory,
|
||||||
simpleRegionFactory,
|
|
||||||
simpleCartFactory,
|
simpleCartFactory,
|
||||||
|
simpleRegionFactory,
|
||||||
} = require("../../factories")
|
} = require("../../factories")
|
||||||
const { OrderEditItemChangeType, OrderEdit } = require("@medusajs/medusa")
|
const { OrderEditItemChangeType, OrderEdit } = require("@medusajs/medusa")
|
||||||
|
|
||||||
@@ -775,6 +775,327 @@ describe("[MEDUSA_FF_ORDER_EDITING] /admin/order-edits", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("POST /admin/order-edits/:id/items", () => {
|
||||||
|
const orderEditId = IdMap.getId("order-edit-1")
|
||||||
|
const prodId1 = IdMap.getId("prodId1")
|
||||||
|
const lineItemId1 = IdMap.getId("line-item-1")
|
||||||
|
const orderId1 = IdMap.getId("order-id-1")
|
||||||
|
const toBeAddedVariantId = IdMap.getId("variant id")
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await adminSeeder(dbConnection)
|
||||||
|
|
||||||
|
const product1 = await simpleProductFactory(dbConnection, {
|
||||||
|
id: prodId1,
|
||||||
|
})
|
||||||
|
|
||||||
|
const toBeAddedProduct = await simpleProductFactory(dbConnection, {
|
||||||
|
variants: [
|
||||||
|
{
|
||||||
|
id: toBeAddedVariantId,
|
||||||
|
prices: [{ currency: "usd", amount: 200 }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
const order = await simpleOrderFactory(dbConnection, {
|
||||||
|
id: orderId1,
|
||||||
|
fulfillment_status: "fulfilled",
|
||||||
|
payment_status: "captured",
|
||||||
|
region: {
|
||||||
|
id: "test-region",
|
||||||
|
name: "Test region",
|
||||||
|
tax_rate: 12.5,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
await simpleOrderEditFactory(dbConnection, {
|
||||||
|
id: orderEditId,
|
||||||
|
order_id: order.id,
|
||||||
|
created_by: "admin_user",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
const db = useDb()
|
||||||
|
return await db.teardown()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("creates line item that will be added to the order", async () => {
|
||||||
|
const api = useApi()
|
||||||
|
|
||||||
|
const response = await api.post(
|
||||||
|
`/admin/order-edits/${orderEditId}/items`,
|
||||||
|
{ variant_id: toBeAddedVariantId, quantity: 2 },
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.status).toEqual(200)
|
||||||
|
expect(response.data.order_edit).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: orderEditId,
|
||||||
|
created_by: "admin_user",
|
||||||
|
requested_by: null,
|
||||||
|
canceled_by: null,
|
||||||
|
confirmed_by: null,
|
||||||
|
// "Add item" change has been created
|
||||||
|
changes: [
|
||||||
|
expect.objectContaining({
|
||||||
|
type: "item_add",
|
||||||
|
order_edit_id: orderEditId,
|
||||||
|
original_line_item_id: null,
|
||||||
|
line_item_id: expect.any(String),
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
items: expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
variant: expect.objectContaining({ id: toBeAddedVariantId }),
|
||||||
|
quantity: 2,
|
||||||
|
order_id: null, // <-- NOT associated with the order at this point
|
||||||
|
tax_lines: [
|
||||||
|
expect.objectContaining({
|
||||||
|
rate: 12.5,
|
||||||
|
name: "default",
|
||||||
|
code: "default",
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
/*
|
||||||
|
* Computed totals are appended to the response
|
||||||
|
*/
|
||||||
|
discount_total: 0,
|
||||||
|
gift_card_total: 0,
|
||||||
|
gift_card_tax_total: 0,
|
||||||
|
shipping_total: 0,
|
||||||
|
subtotal: 2 * 200,
|
||||||
|
tax_total: 0.125 * 2 * 200,
|
||||||
|
total: 400 + 50,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("adding line item to the order edit will create adjustments percentage discount", async () => {
|
||||||
|
const api = useApi()
|
||||||
|
|
||||||
|
const region = await simpleRegionFactory(dbConnection, { tax_rate: 10 })
|
||||||
|
|
||||||
|
const initialProduct = await simpleProductFactory(dbConnection, {
|
||||||
|
variants: [{ id: "initial-variant" }],
|
||||||
|
})
|
||||||
|
|
||||||
|
const toBeAddedProduct = await simpleProductFactory(dbConnection, {
|
||||||
|
variants: [
|
||||||
|
{
|
||||||
|
id: toBeAddedVariantId,
|
||||||
|
prices: [{ currency: "usd", amount: 200 }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
const discount = await simpleDiscountFactory(dbConnection, {
|
||||||
|
code: "20PERCENT",
|
||||||
|
rule: {
|
||||||
|
type: "percentage",
|
||||||
|
allocation: "item",
|
||||||
|
value: 20,
|
||||||
|
},
|
||||||
|
regions: [region.id],
|
||||||
|
})
|
||||||
|
|
||||||
|
const cart = await simpleCartFactory(dbConnection, {
|
||||||
|
email: "testy@test.com",
|
||||||
|
region: region.id,
|
||||||
|
line_items: [
|
||||||
|
{ variant_id: initialProduct.variants[0].id, quantity: 1 },
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
// Apply the discount on the cart and complete the cart to create an order.
|
||||||
|
|
||||||
|
await api.post(`/store/carts/${cart.id}`, {
|
||||||
|
discounts: [{ code: "20PERCENT" }],
|
||||||
|
})
|
||||||
|
|
||||||
|
await api.post(`/store/carts/${cart.id}/payment-sessions`)
|
||||||
|
|
||||||
|
const completeRes = await api.post(`/store/carts/${cart.id}/complete`)
|
||||||
|
|
||||||
|
const orderWithDiscount = completeRes.data.data
|
||||||
|
|
||||||
|
// Create an order edit for the created order
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: { order_edit },
|
||||||
|
} = await api.post(
|
||||||
|
`/admin/order-edits/`,
|
||||||
|
{
|
||||||
|
order_id: orderWithDiscount.id,
|
||||||
|
},
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
const response = await api.post(
|
||||||
|
`/admin/order-edits/${order_edit.id}/items`,
|
||||||
|
{ variant_id: toBeAddedVariantId, quantity: 2 },
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.status).toEqual(200)
|
||||||
|
expect(response.data.order_edit).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
order_id: orderWithDiscount.id,
|
||||||
|
items: expect.arrayContaining([
|
||||||
|
// New line item
|
||||||
|
expect.objectContaining({
|
||||||
|
adjustments: [
|
||||||
|
expect.objectContaining({
|
||||||
|
discount_id: discount.id,
|
||||||
|
amount: 80,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
tax_lines: [expect.objectContaining({ rate: 10 })],
|
||||||
|
unit_price: 200,
|
||||||
|
quantity: 2,
|
||||||
|
}),
|
||||||
|
// Already existing line item
|
||||||
|
expect.objectContaining({
|
||||||
|
adjustments: [
|
||||||
|
expect.objectContaining({
|
||||||
|
discount_id: discount.id,
|
||||||
|
amount: 20,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
tax_lines: [expect.objectContaining({ rate: 10 })],
|
||||||
|
unit_price: 100,
|
||||||
|
quantity: 1,
|
||||||
|
variant: expect.objectContaining({
|
||||||
|
id: initialProduct.variants[0].id,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
gift_card_total: 0,
|
||||||
|
gift_card_tax_total: 0,
|
||||||
|
shipping_total: 0,
|
||||||
|
subtotal: 500, // 1 * 100$ + 2 * 200$
|
||||||
|
discount_total: 100, // discount === 20%
|
||||||
|
tax_total: 40, // tax rate === 10%
|
||||||
|
total: 440,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("adding line item to the order edit will create adjustments for fixed discount case", async () => {
|
||||||
|
const api = useApi()
|
||||||
|
|
||||||
|
const region = await simpleRegionFactory(dbConnection, { tax_rate: 10 })
|
||||||
|
|
||||||
|
const initialProduct = await simpleProductFactory(dbConnection, {
|
||||||
|
variants: [{ id: "initial-variant" }],
|
||||||
|
})
|
||||||
|
|
||||||
|
const toBeAddedProduct = await simpleProductFactory(dbConnection, {
|
||||||
|
variants: [
|
||||||
|
{
|
||||||
|
id: toBeAddedVariantId,
|
||||||
|
prices: [{ currency: "usd", amount: 200 }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
const discount = await simpleDiscountFactory(dbConnection, {
|
||||||
|
code: "30FIXED",
|
||||||
|
rule: {
|
||||||
|
type: "fixed",
|
||||||
|
value: 30,
|
||||||
|
},
|
||||||
|
regions: [region.id],
|
||||||
|
})
|
||||||
|
|
||||||
|
const cart = await simpleCartFactory(dbConnection, {
|
||||||
|
email: "testy@test.com",
|
||||||
|
region: region.id,
|
||||||
|
line_items: [
|
||||||
|
{ variant_id: initialProduct.variants[0].id, quantity: 1 },
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
// Apply the discount on the cart and complete the cart to create an order.
|
||||||
|
|
||||||
|
await api.post(`/store/carts/${cart.id}`, {
|
||||||
|
discounts: [{ code: "30FIXED" }],
|
||||||
|
})
|
||||||
|
|
||||||
|
await api.post(`/store/carts/${cart.id}/payment-sessions`)
|
||||||
|
|
||||||
|
const completeRes = await api.post(`/store/carts/${cart.id}/complete`)
|
||||||
|
|
||||||
|
const orderWithDiscount = completeRes.data.data
|
||||||
|
|
||||||
|
// all fixed discount is allocated to single initial line item
|
||||||
|
expect(orderWithDiscount.items[0].adjustments[0].amount).toEqual(30)
|
||||||
|
|
||||||
|
// Create an order edit for the created order
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: { order_edit },
|
||||||
|
} = await api.post(
|
||||||
|
`/admin/order-edits/`,
|
||||||
|
{
|
||||||
|
order_id: orderWithDiscount.id,
|
||||||
|
},
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
const response = await api.post(
|
||||||
|
`/admin/order-edits/${order_edit.id}/items`,
|
||||||
|
{ variant_id: toBeAddedVariantId, quantity: 2 },
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.status).toEqual(200)
|
||||||
|
expect(response.data.order_edit).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
order_id: orderWithDiscount.id,
|
||||||
|
items: expect.arrayContaining([
|
||||||
|
// New line item
|
||||||
|
expect.objectContaining({
|
||||||
|
adjustments: [
|
||||||
|
expect.objectContaining({
|
||||||
|
discount_id: discount.id,
|
||||||
|
amount: 24,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
unit_price: 200,
|
||||||
|
quantity: 2,
|
||||||
|
}),
|
||||||
|
// Already existing line item
|
||||||
|
expect.objectContaining({
|
||||||
|
adjustments: [
|
||||||
|
expect.objectContaining({
|
||||||
|
discount_id: discount.id,
|
||||||
|
amount: 6,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
unit_price: 100,
|
||||||
|
quantity: 1,
|
||||||
|
variant: expect.objectContaining({
|
||||||
|
id: initialProduct.variants[0].id,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
gift_card_total: 0,
|
||||||
|
gift_card_tax_total: 0,
|
||||||
|
shipping_total: 0,
|
||||||
|
subtotal: 500, // 1 * 100$ + 2 * 200$
|
||||||
|
discount_total: 30, // discount === fixed 30
|
||||||
|
tax_total: 47, // tax rate === 10%
|
||||||
|
total: 470 + 47,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe("DELETE /admin/order-edits/:id/changes/:change_id", () => {
|
describe("DELETE /admin/order-edits/:id/changes/:change_id", () => {
|
||||||
let product
|
let product
|
||||||
const orderId1 = IdMap.getId("order-id-1")
|
const orderId1 = IdMap.getId("order-id-1")
|
||||||
@@ -1575,17 +1896,12 @@ describe("[MEDUSA_FF_ORDER_EDITING] /admin/order-edits", () => {
|
|||||||
(item) => item.original_item_id === lineItemId1
|
(item) => item.original_item_id === lineItemId1
|
||||||
).id
|
).id
|
||||||
|
|
||||||
await api.post(
|
let response = await api.post(
|
||||||
`/admin/order-edits/${orderEditId}/items/${updateItemId}`,
|
`/admin/order-edits/${orderEditId}/items/${updateItemId}`,
|
||||||
{ quantity: 2 },
|
{ quantity: 2 },
|
||||||
adminHeaders
|
adminHeaders
|
||||||
)
|
)
|
||||||
|
|
||||||
let response = await api.get(
|
|
||||||
`/admin/order-edits/${orderEditId}?expand=changes,items,items.tax_lines,items.adjustments`,
|
|
||||||
adminHeaders
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(response.status).toEqual(200)
|
expect(response.status).toEqual(200)
|
||||||
expect(response.data.order_edit.changes).toHaveLength(1)
|
expect(response.data.order_edit.changes).toHaveLength(1)
|
||||||
|
|
||||||
@@ -1696,17 +2012,12 @@ describe("[MEDUSA_FF_ORDER_EDITING] /admin/order-edits", () => {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
await api.post(
|
response = await api.post(
|
||||||
`/admin/order-edits/${orderEditId}/items/${updateItemId}`,
|
`/admin/order-edits/${orderEditId}/items/${updateItemId}`,
|
||||||
{ quantity: 3 },
|
{ quantity: 3 },
|
||||||
adminHeaders
|
adminHeaders
|
||||||
)
|
)
|
||||||
|
|
||||||
response = await api.get(
|
|
||||||
`/admin/order-edits/${orderEditId}?expand=changes,items,items.tax_lines,items.adjustments`,
|
|
||||||
adminHeaders
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(response.status).toEqual(200)
|
expect(response.status).toEqual(200)
|
||||||
expect(response.data.order_edit.changes).toHaveLength(1)
|
expect(response.data.order_edit.changes).toHaveLength(1)
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,10 @@ import {
|
|||||||
AdminOrderEditDeleteRes,
|
AdminOrderEditDeleteRes,
|
||||||
AdminOrderEditItemChangeDeleteRes,
|
AdminOrderEditItemChangeDeleteRes,
|
||||||
AdminOrderEditsRes,
|
AdminOrderEditsRes,
|
||||||
AdminPostOrderEditsEditLineItemsLineItemReq,
|
|
||||||
AdminPostOrderEditsOrderEditReq,
|
AdminPostOrderEditsOrderEditReq,
|
||||||
AdminPostOrderEditsReq,
|
AdminPostOrderEditsReq,
|
||||||
|
AdminPostOrderEditsEditLineItemsReq,
|
||||||
|
AdminPostOrderEditsEditLineItemsLineItemReq,
|
||||||
} from "@medusajs/medusa"
|
} from "@medusajs/medusa"
|
||||||
import { ResponsePromise } from "../../typings"
|
import { ResponsePromise } from "../../typings"
|
||||||
import BaseResource from "../base"
|
import BaseResource from "../base"
|
||||||
@@ -43,6 +44,15 @@ class AdminOrderEditsResource extends BaseResource {
|
|||||||
return this.client.request("DELETE", path, undefined, {}, customHeaders)
|
return this.client.request("DELETE", path, undefined, {}, customHeaders)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addLineItem(
|
||||||
|
id: string,
|
||||||
|
payload: AdminPostOrderEditsEditLineItemsReq,
|
||||||
|
customHeaders: Record<string, any> = {}
|
||||||
|
): ResponsePromise<AdminOrderEditsRes> {
|
||||||
|
const path = `/admin/order-edits/${id}/items`
|
||||||
|
return this.client.request("POST", path, payload, {}, customHeaders)
|
||||||
|
}
|
||||||
|
|
||||||
deleteItemChange(
|
deleteItemChange(
|
||||||
orderEditId: string,
|
orderEditId: string,
|
||||||
itemChangeId: string,
|
itemChangeId: string,
|
||||||
|
|||||||
@@ -1705,6 +1705,15 @@ export const adminHandlers = [
|
|||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
rest.post("/admin/order-edits/:id/items", (req, res, ctx) => {
|
||||||
|
return res(
|
||||||
|
ctx.status(200),
|
||||||
|
ctx.json({
|
||||||
|
order_edit: { ...fixtures.get("order_edit"), ...(req.body as any) },
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
|
||||||
rest.post("/admin/order-edits/:id/request", (req, res, ctx) => {
|
rest.post("/admin/order-edits/:id/request", (req, res, ctx) => {
|
||||||
return res(
|
return res(
|
||||||
ctx.status(200),
|
ctx.status(200),
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
AdminPostOrderEditsEditLineItemsLineItemReq,
|
AdminPostOrderEditsEditLineItemsLineItemReq,
|
||||||
AdminPostOrderEditsOrderEditReq,
|
AdminPostOrderEditsOrderEditReq,
|
||||||
AdminPostOrderEditsReq,
|
AdminPostOrderEditsReq,
|
||||||
|
AdminPostOrderEditsEditLineItemsReq,
|
||||||
} from "@medusajs/medusa"
|
} from "@medusajs/medusa"
|
||||||
|
|
||||||
import { buildOptions } from "../../utils/buildOptions"
|
import { buildOptions } from "../../utils/buildOptions"
|
||||||
@@ -114,6 +115,27 @@ export const useAdminUpdateOrderEdit = (
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const useAdminOrderEditLineItem = (
|
||||||
|
id: string,
|
||||||
|
options?: UseMutationOptions<
|
||||||
|
Response<AdminOrderEditsRes>,
|
||||||
|
Error,
|
||||||
|
AdminPostOrderEditsEditLineItemsReq
|
||||||
|
>
|
||||||
|
) => {
|
||||||
|
const { client } = useMedusa()
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
return useMutation(
|
||||||
|
(payload: AdminPostOrderEditsEditLineItemsReq) =>
|
||||||
|
client.admin.orderEdits.addLineItem(id, payload),
|
||||||
|
buildOptions(
|
||||||
|
queryClient,
|
||||||
|
[adminOrderEditsKeys.lists(), adminOrderEditsKeys.detail(id)],
|
||||||
|
options
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export const useAdminRequestOrderEditConfirmation = (
|
export const useAdminRequestOrderEditConfirmation = (
|
||||||
id: string,
|
id: string,
|
||||||
options?: UseMutationOptions<Response<AdminOrderEditsRes>, Error>
|
options?: UseMutationOptions<Response<AdminOrderEditsRes>, Error>
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import {
|
|||||||
useAdminDeleteOrderEditItemChange,
|
useAdminDeleteOrderEditItemChange,
|
||||||
useAdminOrderEditUpdateLineItem,
|
useAdminOrderEditUpdateLineItem,
|
||||||
useAdminRequestOrderEditConfirmation,
|
useAdminRequestOrderEditConfirmation,
|
||||||
|
useAdminOrderEditLineItem,
|
||||||
|
useAdminCancelOrderEdit,
|
||||||
useAdminUpdateOrderEdit,
|
useAdminUpdateOrderEdit,
|
||||||
} from "../../../../src/"
|
} from "../../../../src/"
|
||||||
import { fixtures } from "../../../../mocks/data"
|
import { fixtures } from "../../../../mocks/data"
|
||||||
@@ -163,6 +165,36 @@ describe("useAdminRequestOrderEditConfirmation hook", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("useAdminOrderEditLineItem hook", () => {
|
||||||
|
test("Created an order edit line item", async () => {
|
||||||
|
const { result, waitFor } = renderHook(
|
||||||
|
() => useAdminOrderEditLineItem(fixtures.get("order_edit").id),
|
||||||
|
{
|
||||||
|
wrapper: createWrapper(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
variant_id: "var_1",
|
||||||
|
quantity: 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
result.current.mutate(payload)
|
||||||
|
|
||||||
|
await waitFor(() => result.current.isSuccess)
|
||||||
|
|
||||||
|
expect(result.current.data.response.status).toEqual(200)
|
||||||
|
expect(result.current.data).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
order_edit: {
|
||||||
|
...fixtures.get("order_edit"),
|
||||||
|
...payload,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe("useAdminCancelOrderEdit hook", () => {
|
describe("useAdminCancelOrderEdit hook", () => {
|
||||||
test("cancel an order edit", async () => {
|
test("cancel an order edit", async () => {
|
||||||
const { result, waitFor } = renderHook(
|
const { result, waitFor } = renderHook(
|
||||||
|
|||||||
@@ -3,13 +3,7 @@ import {
|
|||||||
DraftOrderService,
|
DraftOrderService,
|
||||||
LineItemService,
|
LineItemService,
|
||||||
} from "../../../../services"
|
} from "../../../../services"
|
||||||
import {
|
import { IsInt, IsObject, IsOptional, IsString } from "class-validator"
|
||||||
IsBoolean,
|
|
||||||
IsInt,
|
|
||||||
IsObject,
|
|
||||||
IsOptional,
|
|
||||||
IsString,
|
|
||||||
} from "class-validator"
|
|
||||||
import {
|
import {
|
||||||
defaultAdminDraftOrdersCartFields,
|
defaultAdminDraftOrdersCartFields,
|
||||||
defaultAdminDraftOrdersCartRelations,
|
defaultAdminDraftOrdersCartRelations,
|
||||||
@@ -17,7 +11,6 @@ import {
|
|||||||
} from "."
|
} from "."
|
||||||
|
|
||||||
import { EntityManager } from "typeorm"
|
import { EntityManager } from "typeorm"
|
||||||
import { FlagRouter } from "../../../../utils/flag-router"
|
|
||||||
import { MedusaError } from "medusa-core-utils"
|
import { MedusaError } from "medusa-core-utils"
|
||||||
import { validator } from "../../../../utils/validator"
|
import { validator } from "../../../../utils/validator"
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
import { Request, Response } from "express"
|
||||||
|
import { IsInt, IsOptional, IsString } from "class-validator"
|
||||||
|
import { EntityManager } from "typeorm"
|
||||||
|
|
||||||
|
import { OrderEditService } from "../../../../services"
|
||||||
|
import {
|
||||||
|
defaultOrderEditFields,
|
||||||
|
defaultOrderEditRelations,
|
||||||
|
} from "../../../../types/order-edit"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @oas [post] /order-edits/{id}/items
|
||||||
|
* operationId: "PostOrderEditsEditLineItems"
|
||||||
|
* summary: "Add an line item to an order (edit)"
|
||||||
|
* description: "Create an OrderEdit LineItem."
|
||||||
|
* parameters:
|
||||||
|
* - (path) id=* {string} The ID of the Order Edit.
|
||||||
|
* x-authenticated: true
|
||||||
|
* 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.orderEdit.addLineItem(order_edit_id, { variant_id, quantity })
|
||||||
|
* .then(({ order_edit }) => {
|
||||||
|
* console.log(order_edit.id)
|
||||||
|
* })
|
||||||
|
* - lang: Shell
|
||||||
|
* label: cURL
|
||||||
|
* source: |
|
||||||
|
* curl --location --request POST 'https://medusa-url.com/admin/order-edits/{id}/items' \
|
||||||
|
* --header 'Authorization: Bearer {api_token}'
|
||||||
|
* -d '{ "variant_id": "some_variant_id", "quantity": 3 }'
|
||||||
|
* security:
|
||||||
|
* - api_token: []
|
||||||
|
* - cookie_auth: []
|
||||||
|
* tags:
|
||||||
|
* - OrderEdit
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: OK
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* properties:
|
||||||
|
* order_edit:
|
||||||
|
* $ref: "#/components/schemas/order_edit"
|
||||||
|
* "400":
|
||||||
|
* $ref: "#/components/responses/400_error"
|
||||||
|
* "401":
|
||||||
|
* $ref: "#/components/responses/unauthorized"
|
||||||
|
* "404":
|
||||||
|
* $ref: "#/components/responses/not_found_error"
|
||||||
|
* "409":
|
||||||
|
* $ref: "#/components/responses/invalid_state_error"
|
||||||
|
* "422":
|
||||||
|
* $ref: "#/components/responses/invalid_request_error"
|
||||||
|
* "500":
|
||||||
|
* $ref: "#/components/responses/500_error"
|
||||||
|
*/
|
||||||
|
export default async (req: Request, res: Response) => {
|
||||||
|
const orderEditService = req.scope.resolve(
|
||||||
|
"orderEditService"
|
||||||
|
) as OrderEditService
|
||||||
|
|
||||||
|
const { id } = req.params
|
||||||
|
|
||||||
|
const manager = req.scope.resolve("manager") as EntityManager
|
||||||
|
|
||||||
|
const data = req.validatedBody as AdminPostOrderEditsEditLineItemsReq
|
||||||
|
|
||||||
|
await manager.transaction(async (transactionManager) => {
|
||||||
|
await orderEditService
|
||||||
|
.withTransaction(transactionManager)
|
||||||
|
.addLineItem(id, data)
|
||||||
|
})
|
||||||
|
|
||||||
|
let orderEdit = await orderEditService.retrieve(id, {
|
||||||
|
select: defaultOrderEditFields,
|
||||||
|
relations: defaultOrderEditRelations,
|
||||||
|
})
|
||||||
|
|
||||||
|
orderEdit = await orderEditService.decorateTotals(orderEdit)
|
||||||
|
|
||||||
|
res.status(200).send({
|
||||||
|
order_edit: orderEdit,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AdminPostOrderEditsEditLineItemsReq {
|
||||||
|
@IsString()
|
||||||
|
variant_id: string
|
||||||
|
|
||||||
|
@IsInt()
|
||||||
|
quantity: number
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
metadata?: Record<string, unknown> | undefined
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@ import { OrderEditService } from "../../../../services"
|
|||||||
* description: "Deletes an Order Edit"
|
* description: "Deletes an Order Edit"
|
||||||
* x-authenticated: true
|
* x-authenticated: true
|
||||||
* parameters:
|
* parameters:
|
||||||
* - (path) id=* {string} The ID of the Note to delete.
|
* - (path) id=* {string} The ID of the Order Edit to delete.
|
||||||
* x-codeSamples:
|
* x-codeSamples:
|
||||||
* - lang: JavaScript
|
* - lang: JavaScript
|
||||||
* label: JS Client
|
* label: JS Client
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
import { OrderEdit } from "../../../../models"
|
import { OrderEdit } from "../../../../models"
|
||||||
import { AdminPostOrderEditsOrderEditReq } from "./update-order-edit"
|
import { AdminPostOrderEditsOrderEditReq } from "./update-order-edit"
|
||||||
import { AdminPostOrderEditsReq } from "./create-order-edit"
|
import { AdminPostOrderEditsReq } from "./create-order-edit"
|
||||||
|
import { AdminPostOrderEditsEditLineItemsReq } from "./add-line-item"
|
||||||
import { AdminPostOrderEditsEditLineItemsLineItemReq } from "./update-order-edit-line-item"
|
import { AdminPostOrderEditsEditLineItemsLineItemReq } from "./update-order-edit-line-item"
|
||||||
|
|
||||||
const route = Router()
|
const route = Router()
|
||||||
@@ -52,6 +53,12 @@ export default (app) => {
|
|||||||
middlewares.wrap(require("./cancel-order-edit").default)
|
middlewares.wrap(require("./cancel-order-edit").default)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
route.post(
|
||||||
|
"/:id/items",
|
||||||
|
transformBody(AdminPostOrderEditsEditLineItemsReq),
|
||||||
|
middlewares.wrap(require("./add-line-item").default)
|
||||||
|
)
|
||||||
|
|
||||||
route.delete("/:id", middlewares.wrap(require("./delete-order-edit").default))
|
route.delete("/:id", middlewares.wrap(require("./delete-order-edit").default))
|
||||||
|
|
||||||
route.delete(
|
route.delete(
|
||||||
@@ -86,3 +93,5 @@ export type AdminOrderEditItemChangeDeleteRes = {
|
|||||||
export * from "./update-order-edit"
|
export * from "./update-order-edit"
|
||||||
export * from "./update-order-edit-line-item"
|
export * from "./update-order-edit-line-item"
|
||||||
export * from "./create-order-edit"
|
export * from "./create-order-edit"
|
||||||
|
|
||||||
|
export * from "./add-line-item"
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ export const orderEditItemChangeServiceMock = {
|
|||||||
order_edit_id: orderEditId,
|
order_edit_id: orderEditId,
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
|
create: jest.fn().mockImplementation((data) => {
|
||||||
|
return Promise.resolve(data)
|
||||||
|
}),
|
||||||
delete: jest.fn().mockImplementation(() => {
|
delete: jest.fn().mockImplementation(() => {
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import LineItemService from "../line-item"
|
|||||||
import { PricingServiceMock } from "../__mocks__/pricing"
|
import { PricingServiceMock } from "../__mocks__/pricing"
|
||||||
import { ProductVariantServiceMock } from "../__mocks__/product-variant"
|
import { ProductVariantServiceMock } from "../__mocks__/product-variant"
|
||||||
import { RegionServiceMock } from "../__mocks__/region"
|
import { RegionServiceMock } from "../__mocks__/region"
|
||||||
|
|
||||||
;[true, false].forEach((isTaxInclusiveEnabled) => {
|
;[true, false].forEach((isTaxInclusiveEnabled) => {
|
||||||
describe(`tax inclusive flag set to: ${isTaxInclusiveEnabled}`, () => {
|
describe(`tax inclusive flag set to: ${isTaxInclusiveEnabled}`, () => {
|
||||||
describe("LineItemService", () => {
|
describe("LineItemService", () => {
|
||||||
|
|||||||
@@ -77,6 +77,17 @@ const orderEditWithChanges = {
|
|||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const orderEditWithAddedLineItem = {
|
||||||
|
id: IdMap.getId("order-edit-with-changes"),
|
||||||
|
order: {
|
||||||
|
id: IdMap.getId("order-edit-change"),
|
||||||
|
cart: {
|
||||||
|
discounts: [{ rule: {} }],
|
||||||
|
},
|
||||||
|
region: { id: IdMap.getId("test-region") },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
const lineItemServiceMock = {
|
const lineItemServiceMock = {
|
||||||
...LineItemServiceMock,
|
...LineItemServiceMock,
|
||||||
list: jest.fn().mockImplementation(() => {
|
list: jest.fn().mockImplementation(() => {
|
||||||
@@ -179,9 +190,9 @@ describe("OrderEditService", () => {
|
|||||||
lineItemService: lineItemServiceMock as unknown as LineItemService,
|
lineItemService: lineItemServiceMock as unknown as LineItemService,
|
||||||
orderEditItemChangeService:
|
orderEditItemChangeService:
|
||||||
orderEditItemChangeServiceMock as unknown as OrderEditItemChangeService,
|
orderEditItemChangeServiceMock as unknown as OrderEditItemChangeService,
|
||||||
taxProviderService: taxProviderServiceMock as unknown as TaxProviderService,
|
|
||||||
lineItemAdjustmentService:
|
lineItemAdjustmentService:
|
||||||
LineItemAdjustmentServiceMock as unknown as LineItemAdjustmentService,
|
LineItemAdjustmentServiceMock as unknown as LineItemAdjustmentService,
|
||||||
|
taxProviderService: taxProviderServiceMock as unknown as TaxProviderService,
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should retrieve an order edit and call the repository with the right arguments", async () => {
|
it("should retrieve an order edit and call the repository with the right arguments", async () => {
|
||||||
@@ -392,4 +403,20 @@ describe("OrderEditService", () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should add a line item to an order edit", async () => {
|
||||||
|
jest
|
||||||
|
.spyOn(orderEditService, "refreshAdjustments")
|
||||||
|
.mockImplementation(async () => {})
|
||||||
|
|
||||||
|
await orderEditService.addLineItem(IdMap.getId("order-edit-with-changes"), {
|
||||||
|
variant_id: IdMap.getId("to-be-added-variant"),
|
||||||
|
quantity: 3,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(LineItemServiceMock.generate).toHaveBeenCalledTimes(1)
|
||||||
|
expect(orderEditService.refreshAdjustments).toHaveBeenCalledTimes(1)
|
||||||
|
expect(taxProviderServiceMock.createTaxLines).toHaveBeenCalledTimes(1)
|
||||||
|
expect(orderEditItemChangeServiceMock.create).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export { default as GiftCardService } from "./gift-card"
|
|||||||
export { default as IdempotencyKeyService } from "./idempotency-key"
|
export { default as IdempotencyKeyService } from "./idempotency-key"
|
||||||
export { default as InventoryService } from "./inventory"
|
export { default as InventoryService } from "./inventory"
|
||||||
export { default as LineItemService } from "./line-item"
|
export { default as LineItemService } from "./line-item"
|
||||||
|
export { default as LineItemAdjustmentService } from "./line-item-adjustment"
|
||||||
export { default as MiddlewareService } from "./middleware"
|
export { default as MiddlewareService } from "./middleware"
|
||||||
export { default as NoteService } from "./note"
|
export { default as NoteService } from "./note"
|
||||||
export { default as NotificationService } from "./notification"
|
export { default as NotificationService } from "./notification"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { MedusaError } from "medusa-core-utils"
|
import { MedusaError } from "medusa-core-utils"
|
||||||
import { BaseService } from "medusa-interfaces"
|
|
||||||
import { EntityManager, In } from "typeorm"
|
import { EntityManager, In } from "typeorm"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Cart,
|
Cart,
|
||||||
DiscountRuleType,
|
DiscountRuleType,
|
||||||
@@ -12,6 +12,8 @@ import { LineItemAdjustmentRepository } from "../repositories/line-item-adjustme
|
|||||||
import { FindConfig } from "../types/common"
|
import { FindConfig } from "../types/common"
|
||||||
import { FilterableLineItemAdjustmentProps } from "../types/line-item-adjustment"
|
import { FilterableLineItemAdjustmentProps } from "../types/line-item-adjustment"
|
||||||
import DiscountService from "./discount"
|
import DiscountService from "./discount"
|
||||||
|
import { TransactionBaseService } from "../interfaces"
|
||||||
|
import { buildQuery, setMetadata } from "../utils"
|
||||||
|
|
||||||
type LineItemAdjustmentServiceProps = {
|
type LineItemAdjustmentServiceProps = {
|
||||||
manager: EntityManager
|
manager: EntityManager
|
||||||
@@ -23,46 +25,36 @@ type AdjustmentContext = {
|
|||||||
variant: ProductVariant
|
variant: ProductVariant
|
||||||
}
|
}
|
||||||
|
|
||||||
type GeneratedAdjustment = Omit<LineItem, "id" | "item_id">
|
type GeneratedAdjustment = {
|
||||||
|
amount: number
|
||||||
|
discount_id: string
|
||||||
|
description: string
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides layer to manipulate line item adjustments.
|
* Provides layer to manipulate line item adjustments.
|
||||||
* @extends BaseService
|
* @extends BaseService
|
||||||
*/
|
*/
|
||||||
class LineItemAdjustmentService extends BaseService {
|
class LineItemAdjustmentService extends TransactionBaseService {
|
||||||
private manager_: EntityManager
|
protected readonly manager_: EntityManager
|
||||||
private lineItemAdjustmentRepo_: typeof LineItemAdjustmentRepository
|
protected transactionManager_: EntityManager | undefined
|
||||||
private discountService: DiscountService
|
|
||||||
|
private readonly lineItemAdjustmentRepo_: typeof LineItemAdjustmentRepository
|
||||||
|
private readonly discountService: DiscountService
|
||||||
|
|
||||||
constructor({
|
constructor({
|
||||||
manager,
|
manager,
|
||||||
lineItemAdjustmentRepository,
|
lineItemAdjustmentRepository,
|
||||||
discountService,
|
discountService,
|
||||||
}: LineItemAdjustmentServiceProps) {
|
}: LineItemAdjustmentServiceProps) {
|
||||||
super()
|
// eslint-disable-next-line prefer-rest-params
|
||||||
|
super(arguments[0])
|
||||||
|
|
||||||
this.manager_ = manager
|
this.manager_ = manager
|
||||||
this.lineItemAdjustmentRepo_ = lineItemAdjustmentRepository
|
this.lineItemAdjustmentRepo_ = lineItemAdjustmentRepository
|
||||||
this.discountService = discountService
|
this.discountService = discountService
|
||||||
}
|
}
|
||||||
|
|
||||||
withTransaction(
|
|
||||||
transactionManager: EntityManager
|
|
||||||
): LineItemAdjustmentService {
|
|
||||||
if (!transactionManager) {
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
const cloned = new LineItemAdjustmentService({
|
|
||||||
manager: transactionManager,
|
|
||||||
lineItemAdjustmentRepository: this.lineItemAdjustmentRepo_,
|
|
||||||
discountService: this.discountService,
|
|
||||||
})
|
|
||||||
|
|
||||||
cloned.transactionManager_ = transactionManager
|
|
||||||
|
|
||||||
return cloned
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves a line item adjustment by id.
|
* Retrieves a line item adjustment by id.
|
||||||
* @param id - the id of the line item adjustment to retrieve
|
* @param id - the id of the line item adjustment to retrieve
|
||||||
@@ -76,7 +68,7 @@ class LineItemAdjustmentService extends BaseService {
|
|||||||
const lineItemAdjustmentRepo: LineItemAdjustmentRepository =
|
const lineItemAdjustmentRepo: LineItemAdjustmentRepository =
|
||||||
this.manager_.getCustomRepository(this.lineItemAdjustmentRepo_)
|
this.manager_.getCustomRepository(this.lineItemAdjustmentRepo_)
|
||||||
|
|
||||||
const query = this.buildQuery_({ id }, config)
|
const query = buildQuery({ id }, config)
|
||||||
const lineItemAdjustment = await lineItemAdjustmentRepo.findOne(query)
|
const lineItemAdjustment = await lineItemAdjustmentRepo.findOne(query)
|
||||||
|
|
||||||
if (!lineItemAdjustment) {
|
if (!lineItemAdjustment) {
|
||||||
@@ -124,10 +116,7 @@ class LineItemAdjustmentService extends BaseService {
|
|||||||
const { metadata, ...rest } = data
|
const { metadata, ...rest } = data
|
||||||
|
|
||||||
if (metadata) {
|
if (metadata) {
|
||||||
lineItemAdjustment.metadata = this.setMetadata_(
|
lineItemAdjustment.metadata = setMetadata(lineItemAdjustment, metadata)
|
||||||
lineItemAdjustment,
|
|
||||||
metadata
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(rest)) {
|
for (const [key, value] of Object.entries(rest)) {
|
||||||
@@ -153,7 +142,7 @@ class LineItemAdjustmentService extends BaseService {
|
|||||||
this.lineItemAdjustmentRepo_
|
this.lineItemAdjustmentRepo_
|
||||||
)
|
)
|
||||||
|
|
||||||
const query = this.buildQuery_(selector, config)
|
const query = buildQuery(selector, config)
|
||||||
return await lineItemAdjustmentRepo.find(query)
|
return await lineItemAdjustmentRepo.find(query)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,15 +161,15 @@ class LineItemAdjustmentService extends BaseService {
|
|||||||
if (typeof selectorOrIds === "string" || Array.isArray(selectorOrIds)) {
|
if (typeof selectorOrIds === "string" || Array.isArray(selectorOrIds)) {
|
||||||
const ids =
|
const ids =
|
||||||
typeof selectorOrIds === "string" ? [selectorOrIds] : selectorOrIds
|
typeof selectorOrIds === "string" ? [selectorOrIds] : selectorOrIds
|
||||||
return await lineItemAdjustmentRepo.delete({ id: In(ids) })
|
await lineItemAdjustmentRepo.delete({ id: In(ids) })
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const query = this.buildQuery_(selectorOrIds)
|
const query = buildQuery(selectorOrIds)
|
||||||
|
|
||||||
const lineItemAdjustments = await lineItemAdjustmentRepo.find(query)
|
const lineItemAdjustments = await lineItemAdjustmentRepo.find(query)
|
||||||
|
|
||||||
await lineItemAdjustmentRepo.remove(lineItemAdjustments)
|
await lineItemAdjustmentRepo.remove(lineItemAdjustments)
|
||||||
return
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,23 +2,22 @@ import { MedusaError } from "medusa-core-utils"
|
|||||||
import { BaseService } from "medusa-interfaces"
|
import { BaseService } from "medusa-interfaces"
|
||||||
import { EntityManager, In } from "typeorm"
|
import { EntityManager, In } from "typeorm"
|
||||||
import { DeepPartial } from "typeorm/common/DeepPartial"
|
import { DeepPartial } from "typeorm/common/DeepPartial"
|
||||||
import TaxInclusivePricingFeatureFlag from "../loaders/feature-flags/tax-inclusive-pricing"
|
|
||||||
import { LineItemTaxLine } from "../models"
|
|
||||||
import { Cart } from "../models/cart"
|
|
||||||
import { LineItem } from "../models/line-item"
|
|
||||||
import { LineItemAdjustment } from "../models/line-item-adjustment"
|
|
||||||
import { CartRepository } from "../repositories/cart"
|
import { CartRepository } from "../repositories/cart"
|
||||||
import { LineItemRepository } from "../repositories/line-item"
|
import { LineItemRepository } from "../repositories/line-item"
|
||||||
import { LineItemTaxLineRepository } from "../repositories/line-item-tax-line"
|
import { LineItemTaxLineRepository } from "../repositories/line-item-tax-line"
|
||||||
import { FindConfig } from "../types/common"
|
import { Cart, LineItemTaxLine, LineItem, LineItemAdjustment } from "../models"
|
||||||
|
import { FindConfig, Selector } from "../types/common"
|
||||||
import { FlagRouter } from "../utils/flag-router"
|
import { FlagRouter } from "../utils/flag-router"
|
||||||
|
import LineItemAdjustmentService from "./line-item-adjustment"
|
||||||
|
import OrderEditingFeatureFlag from "../loaders/feature-flags/order-editing"
|
||||||
|
import TaxInclusivePricingFeatureFlag from "../loaders/feature-flags/tax-inclusive-pricing"
|
||||||
import {
|
import {
|
||||||
PricingService,
|
PricingService,
|
||||||
ProductService,
|
ProductService,
|
||||||
ProductVariantService,
|
ProductVariantService,
|
||||||
RegionService,
|
RegionService,
|
||||||
} from "./index"
|
} from "./index"
|
||||||
import LineItemAdjustmentService from "./line-item-adjustment"
|
|
||||||
|
|
||||||
type InjectedDependencies = {
|
type InjectedDependencies = {
|
||||||
manager: EntityManager
|
manager: EntityManager
|
||||||
@@ -99,7 +98,7 @@ class LineItemService extends BaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async list(
|
async list(
|
||||||
selector,
|
selector: Selector<LineItem>,
|
||||||
config: FindConfig<LineItem> = {
|
config: FindConfig<LineItem> = {
|
||||||
skip: 0,
|
skip: 0,
|
||||||
take: 50,
|
take: 50,
|
||||||
@@ -208,6 +207,7 @@ class LineItemService extends BaseService {
|
|||||||
includes_tax?: boolean
|
includes_tax?: boolean
|
||||||
metadata?: Record<string, unknown>
|
metadata?: Record<string, unknown>
|
||||||
customer_id?: string
|
customer_id?: string
|
||||||
|
order_edit_id?: string
|
||||||
cart?: Cart
|
cart?: Cart
|
||||||
} = {}
|
} = {}
|
||||||
): Promise<LineItem> {
|
): Promise<LineItem> {
|
||||||
@@ -267,6 +267,12 @@ class LineItemService extends BaseService {
|
|||||||
rawLineItem.includes_tax = unitPriceIncludesTax
|
rawLineItem.includes_tax = unitPriceIncludesTax
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.featureFlagRouter_.isFeatureEnabled(OrderEditingFeatureFlag.key)
|
||||||
|
) {
|
||||||
|
rawLineItem.order_edit_id = context.order_edit_id || null
|
||||||
|
}
|
||||||
|
|
||||||
const lineItemRepo = transactionManager.getCustomRepository(
|
const lineItemRepo = transactionManager.getCustomRepository(
|
||||||
this.lineItemRepository_
|
this.lineItemRepository_
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { TransactionBaseService } from "../interfaces"
|
import { TransactionBaseService } from "../interfaces"
|
||||||
import { OrderItemChangeRepository } from "../repositories/order-item-change"
|
import { OrderItemChangeRepository } from "../repositories/order-item-change"
|
||||||
import { EntityManager, In } from "typeorm"
|
import { DeepPartial, EntityManager, In } from "typeorm"
|
||||||
import { EventBusService, LineItemService } from "./index"
|
import { EventBusService, LineItemService } from "./index"
|
||||||
import { FindConfig, Selector } from "../types/common"
|
import { FindConfig, Selector } from "../types/common"
|
||||||
import { OrderItemChange } from "../models"
|
import { OrderItemChange } from "../models"
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { EntityManager, IsNull } from "typeorm"
|
import { EntityManager, IsNull } from "typeorm"
|
||||||
|
import { MedusaError } from "medusa-core-utils"
|
||||||
|
|
||||||
import { FindConfig } from "../types/common"
|
import { FindConfig } from "../types/common"
|
||||||
import { buildQuery, isDefined } from "../utils"
|
import { buildQuery, isDefined } from "../utils"
|
||||||
import { MedusaError } from "medusa-core-utils"
|
|
||||||
import { OrderEditRepository } from "../repositories/order-edit"
|
import { OrderEditRepository } from "../repositories/order-edit"
|
||||||
import {
|
import {
|
||||||
Cart,
|
Cart,
|
||||||
@@ -13,26 +14,30 @@ import {
|
|||||||
import { TransactionBaseService } from "../interfaces"
|
import { TransactionBaseService } from "../interfaces"
|
||||||
import {
|
import {
|
||||||
EventBusService,
|
EventBusService,
|
||||||
|
LineItemAdjustmentService,
|
||||||
LineItemService,
|
LineItemService,
|
||||||
OrderEditItemChangeService,
|
OrderEditItemChangeService,
|
||||||
OrderService,
|
OrderService,
|
||||||
TaxProviderService,
|
TaxProviderService,
|
||||||
TotalsService,
|
TotalsService,
|
||||||
} from "./index"
|
} from "./index"
|
||||||
import { CreateOrderEditInput, UpdateOrderEditInput } from "../types/order-edit"
|
import {
|
||||||
import region from "./region"
|
AddOrderEditLineItemInput,
|
||||||
import LineItemAdjustmentService from "./line-item-adjustment"
|
CreateOrderEditInput,
|
||||||
|
UpdateOrderEditInput,
|
||||||
|
} from "../types/order-edit"
|
||||||
|
|
||||||
type InjectedDependencies = {
|
type InjectedDependencies = {
|
||||||
manager: EntityManager
|
manager: EntityManager
|
||||||
orderEditRepository: typeof OrderEditRepository
|
orderEditRepository: typeof OrderEditRepository
|
||||||
|
|
||||||
orderService: OrderService
|
orderService: OrderService
|
||||||
eventBusService: EventBusService
|
|
||||||
totalsService: TotalsService
|
totalsService: TotalsService
|
||||||
lineItemService: LineItemService
|
lineItemService: LineItemService
|
||||||
orderEditItemChangeService: OrderEditItemChangeService
|
eventBusService: EventBusService
|
||||||
lineItemAdjustmentService: LineItemAdjustmentService
|
|
||||||
taxProviderService: TaxProviderService
|
taxProviderService: TaxProviderService
|
||||||
|
lineItemAdjustmentService: LineItemAdjustmentService
|
||||||
|
orderEditItemChangeService: OrderEditItemChangeService
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class OrderEditService extends TransactionBaseService {
|
export default class OrderEditService extends TransactionBaseService {
|
||||||
@@ -44,16 +49,18 @@ export default class OrderEditService extends TransactionBaseService {
|
|||||||
CANCELED: "order-edit.canceled",
|
CANCELED: "order-edit.canceled",
|
||||||
}
|
}
|
||||||
|
|
||||||
protected transactionManager_: EntityManager | undefined
|
|
||||||
protected readonly manager_: EntityManager
|
protected readonly manager_: EntityManager
|
||||||
|
protected transactionManager_: EntityManager | undefined
|
||||||
|
|
||||||
protected readonly orderEditRepository_: typeof OrderEditRepository
|
protected readonly orderEditRepository_: typeof OrderEditRepository
|
||||||
|
|
||||||
protected readonly orderService_: OrderService
|
protected readonly orderService_: OrderService
|
||||||
|
protected readonly totalsService_: TotalsService
|
||||||
protected readonly lineItemService_: LineItemService
|
protected readonly lineItemService_: LineItemService
|
||||||
protected readonly eventBusService_: EventBusService
|
protected readonly eventBusService_: EventBusService
|
||||||
protected readonly totalsService_: TotalsService
|
|
||||||
protected readonly orderEditItemChangeService_: OrderEditItemChangeService
|
|
||||||
protected readonly lineItemAdjustmentService_: LineItemAdjustmentService
|
|
||||||
protected readonly taxProviderService_: TaxProviderService
|
protected readonly taxProviderService_: TaxProviderService
|
||||||
|
protected readonly lineItemAdjustmentService_: LineItemAdjustmentService
|
||||||
|
protected readonly orderEditItemChangeService_: OrderEditItemChangeService
|
||||||
|
|
||||||
constructor({
|
constructor({
|
||||||
manager,
|
manager,
|
||||||
@@ -459,6 +466,78 @@ export default class OrderEditService extends TransactionBaseService {
|
|||||||
return orderEdit
|
return orderEdit
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async addLineItem(
|
||||||
|
orderEditId: string,
|
||||||
|
data: AddOrderEditLineItemInput
|
||||||
|
): Promise<void> {
|
||||||
|
return await this.atomicPhase_(async (manager) => {
|
||||||
|
const lineItemServiceTx = this.lineItemService_.withTransaction(manager)
|
||||||
|
|
||||||
|
const orderEdit = await this.retrieve(orderEditId, {
|
||||||
|
relations: ["order", "order.region"],
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!OrderEditService.isOrderEditActive(orderEdit)) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_ALLOWED,
|
||||||
|
`Can not add an item to the edit with status ${orderEdit.status}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const regionId = orderEdit.order.region_id
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create new line item and refresh adjustments for all cloned order edit items
|
||||||
|
*/
|
||||||
|
|
||||||
|
const lineItemData = await lineItemServiceTx.generate(
|
||||||
|
data.variant_id,
|
||||||
|
regionId,
|
||||||
|
data.quantity,
|
||||||
|
{
|
||||||
|
customer_id: orderEdit.order.customer_id,
|
||||||
|
metadata: data.metadata,
|
||||||
|
order_edit_id: orderEditId,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
let lineItem = await lineItemServiceTx.create(lineItemData)
|
||||||
|
lineItem = await lineItemServiceTx.retrieve(lineItem.id)
|
||||||
|
|
||||||
|
await this.refreshAdjustments(orderEditId)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a change record
|
||||||
|
*/
|
||||||
|
|
||||||
|
await this.orderEditItemChangeService_.withTransaction(manager).create({
|
||||||
|
type: OrderEditItemChangeType.ITEM_ADD,
|
||||||
|
line_item_id: lineItem.id,
|
||||||
|
order_edit_id: orderEditId,
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute tax lines
|
||||||
|
*/
|
||||||
|
|
||||||
|
const localCart = {
|
||||||
|
...orderEdit.order,
|
||||||
|
object: "cart",
|
||||||
|
items: [lineItem],
|
||||||
|
} as unknown as Cart
|
||||||
|
|
||||||
|
const calcContext = await this.totalsService_
|
||||||
|
.withTransaction(manager)
|
||||||
|
.getCalculationContext(localCart, {
|
||||||
|
exclude_shipping: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
await this.taxProviderService_
|
||||||
|
.withTransaction(manager)
|
||||||
|
.createTaxLines([lineItem], calcContext)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
async deleteItemChange(
|
async deleteItemChange(
|
||||||
orderEditId: string,
|
orderEditId: string,
|
||||||
itemChangeId: string
|
itemChangeId: string
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ export type Selector<TEntity> = {
|
|||||||
| DateComparisonOperator
|
| DateComparisonOperator
|
||||||
| StringComparisonOperator
|
| StringComparisonOperator
|
||||||
| NumericalComparisonOperator
|
| NumericalComparisonOperator
|
||||||
| FindOperator<TEntity[key][] | string[]>
|
| FindOperator<TEntity[key][] | string | string[]>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TotalField =
|
export type TotalField =
|
||||||
|
|||||||
@@ -9,6 +9,13 @@ export type CreateOrderEditInput = {
|
|||||||
internal_note?: string
|
internal_note?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type AddOrderEditLineItemInput = {
|
||||||
|
quantity: number
|
||||||
|
variant_id: string
|
||||||
|
|
||||||
|
metadata?: Record<string, unknown>
|
||||||
|
}
|
||||||
|
|
||||||
export type CreateOrderEditItemChangeInput = {
|
export type CreateOrderEditItemChangeInput = {
|
||||||
type: OrderEditItemChangeType
|
type: OrderEditItemChangeType
|
||||||
order_edit_id: string
|
order_edit_id: string
|
||||||
@@ -21,6 +28,7 @@ export const defaultOrderEditRelations: string[] = [
|
|||||||
"changes.line_item",
|
"changes.line_item",
|
||||||
"changes.original_line_item",
|
"changes.original_line_item",
|
||||||
"items",
|
"items",
|
||||||
|
"items.adjustments",
|
||||||
"items.tax_lines",
|
"items.tax_lines",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user