diff --git a/src/handlers/db.ts b/src/handlers/db.ts index db79ffb..72a02b3 100644 --- a/src/handlers/db.ts +++ b/src/handlers/db.ts @@ -58,3 +58,10 @@ export function handlePrismaError(errorObj: any, res: Response, source: string) export default prisma; //FIXME: https://www.prisma.io/docs/orm/prisma-client/special-fields-and-types/null-and-undefined + + +// // Simulate a Prisma error for testing purposes +// throw new Prisma.PrismaClientKnownRequestError( +// 'Simulated Prisma error for testing', +// { code: 'P2000', clientVersion: 'unknown' } // Example error parameters +// ); diff --git a/src/routes/api/v1/transaction/transaction.ts b/src/routes/api/v1/transaction/transaction.ts index 4405014..e84b90d 100644 --- a/src/routes/api/v1/transaction/transaction.ts +++ b/src/routes/api/v1/transaction/transaction.ts @@ -5,6 +5,13 @@ import { parseDynamicSortBy } from '../../../../helpers/prisma_helpers.js'; import { schema_get, schema_post, schema_patch, schema_del } from './transaction_schema.js'; import { Prisma } from '@prisma/client'; +class AbortError extends Error { + constructor(public http_status: number, public status: string, public errorcode: string, public message: string, public details?: any) { + super(message); + this.name = 'AbortError'; + } +} + // MARK: GET transaction async function get(req: Request, res: Response) { const { error, value } = schema_get.validate(req.query); @@ -94,38 +101,81 @@ async function post(req: Request, res: Response) { log.api?.debug('POST transaction Success:', req.body, value); const products: Array = value.products; - let total = new Prisma.Decimal(0); + const outOfStockProducts: { id: number; name: string }[] = []; + const notFoundProducts: { id: number; name: string }[] = []; const salesData: { productId: number; price: number }[] = []; + let total = new Prisma.Decimal(0); - try { - // Start Prisma transaction - await db.$transaction(async (prisma) => { + // Start Prisma transaction + await db + .$transaction(async (prisma) => { + // Iterate over all products for this transaction(not prisma) for (let i = 0; i < products.length; i++) { - log.api?.debug('Product:', products[i]); + log.api?.debug('Product:', i + 1, 'of', products.length, '(Loop)'); + + // Get product (price, stock, name) const product = await prisma.products.findUnique({ where: { id: products[i] }, - select: { price: true } + select: { price: true, stock: true, name: true } }); + // Check if product exists if (product) { - log.api?.debug('Price:', product.price, Number(product.price)); - //total += Number(product.price); - total = total.add(product.price); + log.api?.debug('Price:', product.price, '[Name:' + product.name + ']', '[ID:' + products[i] + ']'); - salesData.push({ - productId: products[i], - price: Number(product.price) - }); + if (product.stock > 0) { + // Add price of current product to total + total = total.add(product.price); + + // Add product to salesData -> Later generate sales entry for each product + salesData.push({ + productId: products[i], + price: Number(product.price) + }); + + // Reduce stock by 1 + await prisma.products.update({ + where: { id: products[i] }, + data: { stock: { decrement: 1 } } + }); + } else { + // Product is out of stock + outOfStockProducts.push({ id: products[i], name: product.name }); + } } else { - log.api?.debug('Product not found:', products[i]); + // Product not found + notFoundProducts.push({ id: products[i], name: 'unknown' }); } } log.api?.debug('Total:', total.toFixed(2)); - // TODO: Check if user exists + // Abort the Prisma transaction if there are not existing products + if (notFoundProducts.length > 0) { + log.api?.debug('Aborting. missing products:', notFoundProducts); - // Create transaction with sales + throw new AbortError( + 400, // http_status + 'ERROR', // status + 'NOT_FOUND', // errorcode + 'Some of the products included in the transaction do not exist, therefore the transaction has not been processed.', // message + notFoundProducts // details + ); + } + + // Abort the Prisma transaction if there are products with insufficient stock + if (outOfStockProducts.length > 0) { + log.api?.debug('Aborting. out of stock products:', outOfStockProducts); + throw new AbortError( + 400, // http_status + 'ERROR', // status + 'OUT_OF_STOCK', // errorcode + 'Some of the products included in the transaction are out of stock, therefore the transaction has not been processed.', // message + outOfStockProducts // details + ); + } + + // Create transaction with salesData const transaction = await prisma.transactions.create({ data: { userId: value.user_id, @@ -140,11 +190,21 @@ async function post(req: Request, res: Response) { } }); + // Everything went well res.status(201).json({ status: 'CREATED', message: 'Successfully created transaction', id: transaction.id }); + }) + .catch((err) => { + if (err instanceof AbortError) { + res.status(err.http_status).json({ + status: err.status, + errorcode: err.errorcode, + message: err.message, + details: err.details + }); + } else { + handlePrismaError(err, res, 'POST transaction'); + } }); - } catch (err) { - handlePrismaError(err, res, 'POST transaction'); - } } }