Add createAndAddItemToCart method and refactor cart and invoice structures

master
nicekid1 2 months ago
parent 98139f75bf
commit 4520299ef0
  1. 40
      migrations/20250105062732-create-invoice.js
  2. 35
      migrations/20250105063054-create-cart.js
  3. 17
      src/cart/cart.controller.ts
  4. 92
      src/cart/cart.service.ts
  5. 2
      src/cart/dto/add-to-cart.dto.ts
  6. 24
      src/cart/entities/cart.entity.ts
  7. 16
      src/invoice/entities/invoice.entity.ts
  8. 8
      src/invoice/invoice.controller.ts
  9. 48
      src/invoice/invoice.service.ts

@ -1,10 +1,16 @@
'use strict';
"use strict";
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.dropTable('Invoices', { cascade: true });
await queryInterface.createTable('Invoices', {
const tableExists = await queryInterface
.describeTable("Invoices")
.then(() => true)
.catch(() => false);
if (tableExists) {
await queryInterface.dropTable("Invoices", { cascade: true });
}
await queryInterface.createTable("Invoices", {
id: {
type: Sequelize.INTEGER,
autoIncrement: true,
@ -15,38 +21,34 @@ module.exports = {
type: Sequelize.INTEGER,
allowNull: false,
references: {
model: 'Users',
key: 'id',
},
onDelete: 'CASCADE',
},
cartId: {
type: Sequelize.INTEGER,
allowNull: false,
references: {
model: 'Carts',
key: 'id',
model: "Users",
key: "id",
},
onDelete: 'CASCADE',
onDelete: "CASCADE",
},
totalPaymentAmount: {
type: Sequelize.FLOAT,
allowNull: false,
},
status: {
type: Sequelize.ENUM("pending", "paid"),
allowNull: false,
defaultValue: "pending",
},
createdAt: {
type: Sequelize.DATE,
allowNull: false,
defaultValue: Sequelize.fn('NOW'),
defaultValue: Sequelize.fn("NOW"),
},
updatedAt: {
type: Sequelize.DATE,
allowNull: false,
defaultValue: Sequelize.fn('NOW'),
defaultValue: Sequelize.fn("NOW"),
},
});
},
down: async (queryInterface, Sequelize) => {
await queryInterface.dropTable('Invoices');
await queryInterface.dropTable("Invoices", { cascade: true });
},
};

@ -2,14 +2,13 @@
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.dropTable('Carts', { cascade: true });
await queryInterface.dropTable("Carts", { cascade: true });
await queryInterface.createTable('Carts', {
id: {
type: Sequelize.INTEGER,
allowNull: false,
autoIncrement: true,
primaryKey: true,
allowNull: false,
},
userId: {
type: Sequelize.INTEGER,
@ -22,38 +21,44 @@ module.exports = {
},
productId: {
type: Sequelize.INTEGER,
allowNull: false,
allowNull: true,
references: {
model: 'Products',
model: 'Products',
key: 'id',
},
onDelete: 'CASCADE',
},
invoiceId: {
type: Sequelize.INTEGER,
allowNull: true,
references: {
model: 'Invoices',
key: 'id',
},
onDelete: 'CASCADE',
},
quantity: {
type: Sequelize.INTEGER,
allowNull: false,
allowNull: true,
},
productPrice: {
type: Sequelize.DECIMAL(10, 2),
allowNull: false,
},
totalPrice: {
type: Sequelize.DECIMAL(10, 2),
allowNull: false,
allowNull: true,
},
productName: {
type: Sequelize.STRING,
status: {
type: Sequelize.ENUM('open', 'closed'),
allowNull: false,
defaultValue: 'open',
},
createdAt: {
type: Sequelize.DATE,
allowNull: false,
defaultValue: Sequelize.NOW,
defaultValue: Sequelize.fn('NOW'),
},
updatedAt: {
type: Sequelize.DATE,
allowNull: false,
defaultValue: Sequelize.NOW,
defaultValue: Sequelize.fn('NOW'),
},
});
},

@ -10,11 +10,12 @@ import { Invoice } from "src/invoice/entities/invoice.entity";
export class CartController {
constructor(private readonly cartService: CartService) {}
@UseGuards(JwtAuthGuard)
@Post()
async addToCart(@Body() addToCartDto: AddToCartDto, @Request() req: any): Promise<{ message: string; cartItem: Cart }> {
async createAndAddItemToCart(@Body() addToCartDto: AddToCartDto, @Request() req: any): Promise<{ message: string; cartItem: Cart }> {
const userId = req.user.id;
return this.cartService.addToCart({ ...addToCartDto, userId });
return this.cartService.createAndAddItemToCart({ ...addToCartDto, userId });
}
@UseGuards(JwtAuthGuard)
@ -45,21 +46,17 @@ export class CartController {
};
}
@Post(':userId/checkout')
async processOrder(
@Param('userId') userId: number,
@Body('totalAmount') totalAmount: number,
):Promise<{ message: string; invoices: Invoice}> {
@Post(":userId/checkout")
async processOrder(@Param("userId") userId: number, @Body("totalAmount") totalAmount: number): Promise<{ message: string; invoices: Invoice }> {
if (!totalAmount || totalAmount <= 0) {
throw new HttpException('Invalid total amount.', HttpStatus.BAD_REQUEST);
throw new HttpException("Invalid total amount.", HttpStatus.BAD_REQUEST);
}
try {
const result = await this.cartService.processOrder(userId, totalAmount);
return result;
} catch (error) {
throw new HttpException(error.message || 'Order processing failed.', HttpStatus.INTERNAL_SERVER_ERROR);
throw new HttpException(error.message || "Order processing failed.", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}

@ -15,41 +15,74 @@ export class CartService {
@Inject(forwardRef(() => InvoiceService))
private invoiceService: InvoiceService,
) {}
// Add product to cart
async addToCart(addToCartDto: { userId: number; productId: number; quantity: number }): Promise<{ message: string; cartItem: Cart }> {
//create a cart and add item to cart
async createAndAddItemToCart(addToCartDto: { userId: number; productId: number; quantity: number }): Promise<{ message: string; cartItem: Cart }> {
const { userId, productId, quantity } = addToCartDto;
try {
if (!userId || !productId || !quantity || isNaN(Number(quantity)) || Number(quantity) <= 0) {
throw new HttpException("Invalid parameters: userId, productId, and a positive quantity are required.", HttpStatus.BAD_REQUEST);
}
if (!userId || !productId || !quantity) {
throw new HttpException("Missing required parameters: userId, productId, and quantity are required.", HttpStatus.BAD_REQUEST);
}
const product = await this.productModel.findByPk(productId);
if (!product) {
throw new HttpException("Product not found!", HttpStatus.NOT_FOUND);
}
const product = await this.productModel.findByPk(productId);
if (!product) {
throw new HttpException("Product not found!", HttpStatus.NOT_FOUND);
}
let cart = await this.cartModel.findOne({ where: { userId, productId, status: "open" } });
const existingCartItem = await this.cartModel.findOne({
where: { userId, productId },
});
if (!cart) {
cart = await this.cartModel.create({
userId,
productId,
quantity,
productPrice: product.price,
status: "open",
});
const invoice = await this.invoiceService.createInvoiceFromCart(userId);
cart.invoiceId = invoice.id;
await cart.save();
} else {
const invoice = await this.invoiceService.getInvoiceByUserAndCart(userId);
if (!invoice) {
const newInvoice = await this.invoiceService.createInvoiceFromCart(userId);
cart.invoiceId = newInvoice.id;
await cart.save();
}
}
const invoiceId = cart.invoiceId;
let existingCartItem = await this.cartModel.findOne({
where: { userId, productId, invoiceId },
});
if (existingCartItem) {
existingCartItem.quantity += Number(quantity);
existingCartItem.totalPrice = existingCartItem.quantity * existingCartItem.productPrice;
await existingCartItem.save();
if (existingCartItem) {
existingCartItem.quantity += Number(quantity);
await existingCartItem.save();
return {
message: "Product quantity updated in cart successfully!",
cartItem: existingCartItem,
};
}
const newCartItem = await this.cartModel.create({
userId,
productId,
invoiceId,
quantity,
productPrice: product.price,
});
return {
message: "Product quantity updated in cart successfully!",
cartItem: existingCartItem,
message: "Product added to cart successfully!",
cartItem: newCartItem,
};
} catch (error) {
console.error("Error adding product to cart:", error);
throw new HttpException(error.message || "An error occurred while adding the product to the cart.", HttpStatus.INTERNAL_SERVER_ERROR);
}
const newCartItem = await this.cartModel.create({ userId, productId, quantity, productPrice: product.price, totalPrice: product.price * quantity, productName: product.name });
return {
message: "Product added to cart successfully!",
cartItem: newCartItem,
};
}
// Get user's cart
@ -63,7 +96,7 @@ export class CartService {
include: [
{
model: Product,
attributes: ["id", "name", "price", "description", "imageUrl"],
attributes: [],
},
],
});
@ -72,7 +105,7 @@ export class CartService {
throw new HttpException("No cart items found for the specified user.", HttpStatus.NOT_FOUND);
}
const totalPrice = cartItems.reduce((sum, item) => sum + (Number(item.totalPrice) || 0), 0);
const totalPrice = cartItems.reduce((sum, item) => sum + (Number(item.productPrice * item.quantity) || 0), 0);
return { cartItems, totalPrice };
}
@ -86,7 +119,6 @@ export class CartService {
}
cartItem.quantity = quantity;
cartItem.totalPrice = cartItem.quantity * cartItem.productPrice;
await cartItem.save();
return cartItem;
}
@ -146,7 +178,7 @@ export class CartService {
}
// Create the invoices for all cart
const invoices = await this.invoiceService.createInvoiceFromCart(userId,cartId);
const invoices = await this.invoiceService.createInvoiceFromCart(userId);
return { message: "Order processed successfully", invoices };
} catch (error) {

@ -8,6 +8,6 @@ export class AddToCartDto {
@IsInt()
@IsNotEmpty()
@Min(0)
@Min(1)
quantity: number;
}

@ -1,6 +1,7 @@
import { Model, Table, Column, ForeignKey, BelongsTo, DataType } from "sequelize-typescript";
import { User } from "../../users/entities/user.entity";
import { Product } from "../../products/entities/product.entity";
import { Invoice } from "src/invoice/entities/invoice.entity";
@Table
export class Cart extends Model<Cart> {
@ -18,28 +19,29 @@ export class Cart extends Model<Cart> {
@BelongsTo(() => Product, { onDelete: "CASCADE" })
product: Product;
@ForeignKey(() => Invoice)
@Column
invoiceId: number;
@BelongsTo(() => Invoice, { onDelete: "CASCADE" })
invoice: Invoice;
@Column({
type: DataType.INTEGER,
allowNull: false,
allowNull: true,
})
quantity: number;
@Column({
type: DataType.DECIMAL(10, 2),
allowNull: false,
allowNull: true,
})
productPrice: number;
@Column({
type: DataType.DECIMAL(10, 2),
type: DataType.ENUM("open", "closed"),
allowNull: false,
defaultValue: "open",
})
totalPrice: number;
@Column({
type: DataType.STRING,
allowNull: false,
})
productName: string;
status: "open" | "closed";
}

@ -5,6 +5,7 @@ import {
BelongsTo,
DataType,
Model,
HasMany,
} from "sequelize-typescript";
import { User } from "../../users/entities/user.entity";
import { Cart } from "src/cart/entities/cart.entity";
@ -18,16 +19,19 @@ export class Invoice extends Model<Invoice> {
@BelongsTo(() => User, { onDelete: "CASCADE" })
user: User;
@ForeignKey(() => Cart)
@Column
cartId: number;
@BelongsTo(() => Cart, { onDelete: "CASCADE" })
cart: Cart;
@HasMany(() => Cart)
carts: Cart[];
@Column({
type: DataType.FLOAT,
allowNull: false,
})
totalPaymentAmount: number;
@Column({
type: DataType.ENUM("pending", "paid"),
allowNull: false,
defaultValue: "pending",
})
status: string;
}

@ -4,8 +4,8 @@ import { InvoiceService } from "./invoice.service";
@Controller("invoice")
export class InvoiceController {
constructor(private readonly invoiceService: InvoiceService) {}
@Get(":userId")
async getInvoices(@Param("userId") userId: number): Promise<any> {
return this.invoiceService.getInvoicesByUser(userId);
}
// @Get(":userId")
// async getInvoices(@Param("userId") userId: number): Promise<any> {
// return this.invoiceService.getInvoicesByUser(userId);
// }
}

@ -12,12 +12,12 @@ export class InvoiceService {
private cartService: CartService,
) {}
async createInvoiceFromCart(userId: number,cartId:number): Promise<Invoice> {
async createInvoiceFromCart(userId: number): Promise<Invoice> {
const user = await User.findByPk(userId);
if (!user) {
throw new HttpException("User not found", HttpStatus.NOT_FOUND);
}
const userCartItems = await this.cartService.getUserCart(userId);
if (!userCartItems || userCartItems.cartItems.length === 0) {
throw new HttpException("Cart is empty", HttpStatus.BAD_REQUEST);
@ -25,33 +25,47 @@ export class InvoiceService {
const invoice = await this.invoiceModel.create({
userId,
cartId,
totalPaymentAmount:userCartItems.totalPrice,
})
return invoice
totalPaymentAmount: userCartItems.totalPrice,
});
return invoice;
}
async getInvoicesByUser(userId: number): Promise<Invoice[]> {
async getInvoiceByUserAndCart(userId: number): Promise<Invoice> {
try {
if (!userId) {
throw new HttpException("User ID is required.", HttpStatus.BAD_REQUEST);
if (!userId ) {
throw new HttpException("User ID are required.", HttpStatus.BAD_REQUEST);
}
const invoices = await this.invoiceModel.findAll({
where: { userId },
const invoice = await this.invoiceModel.findOne({
where: { userId, status:'pending' },
});
if (!invoices || invoices.length === 0) {
throw new HttpException("No invoices found for this user.", HttpStatus.NOT_FOUND);
if (!invoice) {
throw new HttpException("Invoice not found for this user and cart.", HttpStatus.NOT_FOUND);
}
return invoices;
return invoice;
} catch (error) {
if (error instanceof HttpException) {
throw error;
}
throw new HttpException("An error occurred while retrieving invoices.", HttpStatus.INTERNAL_SERVER_ERROR);
throw new HttpException("An error occurred while retrieving the invoice.", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
async getInvoiceByUserAndCartOrFalse(userId: number, cartId: number): Promise<Invoice | false> {
try {
const invoice = await this.invoiceModel.findOne({
where: { userId, id: cartId },
});
if (!invoice) {
return false;
}
return invoice;
} catch (error) {
console.error("Error getting invoice:", error);
return false;
}
}
}

Loading…
Cancel
Save