diff --git a/src/lib/actions/order-item.test.ts b/src/lib/actions/order-item.test.ts index 0bab7c1..808dcbd 100644 --- a/src/lib/actions/order-item.test.ts +++ b/src/lib/actions/order-item.test.ts @@ -1,21 +1,25 @@ -import { createOrderItem } from '@/lib/actions/order-item'; +import { createOrderItem, editOrderItem } from '@/lib/actions/order-item'; import { fakeOrderItem } from '@/lib/fakes/order-item'; import { prismaMock } from '../../../test-setup/prisma-mock.setup'; import { fakeBook } from '@/lib/fakes/book'; import { fakeOrder } from '@/lib/fakes/order'; import { computeTax } from '@/lib/money'; -import { ProductType } from '@prisma/client'; +import { Order, ProductType } from '@prisma/client'; describe('order item actions', () => { const order1 = fakeOrder(); const orderItem1 = fakeOrderItem(); + const orderItem2 = fakeOrderItem(); const book1 = fakeBook(); describe('createOrderItem', () => { it('should create order item', async () => { prismaMock.$transaction.mockImplementation((cb) => cb(prismaMock)); prismaMock.book.findUniqueOrThrow.mockResolvedValue(book1); - prismaMock.order.findUniqueOrThrow.mockResolvedValue(order1); + prismaMock.order.findUniqueOrThrow.mockResolvedValue({ + ...order1, + orderItems: [orderItem1, orderItem2], + } as Order); prismaMock.order.update.mockResolvedValue(order1); orderItem1.orderId = order1.id; orderItem1.bookId = book1.id; @@ -42,7 +46,7 @@ describe('order item actions', () => { }); const subTotalInCents = - order1.subTotalInCents + orderItem1.totalPriceInCents; + orderItem1.totalPriceInCents + orderItem2.totalPriceInCents; const taxInCents = computeTax(subTotalInCents); const totalInCents = subTotalInCents + taxInCents; expect(prismaMock.order.update).toHaveBeenCalledWith({ @@ -69,4 +73,56 @@ describe('order item actions', () => { ); }); }); + + describe('editOrderItem', () => { + it('should edit an order item', async () => { + prismaMock.$transaction.mockImplementation((cb) => cb(prismaMock)); + prismaMock.order.findUniqueOrThrow.mockResolvedValue({ + ...order1, + orderItems: [ + orderItem2, + { + ...orderItem1, + productPriceInCents: 10, + quantity: 2, + totalPriceInCents: 20, + }, + ], + } as Order); + orderItem1.orderId = order1.id; + prismaMock.orderItem.update.mockResolvedValue(orderItem1); + + const result = await editOrderItem({ + orderItemId: order1.id, + orderItemUpdate: { + productPriceInCents: 10, + quantity: 2, + totalPriceInCents: 20, + }, + }); + + expect(prismaMock.orderItem.update).toHaveBeenCalledWith({ + data: { + productPriceInCents: 10, + quantity: 2, + totalPriceInCents: 20, + }, + where: { id: order1.id }, + }); + + const subTotalInCents = 20 + orderItem2.totalPriceInCents; + const taxInCents = computeTax(subTotalInCents); + const totalInCents = subTotalInCents + taxInCents; + expect(prismaMock.order.update).toHaveBeenCalledWith({ + data: { + subTotalInCents, + taxInCents, + totalInCents, + }, + where: { id: order1.id }, + }); + + expect(result).toEqual(orderItem1); + }); + }); }); diff --git a/src/lib/actions/order-item.ts b/src/lib/actions/order-item.ts index 33877b5..7abdf68 100644 --- a/src/lib/actions/order-item.ts +++ b/src/lib/actions/order-item.ts @@ -4,6 +4,7 @@ import { recomputeOrderTotals } from '@/lib/actions/order'; import logger from '@/lib/logger'; import prisma from '@/lib/prisma'; import OrderItemCreateInput from '@/types/OrderItemCreateInput'; +import OrderItemUpdateInput from '@/types/OrderItemUpdateInput'; import { OrderItem, Prisma, ProductType } from '@prisma/client'; export async function createOrderItem( @@ -41,7 +42,7 @@ export async function createOrderItem( logger.trace('created order item in DB: %j', createdOrderItem); - await recomputeOrderTotals({ orderItem: createdOrderItem, tx }); + await recomputeOrderTotals({ orderId: createdOrderItem.orderId, tx }); return createdOrderItem; }, @@ -50,3 +51,34 @@ export async function createOrderItem( }, ); } + +export async function editOrderItem({ + orderItemId, + orderItemUpdate, +}: { + orderItemId: OrderItem['id']; + orderItemUpdate: OrderItemUpdateInput; +}): Promise { + return prisma.$transaction( + async (tx) => { + const { productPriceInCents, quantity, totalPriceInCents } = + orderItemUpdate; + + const orderItem = await tx.orderItem.update({ + data: { + productPriceInCents, + quantity, + totalPriceInCents, + }, + where: { id: orderItemId }, + }); + + await recomputeOrderTotals({ orderId: orderItem.orderId, tx }); + + return orderItem; + }, + { + isolationLevel: Prisma.TransactionIsolationLevel.Serializable, + }, + ); +} diff --git a/src/lib/actions/order.ts b/src/lib/actions/order.ts index 131d23f..f2e0d75 100644 --- a/src/lib/actions/order.ts +++ b/src/lib/actions/order.ts @@ -14,13 +14,7 @@ import OrderHydrated from '@/types/OrderHydrated'; import OrderWithItemsHydrated from '@/types/OrderWithItemsHydrated'; import PageInfo from '@/types/PageInfo'; import PaginationQuery from '@/types/PaginationQuery'; -import { - Order, - OrderItem, - OrderState, - Prisma, - ProductType, -} from '@prisma/client'; +import { Order, OrderState, Prisma, ProductType } from '@prisma/client'; import { format } from 'date-fns'; export async function createOrder(): Promise { @@ -50,19 +44,23 @@ export async function createOrder(): Promise { } export async function recomputeOrderTotals({ - orderItem, + orderId, tx, }: { - orderItem: OrderItem; + orderId: Order['id']; tx: Prisma.TransactionClient; }): Promise { - const { orderId } = orderItem; - const order = await tx.order.findUniqueOrThrow({ + include: { + orderItems: true, + }, where: { id: orderId }, }); - const subTotalInCents = order.subTotalInCents + orderItem.totalPriceInCents; + const subTotalInCents = order.orderItems.reduce( + (total, item) => total + item.totalPriceInCents, + 0, + ); const taxInCents = computeTax(subTotalInCents); const totalInCents = subTotalInCents + taxInCents; diff --git a/src/types/OrderItemUpdateInput.ts b/src/types/OrderItemUpdateInput.ts new file mode 100644 index 0000000..a4b835e --- /dev/null +++ b/src/types/OrderItemUpdateInput.ts @@ -0,0 +1,7 @@ +import { OrderItem } from '@prisma/client'; + +type OrderItemUpdateInput = Pick< + OrderItem, + 'productPriceInCents' | 'quantity' | 'totalPriceInCents' +>; +export default OrderItemUpdateInput;