From 72185336603359e72e04526eaf351a1bf2c9c706 Mon Sep 17 00:00:00 2001 From: nfesta2023 <142601504+nfesta2023@users.noreply.github.com> Date: Mon, 8 Jan 2024 11:55:21 +0700 Subject: [PATCH] Fes 24 add to cart (#31) * setup cart module, controller & service, complete the add-to-cart route * add entity CartItem; interpret advanced taste customization --------- Co-authored-by: NHT --- src/app.module.ts | 2 + src/dto/sku.dto.ts | 7 +- src/entity/cart-item.entity.ts | 50 ++++++++++ src/feature/cart/cart.controller.ts | 20 ++++ src/feature/cart/cart.module.ts | 13 +++ src/feature/cart/cart.service.ts | 77 ++++++++++++++++ .../cart/dto/add-to-cart-request.dto.ts | 13 +++ .../cart/dto/add-to-cart-response.dto.ts | 31 +++++++ src/feature/common/common.service.ts | 92 ++++++++++++++++++- src/type/index.ts | 5 + 10 files changed, 304 insertions(+), 6 deletions(-) create mode 100644 src/entity/cart-item.entity.ts create mode 100644 src/feature/cart/cart.controller.ts create mode 100644 src/feature/cart/cart.module.ts create mode 100644 src/feature/cart/cart.service.ts create mode 100644 src/feature/cart/dto/add-to-cart-request.dto.ts create mode 100644 src/feature/cart/dto/add-to-cart-response.dto.ts diff --git a/src/app.module.ts b/src/app.module.ts index daedb58..da19dd5 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -11,6 +11,7 @@ import configuration from './config/configuration'; import { FlagsmithModule } from './dependency/flagsmith/flagsmith.module'; import { SearchModule } from './feature/search/search.module'; import { CommonModule } from './feature/common/common.module'; +import { CartModule } from './feature/cart/cart.module'; @Module({ imports: [ @@ -41,6 +42,7 @@ import { CommonModule } from './feature/common/common.module'; FlagsmithModule, SearchModule, CommonModule, + CartModule, ], controllers: [AppController], providers: [AppService], diff --git a/src/dto/sku.dto.ts b/src/dto/sku.dto.ts index 4f958b0..6e8c5e4 100644 --- a/src/dto/sku.dto.ts +++ b/src/dto/sku.dto.ts @@ -1,3 +1,5 @@ +import { OptionSelection } from 'src/type'; + export class SkuDTO { sku_id: number; price: number; @@ -11,8 +13,3 @@ export class SkuDTO { portion_customization: OptionSelection[]; // taste_customization: OptionSelection[]; } - -interface OptionSelection { - option_id: string; - value_id: string; -} diff --git a/src/entity/cart-item.entity.ts b/src/entity/cart-item.entity.ts new file mode 100644 index 0000000..c68fabf --- /dev/null +++ b/src/entity/cart-item.entity.ts @@ -0,0 +1,50 @@ +import { + Column, + CreateDateColumn, + Entity, + PrimaryGeneratedColumn, +} from 'typeorm'; + +@Entity('Cart_Item') +export class CartItem { + @PrimaryGeneratedColumn() + public item_id: number; + + @Column({ type: 'int', unique: false, nullable: false }) + public customer_id: number; + + @Column({ type: 'int', unique: false, nullable: false }) + public sku_id: number; + + @Column({ type: 'int', unique: false, nullable: true }) + public qty_ordered: number; + + @Column({ type: 'varchar', length: 255, unique: false, nullable: true }) + public advanced_taste_customization: string; + + @Column({ type: 'varchar', length: 255, unique: false, nullable: true }) + public basic_taste_customization: string; + + @Column({ type: 'varchar', length: 255, unique: false, nullable: true }) + public portion_customization: string; + + @Column({ type: 'text', unique: false, nullable: true }) + public advanced_taste_customization_obj: string; + + @Column({ type: 'text', unique: false, nullable: true }) + public basic_taste_customization_obj: string; + + @Column({ type: 'text', unique: false, nullable: true }) + public notes: string; + + @Column({ type: 'int', unique: false, nullable: true }) + public restaurant_id: number; + + @CreateDateColumn({ + type: 'datetime', + nullable: false, + unique: false, + default: () => 'CURRENT_TIMESTAMP', + }) + public created_at: Date; +} diff --git a/src/feature/cart/cart.controller.ts b/src/feature/cart/cart.controller.ts new file mode 100644 index 0000000..1412aaa --- /dev/null +++ b/src/feature/cart/cart.controller.ts @@ -0,0 +1,20 @@ +import { Controller, Inject } from '@nestjs/common'; +import { CartService } from './cart.service'; +import { FlagsmithService } from 'src/dependency/flagsmith/flagsmith.service'; +import { MessagePattern } from '@nestjs/microservices'; +import { AddToCartRequest } from './dto/add-to-cart-request.dto'; +import { AddToCartResponse } from './dto/add-to-cart-response.dto'; + +@Controller() +export class CartController { + constructor( + @Inject('FLAGSMITH_SERVICE') private readonly flagService: FlagsmithService, + private readonly cartService: CartService, + ) {} + @MessagePattern({ cmd: 'add_cart_item' }) + async addCartItem(data: AddToCartRequest): Promise { + if (this.flagService.isFeatureEnabled('fes-24-add-to-cart')) { + return await this.cartService.addCartItem(data); + } + } +} diff --git a/src/feature/cart/cart.module.ts b/src/feature/cart/cart.module.ts new file mode 100644 index 0000000..57dbbe8 --- /dev/null +++ b/src/feature/cart/cart.module.ts @@ -0,0 +1,13 @@ +import { Module } from '@nestjs/common'; +import { CartController } from './cart.controller'; +import { CartService } from './cart.service'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { CartItem } from 'src/entity/cart-item.entity'; + +@Module({ + imports: [TypeOrmModule.forFeature([CartItem])], + controllers: [CartController], + providers: [CartService], + exports: [CartService], +}) +export class CartModule {} diff --git a/src/feature/cart/cart.service.ts b/src/feature/cart/cart.service.ts new file mode 100644 index 0000000..85cd8a2 --- /dev/null +++ b/src/feature/cart/cart.service.ts @@ -0,0 +1,77 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { InjectEntityManager } from '@nestjs/typeorm'; +import { FlagsmithService } from 'src/dependency/flagsmith/flagsmith.service'; +import { EntityManager } from 'typeorm'; +import { AddToCartResponse } from './dto/add-to-cart-response.dto'; +import { AddToCartRequest } from './dto/add-to-cart-request.dto'; +import { CartItem } from 'src/entity/cart-item.entity'; +import { CommonService } from '../common/common.service'; + +@Injectable() +export class CartService { + constructor( + @Inject('FLAGSMITH_SERVICE') private readonly flagService: FlagsmithService, + @InjectEntityManager() private readonly entityManager: EntityManager, + private readonly commonService: CommonService, + ) {} + async addCartItem(inputData: AddToCartRequest): Promise { + if (this.flagService.isFeatureEnabled('fes-24-add-to-cart')) { + const res = new AddToCartResponse(200, ''); + + const { + customer_id, + sku_id, + qty_ordered, + advanced_taste_customization_obj, + basic_taste_customization_obj, + notes, + } = inputData; + + //Get the current cart + const cart = await this.entityManager + .createQueryBuilder(CartItem, 'cart') + .where('cart.customer_id = :customer_id', { customer_id }) + .getMany(); + + //Interpret Advance Taste Customization + const advanced_taste_customization = + await this.commonService.interpretAdvanceTaseCustomization( + advanced_taste_customization_obj, + ); + console.log('advanced_taste_customization', advanced_taste_customization); + + //If cart is empty, create a new cart + // if (cart.length === 0) { + // const item = await this.entityManager + // .createQueryBuilder() + // .insert() + // .into(CartItem) + // .values({ + // customer_id: customer_id, + // sku_id: sku_id, + // qty_ordered: qty_ordered, + // advanced_taste_customization: '', + // basic_taste_customization: '', + // portion_customization: '', + // advanced_taste_customization_obj: JSON.stringify( + // advanced_taste_customization_obj, + // ), + // basic_taste_customization_obj: JSON.stringify( + // basic_taste_customization_obj, + // ), + // notes: notes, + // restaurant_id: null, + // }) + // .execute(); + // } + + //success + res.statusCode = 200; + // res.message = 'Add to cart successfully'; + res.message = cart; + res.data = null; + + return res; + } + } +} diff --git a/src/feature/cart/dto/add-to-cart-request.dto.ts b/src/feature/cart/dto/add-to-cart-request.dto.ts new file mode 100644 index 0000000..afd2a6a --- /dev/null +++ b/src/feature/cart/dto/add-to-cart-request.dto.ts @@ -0,0 +1,13 @@ +export class AddToCartRequest { + customer_id: number; + sku_id: number; + qty_ordered: number; + advanced_taste_customization_obj: OptionSelection[]; + basic_taste_customization_obj: OptionSelection[]; + notes: string; +} + +interface OptionSelection { + option_id: string; + value_id: string; +} diff --git a/src/feature/cart/dto/add-to-cart-response.dto.ts b/src/feature/cart/dto/add-to-cart-response.dto.ts new file mode 100644 index 0000000..9f1a1ec --- /dev/null +++ b/src/feature/cart/dto/add-to-cart-response.dto.ts @@ -0,0 +1,31 @@ +import { GeneralResponse } from 'src/dto/general-response.dto'; + +export class AddToCartResponse extends GeneralResponse { + data: AddToCartResponseData; +} + +interface AddToCartResponseData { + restaurant_id: number; + customer_id: number; + cart_info: CartItem[]; +} + +interface CartItem { + item_id: number; + sku_id: number; + customer_id: number; + qty_ordered: number; + advanced_taste_customization: string; + basic_taste_customization: string; + portion_customization: string; + advanced_taste_customization_obj: OptionSelection[]; + basic_taste_customization_obj: OptionSelection[]; + notes: string; + restaurant_id: number; + created_at: Date; +} + +interface OptionSelection { + option_id: string; + value_id: string; +} diff --git a/src/feature/common/common.service.ts b/src/feature/common/common.service.ts index 6b42ef8..14f3e3d 100644 --- a/src/feature/common/common.service.ts +++ b/src/feature/common/common.service.ts @@ -1,5 +1,10 @@ import { Inject, Injectable } from '@nestjs/common'; -import { DeliveryRestaurant, Review, TextByLang } from 'src/type'; +import { + DeliveryRestaurant, + OptionSelection, + Review, + TextByLang, +} from 'src/type'; import { FlagsmithService } from 'src/dependency/flagsmith/flagsmith.service'; import { RestaurantExt } from 'src/entity/restaurant-ext.entity'; import { InjectEntityManager } from '@nestjs/typeorm'; @@ -11,6 +16,10 @@ import { SKU } from 'src/entity/sku.entity'; import { PERCENTAGE } from 'src/constant/unit.constant'; import { MenuItem } from 'src/entity/menu-item.entity'; import { FoodDTO } from 'src/dto/food.dto'; +import { MenuItemVariant } from 'src/entity/menu-item-variant.entity'; +import { TasteExt } from 'src/entity/taste-ext.entity'; +import { resourceUsage } from 'process'; +import { MenuItemVariantOpion } from 'src/entity/menu-item-variant-option.entity'; @Injectable() export class CommonService { @@ -160,4 +169,85 @@ export class CommonService { units_sold: menuItem.units_sold, }; } + + async interpretAdvanceTaseCustomization( + obj_list: OptionSelection[], + lang: string = 'vie', + ): Promise { + if (this.flagService.isFeatureEnabled('fes-24-add-to-cart')) { + let result = ''; + + //if object is empty => return '' + if (obj_list.length == 0) { + result = ''; + return result; + } + + const menuItemVariantIds = obj_list.map((i) => i.option_id); + const menuItemVariants = await this.entityManager + .createQueryBuilder(MenuItemVariant, 'menuItemVariant') + .leftJoinAndSelect('menuItemVariant.taste_ext', 'taseExt') + .where( + 'menuItemVariant.menu_item_variant_id IN (:...menuItemVariantIds)', + { menuItemVariantIds }, + ) + .andWhere('menuItemVariant.type = :type', { type: 'taste' }) + .andWhere('taseExt.ISO_language_code = :lang', { lang }) + .getMany(); + + const menuItemVariantOptionIds = obj_list.map((i) => i.value_id); + const menuItemVariantOptions = await this.entityManager + .createQueryBuilder(MenuItemVariantOpion, 'option') + .leftJoinAndSelect('option.taste_value_ext', 'ext') + .where( + 'option.menu_item_variant_option_id IN (:...menuItemVariantOptionIds)', + { menuItemVariantOptionIds }, + ) + .andWhere('option.taste_value <> :tasteVal', { tasteVal: 'original' }) //dont generate note for original options + .andWhere('ext.ISO_language_code = :lang', { lang }) + .getMany(); + + const strArr = []; + for (const option of obj_list) { + let str = ''; + const menuItemVariant = menuItemVariants.find( + (i) => i.menu_item_variant_id.toString() == option.option_id, + ); + //check if the option_id has been filtered out + if (!menuItemVariant) { + continue; + } + const menuItemVariantOption = menuItemVariantOptions.find( + (i) => i.menu_item_variant_option_id.toString() == option.value_id, + ); + // check if the value_id has been filtered out + if (!menuItemVariantOption) { + continue; + } + //check if the option_id and value_id is consistent - belong to the same menu_item_variant_id + if ( + menuItemVariantOption.menu_item_variant_id != + menuItemVariant.menu_item_variant_id + ) { + console.log( + 'menuItemVariantOption ', + menuItemVariantOption.menu_item_variant_option_id, + ' does not belong to menuItemVariant ', + menuItemVariant.menu_item_variant_id, + ); + continue; + } + str = + menuItemVariantOption.taste_value_ext[0].name + + ' ' + + menuItemVariant.taste_ext[0].name; + + strArr.push(str); + } + console.log('strArr', strArr); + result = strArr.join(' - '); + + return result; + } + } } diff --git a/src/type/index.ts b/src/type/index.ts index 5448137..126a38e 100644 --- a/src/type/index.ts +++ b/src/type/index.ts @@ -74,3 +74,8 @@ export interface DayShift { to: string; isAvailable?: boolean; } + +export interface OptionSelection { + option_id: string; + value_id: string; +}