import { Request, Response } from 'express'; import db, { handlePrismaError } from '../../../../handlers/db.js'; // Database import log from '../../../../handlers/log.js'; 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); if (error) { log.api?.debug('GET transaction Error:', req.query, value, error.details[0].message); res.status(400).json({ status: 'ERROR', errorcode: 'VALIDATION_ERROR', message: error.details[0].message }); } else { log.api?.debug('GET transaction Success:', req.query, value); if (value.id !== undefined || value.user_id !== undefined) { // get by id or user_id await db .$transaction([ // Same query for count and findMany db.transactions.count({ where: { OR: [{ id: value.id }, { userId: value.user_id }], paid: value.paid }, orderBy: parseDynamicSortBy(value.sort.toString(), value.order.toString()), skip: value.skip, take: value.take }), db.transactions.findMany({ where: { OR: [{ id: value.id }, { userId: value.user_id }], paid: value.paid }, orderBy: parseDynamicSortBy(value.sort.toString(), value.order.toString()), skip: value.skip, take: value.take }) ]) .then(([count, result]) => { if (result.length !== 0) { res.status(200).json({ count, result }); } else { res.status(404).json({ status: 'ERROR', errorcode: 'NOT_FOUND', message: 'Could not find specified transaction' }); } }) .catch((err) => { handlePrismaError(err, res, 'GET transaction'); }); } else { // get all await db .$transaction([ // Same query for count and findMany db.transactions.count({ where: { paid: value.paid }, orderBy: parseDynamicSortBy(value.sort.toString(), value.order.toString()), skip: value.skip, take: value.take }), db.transactions.findMany({ where: { paid: value.paid }, orderBy: parseDynamicSortBy(value.sort.toString(), value.order.toString()), skip: value.skip, take: value.take }) ]) .then(([count, result]) => { if (result.length !== 0) { res.status(200).json({ count, result }); } else { res.status(404).json({ status: 'ERROR', errorcode: 'NOT_FOUND', message: 'Could not find specified transaction' }); } }) .catch((err) => { handlePrismaError(err, res, 'GET transaction'); }); } } } // MARK: CREATE transaction async function post(req: Request, res: Response) { const { error, value } = schema_post.validate(req.body); if (error) { log.api?.debug('POST transaction Error:', req.body, value, error.details[0].message); res.status(400).json({ status: 'ERROR', errorcode: 'VALIDATION_ERROR', message: error.details[0].message }); } else { log.api?.debug('POST transaction Success:', req.body, value); const products: Array = value.products; const outOfStockProducts: { id: number; name: string }[] = []; const notFoundProducts: { id: number; name: string }[] = []; const salesData: { productId: number; price: number }[] = []; let total = new Prisma.Decimal(0); // 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:', i + 1, 'of', products.length, '(Loop)'); // Get product (price, stock, name) const product = await prisma.products.findUnique({ where: { id: products[i] }, select: { price: true, stock: true, name: true } }); // Check if product exists if (product) { log.api?.debug('Price:', product.price, '[Name:' + product.name + ']', '[ID:' + products[i] + ']'); 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 { // Product not found notFoundProducts.push({ id: products[i], name: 'unknown' }); } } log.api?.debug('Total:', total.toFixed(2)); // Abort the Prisma transaction if there are not existing products if (notFoundProducts.length > 0) { log.api?.debug('Aborting. missing products:', notFoundProducts); 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, total: total, paid: false, sales: { create: salesData } }, select: { id: true } }); // 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'); } }); } } // MARK: UPDATE transaction async function patch(req: Request, res: Response) { const { error, value } = schema_patch.validate(req.body); if (error) { log.api?.debug('PATCH transaction Error:', req.body, value, error.details[0].message); res.status(400).json({ status: 'ERROR', errorcode: 'VALIDATION_ERROR', message: error.details[0].message }); } else { log.api?.debug('PATCH transaction Success:', req.body, value); await db.transactions .update({ where: { id: value.id }, data: { userId: value.user_id, paid: value.paid, paidAt: value.paid ? new Date() : undefined }, select: { id: true } }) .then((result) => { res.status(200).json({ status: 'UPDATED', message: 'Successfully updated transaction', id: result.id }); }) .catch((err) => { handlePrismaError(err, res, 'PATCH transaction'); }); } } // MARK: DELETE transaction async function del(req: Request, res: Response) { const { error, value } = schema_del.validate(req.body); if (error) { log.api?.debug('DEL transaction Error:', req.body, value, error.details[0].message); res.status(400).json({ status: 'ERROR', errorcode: 'VALIDATION_ERROR', message: error.details[0].message }); } else { log.api?.debug('DEL transaction Success:', req.body, value); await db.transactions .delete({ where: { id: value.id } }) .then((result) => { res.status(200).json({ status: 'DELETED', message: 'Successfully deleted transaction', id: result.id }); }) .catch((err) => { handlePrismaError(err, res, 'DEL transaction'); }); } } export default { get, post, patch, del };