From 80e9476469111c4a2c6734f591c772d7b1c976fe Mon Sep 17 00:00:00 2001 From: Dylan Smith Date: Mon, 29 Apr 2024 14:35:39 -0500 Subject: [PATCH] feat: allow negative inventory (#195) Remove the NegativeBookQuantityError and allow for book quantity updates to be negative, thus allowing for negative inventory. If this is the case, the admin should use the InventoryAdjustment to correct the inventory after the sale (but in this way, we did not "lose" the sale). --- src/lib/actions/book.ts | 12 +------- src/lib/actions/order.test.ts | 34 ++++++++++----------- src/lib/actions/transaction-safe.test.ts | 19 ------------ src/lib/actions/transaction-safe.ts | 5 +-- src/lib/errors/NegativeBookQuantityError.ts | 11 ------- src/lib/safe-action-wrapper.ts | 10 ++---- 6 files changed, 21 insertions(+), 70 deletions(-) delete mode 100644 src/lib/errors/NegativeBookQuantityError.ts diff --git a/src/lib/actions/book.ts b/src/lib/actions/book.ts index 6bbf3a9..1a5b00b 100644 --- a/src/lib/actions/book.ts +++ b/src/lib/actions/book.ts @@ -12,7 +12,6 @@ import BookCreateInput from '@/types/BookCreateInput'; import BookHydrated from '@/types/BookHydrated'; import { Book, Prisma, ProductType } from '@prisma/client'; import { serializeBookSource } from '@/lib/serializers/book-source'; -import NegativeBookQuantityError from '@/lib/errors/NegativeBookQuantityError'; export async function buildAuthorsInput( tx: Prisma.TransactionClient, @@ -172,9 +171,7 @@ export async function reduceBookUpdates( } /** - * Attempts to update the Book by the quantityChange. - * - * @throws NegativeBookQuantityError if quantity change results in negative quantity + * Updates the quantity for a Book by the quantityChange. */ export async function updateBookQuantity({ bookId, @@ -198,13 +195,6 @@ export async function updateBookQuantity({ updatedQuantity, ); - if (updatedQuantity < 0) { - logger.error( - 'Unable to process update, attempting to set a negative quantity', - ); - throw new NegativeBookQuantityError(book); - } - await tx.book.update({ data: { quantity: updatedQuantity }, where: { id: bookId }, diff --git a/src/lib/actions/order.test.ts b/src/lib/actions/order.test.ts index 10e0d27..198ba64 100644 --- a/src/lib/actions/order.test.ts +++ b/src/lib/actions/order.test.ts @@ -13,7 +13,6 @@ import { Order, OrderState, ProductType } from '@prisma/client'; import { fakeOrder } from '@/lib/fakes/order'; import { fakeOrderItem, fakeOrderItemHydrated } from '@/lib/fakes/order-item'; import { fakeBook } from '@/lib/fakes/book'; -import NegativeBookQuantityError from '@/lib/errors/NegativeBookQuantityError'; import BadRequestError from '@/lib/errors/BadRequestError'; import { buildPaginationRequest } from '@/lib/pagination'; import OrderWithItemsHydrated from '@/types/OrderWithItemsHydrated'; @@ -164,7 +163,7 @@ describe('order action', () => { expect(prismaMock.book.update).toHaveBeenCalledTimes(1); }); - it('should throw NegativeBookQuantityError when attempting to move order with insufficient inventory', async () => { + it('should allow for negative inventory quantity', async () => { prismaMock.$transaction.mockImplementation((cb) => cb(prismaMock)); const book1 = fakeBook(); @@ -182,21 +181,22 @@ describe('order action', () => { prismaMock.order.findFirstOrThrow.mockResolvedValue(order); prismaMock.order.update.mockResolvedValue(order); - expect.assertions(3); - try { - await moveOrderToPendingTransactionOrThrow({ - orderUID: order.orderUID, - tx: prismaMock, - }); - } catch (err) { - expect(err instanceof NegativeBookQuantityError).toBeTruthy(); - const error: NegativeBookQuantityError = - err as NegativeBookQuantityError; - expect(error.book).toEqual(book1); - expect(error.message).toEqual( - 'Attempting to set a negative quantity for Book', - ); - } + await moveOrderToPendingTransactionOrThrow({ + orderUID: order.orderUID, + tx: prismaMock, + }); + + expect(prismaMock.book.update).toHaveBeenCalledWith({ + data: { quantity: -1 }, + where: { id: item1.bookId }, + }); + + expect(prismaMock.order.update).toHaveBeenCalledWith({ + data: { + orderState: OrderState.PENDING_TRANSACTION, + }, + where: { orderUID: order.orderUID }, + }); }); it('should throw BadRequestError when attempting to move order not in OPEN state', async () => { diff --git a/src/lib/actions/transaction-safe.test.ts b/src/lib/actions/transaction-safe.test.ts index e709c83..dcec60a 100644 --- a/src/lib/actions/transaction-safe.test.ts +++ b/src/lib/actions/transaction-safe.test.ts @@ -4,8 +4,6 @@ import { syncTransactionStatusSafe, } from '@/lib/actions/transaction-safe'; import BadRequestError from '@/lib/errors/BadRequestError'; -import NegativeBookQuantityError from '@/lib/errors/NegativeBookQuantityError'; -import { fakeBook } from '@/lib/fakes/book'; import { fakeTransaction } from '@/lib/fakes/transaction'; const mockCancelTransaction = jest.fn(); @@ -50,23 +48,6 @@ describe('transaction safe actions', () => { }); }); - it('should return error when createTransaction throws NegativeBookQuantityError', async () => { - const book = fakeBook(); - mockCreateTransaction.mockRejectedValue( - new NegativeBookQuantityError(book), - ); - - expect(await createTransactionSafe('1')).toEqual({ - data: null, - error: { - book, - message: 'Attempting to set a negative quantity for Book', - name: NegativeBookQuantityError.name, - }, - status: 400, - }); - }); - it('should return error when createTransaction throws Error', async () => { mockCreateTransaction.mockRejectedValue(new Error('unrecognized error')); diff --git a/src/lib/actions/transaction-safe.ts b/src/lib/actions/transaction-safe.ts index a5d7246..414ae65 100644 --- a/src/lib/actions/transaction-safe.ts +++ b/src/lib/actions/transaction-safe.ts @@ -4,16 +4,13 @@ import { syncTransactionStatus, } from '@/lib/actions/transaction'; import BadRequestError from '@/lib/errors/BadRequestError'; -import NegativeBookQuantityError from '@/lib/errors/NegativeBookQuantityError'; import { safeActionWrapper } from '@/lib/safe-action-wrapper'; import { HttpResponse } from '@/types/HttpResponse'; import { Order, Transaction } from '@prisma/client'; export async function createTransactionSafe( orderUID: Order['orderUID'], -): Promise< - HttpResponse -> { +): Promise> { return safeActionWrapper(createTransaction, orderUID); } diff --git a/src/lib/errors/NegativeBookQuantityError.ts b/src/lib/errors/NegativeBookQuantityError.ts deleted file mode 100644 index 4668e63..0000000 --- a/src/lib/errors/NegativeBookQuantityError.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Book } from '@prisma/client'; - -export default class NegativeBookQuantityError extends Error { - public book: Book; - - constructor(book: Book) { - super('Attempting to set a negative quantity for Book'); - this.name = 'NegativeBookQuantityError'; - this.book = book; - } -} diff --git a/src/lib/safe-action-wrapper.ts b/src/lib/safe-action-wrapper.ts index 0bc581b..2bf4fa6 100644 --- a/src/lib/safe-action-wrapper.ts +++ b/src/lib/safe-action-wrapper.ts @@ -1,16 +1,13 @@ 'use server'; import BadRequestError from '@/lib/errors/BadRequestError'; -import NegativeBookQuantityError from '@/lib/errors/NegativeBookQuantityError'; import logger from '@/lib/logger'; import { HttpResponse } from '@/types/HttpResponse'; export async function safeActionWrapper( wrappedFunction: (...args: never[]) => Promise, ...args: unknown[] -): Promise< - HttpResponse -> { +): Promise> { try { const data = await wrappedFunction(...(args as never[])); @@ -19,10 +16,7 @@ export async function safeActionWrapper( status: 200, }; } catch (err: unknown) { - if ( - err instanceof BadRequestError || - err instanceof NegativeBookQuantityError - ) { + if (err instanceof BadRequestError) { return { data: null, error: {