From d6667b832a459db3646cf6245557fe062535b1cd Mon Sep 17 00:00:00 2001 From: nfesta2023 <142601504+nfesta2023@users.noreply.github.com> Date: Tue, 9 Jan 2024 11:33:57 +0700 Subject: [PATCH] Fes 24 add to cart 2 (#32) * add the optional language info to the request data * logic for interpret the basic taste customization * interpret portion customization * validate SKU * move 2 fucntions of food service to common service for mutual usage * validate advanced taste customization input object * adjust response DTO; move exception handle logic to controller * extract the insert logic * add flag to insertCart * complete the logic of adding to cart --------- Co-authored-by: NHT --- src/entity/sku-menu-item-variant.entity.ts | 8 + src/feature/cart/cart.controller.ts | 40 ++- src/feature/cart/cart.service.ts | 268 ++++++++++++++---- .../cart/dto/add-to-cart-request.dto.ts | 6 +- .../cart/dto/add-to-cart-response.dto.ts | 10 +- src/feature/common/common.module.ts | 2 + src/feature/common/common.service.ts | 189 +++++++++++- src/feature/food/food.service.ts | 56 ++-- src/type/index.ts | 10 + 9 files changed, 499 insertions(+), 90 deletions(-) diff --git a/src/entity/sku-menu-item-variant.entity.ts b/src/entity/sku-menu-item-variant.entity.ts index dbc6c52..6e57da3 100644 --- a/src/entity/sku-menu-item-variant.entity.ts +++ b/src/entity/sku-menu-item-variant.entity.ts @@ -8,6 +8,7 @@ import { } from 'typeorm'; import { SKU } from './sku.entity'; import { MenuItemVariant } from './menu-item-variant.entity'; +import { MenuItemVariantOpion } from './menu-item-variant-option.entity'; @Entity('SKU_Menu_Item_Variant') export class SkuMenuItemVariant { @@ -43,4 +44,11 @@ export class SkuMenuItemVariant { referencedColumnName: 'menu_item_variant_id', }) public attribute: MenuItemVariant; + + @ManyToOne(() => MenuItemVariantOpion) + @JoinColumn({ + name: 'option', + referencedColumnName: 'menu_item_variant_option_id', + }) + public value: MenuItemVariantOpion; } diff --git a/src/feature/cart/cart.controller.ts b/src/feature/cart/cart.controller.ts index 1412aaa..3511f47 100644 --- a/src/feature/cart/cart.controller.ts +++ b/src/feature/cart/cart.controller.ts @@ -1,4 +1,4 @@ -import { Controller, Inject } from '@nestjs/common'; +import { Controller, HttpException, Inject } from '@nestjs/common'; import { CartService } from './cart.service'; import { FlagsmithService } from 'src/dependency/flagsmith/flagsmith.service'; import { MessagePattern } from '@nestjs/microservices'; @@ -14,7 +14,43 @@ export class CartController { @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); + const { + customer_id, + sku_id, + qty_ordered, + advanced_taste_customization_obj, + basic_taste_customization_obj, + notes, + } = data; + const res = new AddToCartResponse(200, ''); + try { + const cart = await this.cartService.addCartItem( + customer_id, + sku_id, + qty_ordered, + advanced_taste_customization_obj, + basic_taste_customization_obj, + notes, + ); + //success + res.statusCode = 200; + res.message = 'Add to cart successfully'; + res.data = { + customer_id: customer_id, + cart_info: cart, + }; + } catch (error) { + if (error instanceof HttpException) { + res.statusCode = error.getStatus(); + res.message = error.getResponse(); + res.data = null; + } else { + res.statusCode = 500; + res.message = error.toString(); + res.data = null; + } + } + return res; } } } diff --git a/src/feature/cart/cart.service.ts b/src/feature/cart/cart.service.ts index 85cd8a2..ef9b6d5 100644 --- a/src/feature/cart/cart.service.ts +++ b/src/feature/cart/cart.service.ts @@ -1,11 +1,11 @@ -import { Inject, Injectable } from '@nestjs/common'; +import { HttpException, 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'; +import { SKU } from 'src/entity/sku.entity'; +import { BasicTasteSelection, OptionSelection } from 'src/type'; @Injectable() export class CartService { @@ -14,64 +14,236 @@ export class CartService { @InjectEntityManager() private readonly entityManager: EntityManager, private readonly commonService: CommonService, ) {} - async addCartItem(inputData: AddToCartRequest): Promise { + async addCartItem( + customer_id: number, + sku_id: number, + qty_ordered: number, + advanced_taste_customization_obj: OptionSelection[], + basic_taste_customization_obj: BasicTasteSelection[], + notes: string, + lang: string = 'vie', + ): Promise { if (this.flagService.isFeatureEnabled('fes-24-add-to-cart')) { - const res = new AddToCartResponse(200, ''); - - const { - customer_id, - sku_id, - qty_ordered, + const advanced_taste_customization_obj_txt = JSON.stringify( advanced_taste_customization_obj, + ); + const basic_taste_customization_obj_txt = JSON.stringify( basic_taste_customization_obj, - notes, - } = inputData; + ); + + //Check if the SKU does exist + const sku = await this.entityManager + .createQueryBuilder(SKU, 'sku') + .leftJoinAndSelect('sku.menu_item', 'menuItem') + .where('sku.sku_id = :sku_id', { sku_id }) + .getOne(); + if (!sku) { + throw new HttpException('SKU does not exist', 404); + } + + // Check if the advanced_taste_customization_obj is all available to this SKU + const advancedTasteCustomizationValidation = + await this.commonService.validateAdvacedTasteCustomizationObjWithMenuItem( + advanced_taste_customization_obj, + sku.menu_item_id, + ); + if (!advancedTasteCustomizationValidation.isValid) { + throw new HttpException( + advancedTasteCustomizationValidation.message, + 400, + ); + } + + // Check if the basic_taste_customization_obj is all available to this SKU + const basicTasteCustomizationValidation = + await this.commonService.validateBasicTasteCustomizationObjWithMenuItem( + basic_taste_customization_obj, + sku.menu_item_id, + ); + if (!basicTasteCustomizationValidation.isValid) { + throw new HttpException(basicTasteCustomizationValidation.message, 400); + } //Get the current cart - const cart = await this.entityManager - .createQueryBuilder(CartItem, 'cart') - .where('cart.customer_id = :customer_id', { customer_id }) - .getMany(); + const currentCart = await this.getCart(customer_id); //Interpret Advance Taste Customization const advanced_taste_customization = await this.commonService.interpretAdvanceTaseCustomization( advanced_taste_customization_obj, + lang, ); - console.log('advanced_taste_customization', advanced_taste_customization); + + //Interpret Basic Taste Customization + const basic_taste_customization = + await this.commonService.interpretBasicTaseCustomization( + basic_taste_customization_obj, + lang, + ); + + //Interpret Portion Customization + const portion_customization = + await this.commonService.interpretPortionCustomization(sku_id, lang); //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; + if (currentCart.length === 0) { + await this.insertCart( + customer_id, + sku_id, + qty_ordered, + advanced_taste_customization, + basic_taste_customization, + portion_customization, + advanced_taste_customization_obj_txt, + basic_taste_customization_obj_txt, + notes, + sku.menu_item.restaurant_id, + ); + return await this.getCart(customer_id); + } + + // Check if the sku_id’s restaurant is the same as the current cart’s items + if (sku.menu_item.restaurant_id != currentCart[0].restaurant_id) { + //Only compare to the first item of the cart. Assume that all of the items + //of the cart have the same restaurant_id + throw new HttpException( + `Added item should have the same restaurant_id as the current cart`, + 400, + ); + } + + //Get the item which having the same sku_id + const similarCartItem = currentCart.find((i) => i.sku_id == sku_id); + if (!similarCartItem) { + //There is no item which having the same sku_id in the cart + await this.insertCart( + customer_id, + sku_id, + qty_ordered, + advanced_taste_customization, + basic_taste_customization, + portion_customization, + advanced_taste_customization_obj_txt, + basic_taste_customization_obj_txt, + notes, + sku.menu_item.restaurant_id, + ); + return await this.getCart(customer_id); + } + + //check if the similar item has the same advanced_taste_customization_obj + //and basic_taste_customization_obj + const isSameCustomization = + advanced_taste_customization_obj_txt == + similarCartItem.advanced_taste_customization_obj && + basic_taste_customization_obj_txt == + similarCartItem.basic_taste_customization_obj; + if (isSameCustomization) { + await this.updateCart( + similarCartItem.item_id, + similarCartItem.customer_id, + similarCartItem.sku_id, + similarCartItem.qty_ordered + qty_ordered, + similarCartItem.advanced_taste_customization, + similarCartItem.basic_taste_customization, + similarCartItem.portion_customization, + similarCartItem.advanced_taste_customization_obj, + similarCartItem.basic_taste_customization_obj, + similarCartItem.notes, + similarCartItem.restaurant_id, + ); + return await this.getCart(customer_id); + } + + await this.insertCart( + customer_id, + sku_id, + qty_ordered, + advanced_taste_customization, + basic_taste_customization, + portion_customization, + advanced_taste_customization_obj_txt, + basic_taste_customization_obj_txt, + notes, + sku.menu_item.restaurant_id, + ); + return await this.getCart(customer_id); + } + } + + async getCart(customer_id: number): Promise { + if (this.flagService.isFeatureEnabled('fes-24-add-to-cart')) { + return await this.entityManager + .createQueryBuilder(CartItem, 'cart') + .where('cart.customer_id = :customer_id', { customer_id }) + .getMany(); + } + } + + async insertCart( + customer_id: number, + sku_id: number, + qty_ordered: number, + advanced_taste_customization: string, + basic_taste_customization: string, + portion_customization: string, + advanced_taste_customization_obj: string, + basic_taste_customization_obj: string, + notes: string, + restaurant_id: number, + ): Promise { + if (this.flagService.isFeatureEnabled('fes-24-add-to-cart')) { + await this.entityManager + .createQueryBuilder() + .insert() + .into(CartItem) + .values({ + customer_id: customer_id, + sku_id: sku_id, + qty_ordered: qty_ordered, + advanced_taste_customization: advanced_taste_customization, + basic_taste_customization: basic_taste_customization, + portion_customization: portion_customization, + advanced_taste_customization_obj: advanced_taste_customization_obj, + basic_taste_customization_obj: basic_taste_customization_obj, + notes: notes, + restaurant_id: restaurant_id, + }) + .execute(); + } + } + + async updateCart( + item_id: number, + customer_id: number, + sku_id: number, + qty_ordered: number, + advanced_taste_customization: string, + basic_taste_customization: string, + portion_customization: string, + advanced_taste_customization_obj: string, + basic_taste_customization_obj: string, + notes: string, + restaurant_id: number, + ): Promise { + if (this.flagService.isFeatureEnabled('fes-24-add-to-cart')) { + await this.entityManager + .createQueryBuilder() + .update(CartItem) + .set({ + customer_id: customer_id, + sku_id: sku_id, + qty_ordered: qty_ordered, + advanced_taste_customization: advanced_taste_customization, + basic_taste_customization: basic_taste_customization, + portion_customization: portion_customization, + advanced_taste_customization_obj: advanced_taste_customization_obj, + basic_taste_customization_obj: basic_taste_customization_obj, + notes: notes, + restaurant_id: restaurant_id, + }) + .where('item_id = :item_id', { item_id }) + .execute(); } } } diff --git a/src/feature/cart/dto/add-to-cart-request.dto.ts b/src/feature/cart/dto/add-to-cart-request.dto.ts index afd2a6a..2f88a5a 100644 --- a/src/feature/cart/dto/add-to-cart-request.dto.ts +++ b/src/feature/cart/dto/add-to-cart-request.dto.ts @@ -3,11 +3,15 @@ export class AddToCartRequest { sku_id: number; qty_ordered: number; advanced_taste_customization_obj: OptionSelection[]; - basic_taste_customization_obj: OptionSelection[]; + basic_taste_customization_obj: BasicTasteSelection[]; notes: string; + lang?: string; } interface OptionSelection { option_id: string; value_id: string; } +interface BasicTasteSelection { + no_adding_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 index 9f1a1ec..8947dd1 100644 --- a/src/feature/cart/dto/add-to-cart-response.dto.ts +++ b/src/feature/cart/dto/add-to-cart-response.dto.ts @@ -5,7 +5,6 @@ export class AddToCartResponse extends GeneralResponse { } interface AddToCartResponseData { - restaurant_id: number; customer_id: number; cart_info: CartItem[]; } @@ -18,14 +17,9 @@ interface CartItem { advanced_taste_customization: string; basic_taste_customization: string; portion_customization: string; - advanced_taste_customization_obj: OptionSelection[]; - basic_taste_customization_obj: OptionSelection[]; + advanced_taste_customization_obj: string; + basic_taste_customization_obj: string; notes: string; restaurant_id: number; created_at: Date; } - -interface OptionSelection { - option_id: string; - value_id: string; -} diff --git a/src/feature/common/common.module.ts b/src/feature/common/common.module.ts index cfd2994..54007df 100644 --- a/src/feature/common/common.module.ts +++ b/src/feature/common/common.module.ts @@ -20,6 +20,7 @@ import { TasteValueExt } from 'src/entity/taste-value-ext.entity'; import { BasicCustomization } from 'src/entity/basic-customization.entity'; import { NoAddingExt } from 'src/entity/no-adding-ext.entity'; import { RestaurantExt } from 'src/entity/restaurant-ext.entity'; +import { SkuMenuItemVariant } from 'src/entity/sku-menu-item-variant.entity'; @Global() @Module({ @@ -44,6 +45,7 @@ import { RestaurantExt } from 'src/entity/restaurant-ext.entity'; BasicCustomization, NoAddingExt, RestaurantExt, + SkuMenuItemVariant, ]), ], exports: [CommonService], diff --git a/src/feature/common/common.service.ts b/src/feature/common/common.service.ts index 14f3e3d..792aee0 100644 --- a/src/feature/common/common.service.ts +++ b/src/feature/common/common.service.ts @@ -1,9 +1,11 @@ import { Inject, Injectable } from '@nestjs/common'; import { + BasicTasteSelection, DeliveryRestaurant, OptionSelection, Review, TextByLang, + ValidationResult, } from 'src/type'; import { FlagsmithService } from 'src/dependency/flagsmith/flagsmith.service'; import { RestaurantExt } from 'src/entity/restaurant-ext.entity'; @@ -17,9 +19,10 @@ 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'; +import { NoAddingExt } from 'src/entity/no-adding-ext.entity'; +import { SkuMenuItemVariant } from 'src/entity/sku-menu-item-variant.entity'; +import { BasicCustomization } from 'src/entity/basic-customization.entity'; @Injectable() export class CommonService { @@ -244,10 +247,190 @@ export class CommonService { strArr.push(str); } - console.log('strArr', strArr); result = strArr.join(' - '); return result; } } + + async interpretBasicTaseCustomization( + obj_list: BasicTasteSelection[], + lang: string = 'vie', + ): Promise { + if (this.flagService.isFeatureEnabled('fes-24-add-to-cart')) { + let result: string = ''; + + //if object is empty => return '' + if (obj_list.length == 0) { + result = ''; + return result; + } + + //get unique no_adding_id from obj_list + const uniqueNoAddingIds = obj_list + .map((i) => i.no_adding_id) + .filter((value, index, self) => { + return self.indexOf(value) === index; + }); + + result = ( + await this.entityManager + .createQueryBuilder(NoAddingExt, 'ext') + .where('ext.no_adding_id IN (:...uniqueNoAddingIds)', { + uniqueNoAddingIds, + }) + .andWhere('ext.ISO_language_code = :lang', { lang }) + .getMany() + ) + .map((i) => i.description) + .join(' - '); + + return result; + } + } + + async interpretPortionCustomization( + sku_id: number, + lang: string = 'vie', + ): Promise { + if (this.flagService.isFeatureEnabled('fes-24-add-to-cart')) { + let result: string = ''; + + const variants = await this.entityManager + .createQueryBuilder(SkuMenuItemVariant, 'variant') + .leftJoinAndSelect('variant.attribute', 'attribute') + .leftJoinAndSelect( + 'attribute.menu_item_variant_ext_obj', + 'attributeExt', + ) + .leftJoinAndSelect('variant.value', 'value') + .leftJoinAndSelect('value.unit_obj', 'unit') + .where('variant.sku_id = :sku_id', { sku_id }) + .andWhere('attributeExt.ISO_language_code = :lang', { lang }) + .getMany(); + + if (variants.length == 0) { + result = ''; + return result; + } + + result = variants + .map((i) => { + return ( + i.attribute.menu_item_variant_ext_obj[0].name + + ' ' + + i.value.value + + i.value.unit_obj.symbol + ); + }) + .join(' - '); + + return result; + } + } + + async getBasicCustomizationByMenuItemId( + menu_item_id: number, + ): Promise { + const data = await this.entityManager + .createQueryBuilder(BasicCustomization, 'basicCustomization') + .leftJoinAndSelect('basicCustomization.extension', 'ext') + .where('basicCustomization.menu_item_id = :menu_item_id', { + menu_item_id, + }) + .getMany(); + return data; + } + async getTasteCustomizationByMenuItemId( + menu_item_id: number, + ): Promise { + const data = await this.entityManager + .createQueryBuilder(MenuItemVariant, 'variant') + .leftJoinAndSelect('variant.options', 'options') + .leftJoinAndSelect('variant.taste_ext', 'tasteExt') + .leftJoinAndSelect('options.taste_value_ext', 'tasteValueExt') + .where('variant.menu_item_id = :menu_item_id', { menu_item_id }) + .andWhere("variant.type = 'taste'") + .getMany(); + return data; + } + + async validateAdvacedTasteCustomizationObjWithMenuItem( + obj_list: OptionSelection[], + menu_item_id: number, + ): Promise { + // Check if the advanced_taste_customization_obj is all available to this menu item + if (this.flagService.isFeatureEnabled('fes-24-add-to-cart')) { + const result: ValidationResult = { + isValid: true, + message: null, + data: null, + }; + + const avaibleAdvanceTasteCustomizationList = await this.entityManager + .createQueryBuilder(MenuItemVariant, 'variant') + .leftJoinAndSelect('variant.options', 'options') + .where('variant.menu_item_id = :menu_item_id', { + menu_item_id, + }) + .andWhere("variant.type = 'taste'") + .getMany(); + for (const obj of obj_list) { + //find the attribute + const attribute = avaibleAdvanceTasteCustomizationList.find( + (i) => i.menu_item_variant_id.toString() == obj.option_id, + ); + if (!attribute) { + result.isValid = false; + result.message = `Advanced Taste Customization: option_id ${obj.option_id} cannot be found`; + break; + } + //check the value + const value = attribute.options.find( + (i) => i.menu_item_variant_option_id.toString() == obj.value_id, + ); + if (!value) { + result.isValid = false; + result.message = `Advanced Taste Customization: value_id ${obj.value_id} is not availabe for option_id ${obj.option_id}`; + break; + } + } + + return result; + } + } + + async validateBasicTasteCustomizationObjWithMenuItem( + obj_list: BasicTasteSelection[], + menu_item_id: number, + ): Promise { + // Check if the advanced_taste_customization_obj is all available to this menu item + if (this.flagService.isFeatureEnabled('fes-24-add-to-cart')) { + const result: ValidationResult = { + isValid: true, + message: null, + data: null, + }; + + const avaibleBasicTasteCustomizationList = await this.entityManager + .createQueryBuilder(BasicCustomization, 'basicCustomization') + .where('basicCustomization.menu_item_id = :menu_item_id', { + menu_item_id, + }) + .getMany(); + for (const obj of obj_list) { + //find the attribute + const noAddingId = avaibleBasicTasteCustomizationList.find( + (i) => i.no_adding_id == obj.no_adding_id, + ); + if (!noAddingId) { + result.isValid = false; + result.message = `Basic Taste Customization: no_adding_id ${obj.no_adding_id} cannot be found`; + break; + } + } + + return result; + } + } } diff --git a/src/feature/food/food.service.ts b/src/feature/food/food.service.ts index 2aa0f6a..9efbad5 100644 --- a/src/feature/food/food.service.ts +++ b/src/feature/food/food.service.ts @@ -175,13 +175,13 @@ export class FoodService { //Get taste customization const tasteCustomization = - await this.getTasteCustomizationByMenuItemId(menuItemId); + await this.commonService.getTasteCustomizationByMenuItemId(menuItemId); const convertedTasteCustomization = await this.convertTasteCustomization(tasteCustomization); //Get basic customization const basicCustomization = - await this.getBasicCustomizationByMenuItemId(menuItemId); + await this.commonService.getBasicCustomizationByMenuItemId(menuItemId); const convertedBasicCustomization = []; for (const basic of basicCustomization) { const customizedItem = { @@ -342,32 +342,32 @@ export class FoodService { return data; } - async getTasteCustomizationByMenuItemId( - menu_item_id: number, - ): Promise { - const data = await this.entityManager - .createQueryBuilder(MenuItemVariant, 'variant') - .leftJoinAndSelect('variant.options', 'options') - .leftJoinAndSelect('variant.taste_ext', 'tasteExt') - .leftJoinAndSelect('options.taste_value_ext', 'tasteValueExt') - .where('variant.menu_item_id = :menu_item_id', { menu_item_id }) - .andWhere("variant.type = 'taste'") - .getMany(); - return data; - } - - async getBasicCustomizationByMenuItemId( - menu_item_id: number, - ): Promise { - const data = await this.entityManager - .createQueryBuilder(BasicCustomization, 'basicCustomization') - .leftJoinAndSelect('basicCustomization.extension', 'ext') - .where('basicCustomization.menu_item_id = :menu_item_id', { - menu_item_id, - }) - .getMany(); - return data; - } + // async getTasteCustomizationByMenuItemId( + // menu_item_id: number, + // ): Promise { + // const data = await this.entityManager + // .createQueryBuilder(MenuItemVariant, 'variant') + // .leftJoinAndSelect('variant.options', 'options') + // .leftJoinAndSelect('variant.taste_ext', 'tasteExt') + // .leftJoinAndSelect('options.taste_value_ext', 'tasteValueExt') + // .where('variant.menu_item_id = :menu_item_id', { menu_item_id }) + // .andWhere("variant.type = 'taste'") + // .getMany(); + // return data; + // } + + // async getBasicCustomizationByMenuItemId( + // menu_item_id: number, + // ): Promise { + // const data = await this.entityManager + // .createQueryBuilder(BasicCustomization, 'basicCustomization') + // .leftJoinAndSelect('basicCustomization.extension', 'ext') + // .where('basicCustomization.menu_item_id = :menu_item_id', { + // menu_item_id, + // }) + // .getMany(); + // return data; + // } async generatePackageSentenceByLang(packageInfo: Packaging[]) { const sentenceByLang: TextByLang[] = []; diff --git a/src/type/index.ts b/src/type/index.ts index 126a38e..0ad4029 100644 --- a/src/type/index.ts +++ b/src/type/index.ts @@ -79,3 +79,13 @@ export interface OptionSelection { option_id: string; value_id: string; } + +export interface BasicTasteSelection { + no_adding_id: string; +} + +export interface ValidationResult { + isValid: boolean; + message: string; + data?: any; +}