266 lines
		
	
	
		
			8.2 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			266 lines
		
	
	
		
			8.2 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| 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<number> = 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 };
 |