diff --git a/src/apis/coupon/CouponItemDto.ts b/src/apis/coupon/CouponItemDto.ts index a89eba1b..a1d251bf 100644 --- a/src/apis/coupon/CouponItemDto.ts +++ b/src/apis/coupon/CouponItemDto.ts @@ -63,3 +63,13 @@ export interface Coupons { memberCouponInfoReadItemResponse: Coupon[] totalCounts: number } + +export interface MultipleProductsCouponRequest { + products: ProductCategoryPair[] +} + +export interface MultipleProductCouponsResponse { + coupons: { + [productId: number]: CouponInfoItemResponse[] + } +} diff --git a/src/apis/coupon/coupon.ts b/src/apis/coupon/coupon.ts index f96b4243..a2e45e9c 100644 --- a/src/apis/coupon/coupon.ts +++ b/src/apis/coupon/coupon.ts @@ -1,11 +1,13 @@ import axios, { AxiosError } from 'axios' -import { authAxiosInstance } from '@/apis/utils/index' +import { authAxiosInstance, defaultAxiosInstance } from '@/apis/utils/index' import type { CouponInfoItemWithAvailabilityResponse, ProductCategoryPair, CheckoutCouponApplicationResponse, CouponInfoItemResponse, - MultipleCouponDownloadResponse + MultipleCouponDownloadResponse, + MultipleProductsCouponRequest, + MultipleProductCouponsResponse } from '@/apis/coupon/CouponItemDto' import { openInternalServerErrorNotification } from '@/utils/Toast' import { warningModal } from '@/utils/Modal' @@ -147,3 +149,29 @@ export const getMyCoupons = async (page: Number) => { throw error } } + +export const getMultipleProductsCoupons = async ( + requestPayload: MultipleProductsCouponRequest +): Promise => { + try { + const response = await defaultAxiosInstance.post( + `${PROMOTION_PREFIX_PATH}${COUPON_DOMAIN_PREFIX_PATH}/multiple-products`, + requestPayload + ) + return response.data + } catch (error) { + if (error instanceof AxiosError) { + if (error.response) { + if (error.response.status >= 400 && error.response.status < 500) { + await warningModal('알림', error.response.data.message) + console.error(`Client Error=${error.response.data.message}`) + } + if (error.response.status >= 500) { + openInternalServerErrorNotification() + console.error('Internal Server Error') + } + } + } + throw error + } +} diff --git a/src/apis/product/ProductDto.ts b/src/apis/product/ProductDto.ts index d9a7aa34..1cbd6037 100644 --- a/src/apis/product/ProductDto.ts +++ b/src/apis/product/ProductDto.ts @@ -3,6 +3,10 @@ export interface ReadProductSliceResponse { productResponses: ReadProductResponse[] } +export interface ExtendedReadProductResponse extends ReadProductResponse { + categoryId: number +} + export interface ReadProductResponse { id: number brandName: string @@ -13,7 +17,7 @@ export interface ReadProductResponse { imgUrl: string avgRating: number reviewCount: number - coupons: CouponInfoItemResponse[] + coupons?: CouponInfoItemResponse[] } export interface CouponInfoItemResponse { @@ -62,9 +66,16 @@ export interface ReadCacheProductListResponse { responses: ReadCacheProductResponse[] } +export interface ExtendedReadCacheProductResponse extends ReadCacheProductResponse { + avgRating: number + reviewCount: number + coupons: CouponInfoItemResponse[] +} + export interface ReadCacheProductResponse { id: number brandName: string + categoryId: number categoryName: string price: number name: string diff --git a/src/components/product/ProductListPriceDisplay.vue b/src/components/product/ProductListPriceDisplay.vue index daadf38d..6651d2ff 100644 --- a/src/components/product/ProductListPriceDisplay.vue +++ b/src/components/product/ProductListPriceDisplay.vue @@ -1,12 +1,18 @@ @@ -27,43 +33,51 @@ const hasDiscount = computed(() => props.discountPercentage > 0) diff --git a/src/utils/UtilFunc.ts b/src/utils/UtilFunc.ts new file mode 100644 index 00000000..a7ec9ff9 --- /dev/null +++ b/src/utils/UtilFunc.ts @@ -0,0 +1,58 @@ +import type { ReadProductResponse, ReadProductSliceResponse } from '@/apis/product/ProductDto' + +/** + 혜택가 관련 계산입니다. + */ +export const getFloorDiscountPercentage = (product: ReadProductResponse) => { + const { maxDiscountPercentageBeforeFlooring, maxDiscountPercentageAfterFlooring } = + getProductMaxDiscountPercentage(product) + return Math.floor(maxDiscountPercentageAfterFlooring) +} + +export const getFinalPrice = (product: ReadProductResponse) => { + const { maxDiscountPercentageBeforeFlooring, maxDiscountPercentageAfterFlooring } = + getProductMaxDiscountPercentage(product) + return Math.floor(product.price - (product.price * maxDiscountPercentageBeforeFlooring) / 100) +} + +const getProductMaxDiscountPercentage = (product: ReadProductResponse) => { + let maxDiscountPercentageBeforeFlooring = 0 // 가격을 따로 정확히 보여주기 위한 보존값 + let maxDiscountPercentageAfterFlooring = 0 + + // product.coupons 없을시, for Each 발동안함. 0, 0 return + if (!product.coupons) { + return { maxDiscountPercentageBeforeFlooring, maxDiscountPercentageAfterFlooring } + } + product.coupons.forEach((coupon) => { + if (product.price >= coupon.minPurchaseAmount) { + // Listview에서는 일단 1개 구매를 기준으로 display + // N개 이상 구매했을때를 가정한 최적 할인율과 해당 쿠폰 적용환경의 최소개수도 구할까 고민중임. + let discountPercentage = 0 + + if (coupon.discountType === 'PERCENTAGE') { + discountPercentage = coupon.discountValue + } else if (coupon.discountType === 'FIXED_AMOUNT') { + discountPercentage = (coupon.discountValue / product.price) * 100 + } + + // 쿠폰별 maxDiscount cap 적용해서 갱신 + if (coupon.maxDiscountAmount !== null) { + const maxDiscountValue = (coupon.maxDiscountAmount / product.price) * 100 + discountPercentage = Math.min(discountPercentage, maxDiscountValue) + } + + // 직전 iteration과 비교 갱신 + maxDiscountPercentageBeforeFlooring = Math.max( + // 가격을 따로 정확히 보여주기 위한 보존값 + maxDiscountPercentageBeforeFlooring, + discountPercentage + ) + maxDiscountPercentageAfterFlooring = Math.max( + maxDiscountPercentageAfterFlooring, + Math.floor(discountPercentage) + ) + } + }) + + return { maxDiscountPercentageBeforeFlooring, maxDiscountPercentageAfterFlooring } +} diff --git a/src/views/BestProductView.vue b/src/views/BestProductView.vue index 1c14a017..7bd1c5c7 100644 --- a/src/views/BestProductView.vue +++ b/src/views/BestProductView.vue @@ -1,30 +1,78 @@