Skip to content

Commit

Permalink
apply promotion code (#71)
Browse files Browse the repository at this point in the history
Co-authored-by: NHT <[email protected]>
  • Loading branch information
nfesta2023 and hoangtuan910 authored Mar 13, 2024
1 parent cf59d16 commit 316aeef
Show file tree
Hide file tree
Showing 7 changed files with 175 additions and 5 deletions.
19 changes: 16 additions & 3 deletions src/entity/coupon.entity.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { CouponCreatorType, CouponFilterType, CouponType } from 'src/enum';
import {
CalculationType,
CouponCreatorType,
CouponFilterType,
CouponType,
} from 'src/enum';
import {
Column,
Entity,
Expand Down Expand Up @@ -38,8 +43,16 @@ export class Coupon {
@Column({ type: 'int', nullable: false, unique: false })
public discount_value: number;

@Column({ type: 'int', nullable: false, unique: false })
public discount_unit: number;
// @Column({ type: 'int', nullable: false, unique: false })
// public discount_unit: number;

@Column({
type: 'enum',
enum: CalculationType,
nullable: false,
unique: false,
})
public calculation_type: CalculationType;

@Column({ type: 'tinyint', nullable: false, unique: false })
public is_active: number;
Expand Down
5 changes: 5 additions & 0 deletions src/enum/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,8 @@ export enum CouponFilterType {
INCLUDED = 'included',
EXCLUDED = 'excluded',
}

export enum CalculationType {
PERCENTAGE = 'percentage',
FIXED = 'fixed',
}
11 changes: 11 additions & 0 deletions src/feature/order/dto/apply-promotion-code-request.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export class ApplyPromotionCodeRequest {
coupon_code: string;
restaurant_id: number;
items: CouponAppliedItem[];
}
interface CouponAppliedItem {
sku_id: number;
qty_ordered: number;
price_after_discount: number;
packaging_price: number;
}
13 changes: 13 additions & 0 deletions src/feature/order/dto/apply-promotion-code-response.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export class ApplyPromotionCodeResponse {
discount_amount: number;
currency: string;
coupon_code: string;
restaurant_id: number;
items: CouponAppliedItem[];
}
interface CouponAppliedItem {
sku_id: number;
qty_ordered: number;
price_after_discount: number;
packaging_price: number;
}
56 changes: 56 additions & 0 deletions src/feature/order/order.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import { GetCouponInfoRequest } from './dto/get-coupon-info-request.dto';
import { GetCouponInfoResponse } from './dto/get-coupon-info-response.dto';
import { Coupon } from 'src/entity/coupon.entity';
import { CustomRpcException } from 'src/exceptions/custom-rpc.exception';
import { ApplyPromotionCodeRequest } from './dto/apply-promotion-code-request.dto';
import { ApplyPromotionCodeResponse } from './dto/apply-promotion-code-response.dto';

@Controller('order')
export class OrderController {
Expand Down Expand Up @@ -142,4 +144,58 @@ export class OrderController {

return result;
}

@MessagePattern({ cmd: 'apply_promotion_code' })
@UseFilters(new CustomRpcExceptionFilter())
async applyPromotionCode(
data: ApplyPromotionCodeRequest,
): Promise<ApplyPromotionCodeResponse> {
const { coupon_code, restaurant_id, items } = data;
const result = new ApplyPromotionCodeResponse();
const skuIds = items.map((i) => i.sku_id);
//Validate restaurant exist
const restaurant =
await this.commonService.getRestaurantById(restaurant_id);
if (!restaurant) {
throw new CustomRpcException(2, 'Restaurant doesnot exist');
}

//Validate SKU list belongs to the restaurant
if (items.length > 0) {
const isValidSkuList =
await this.commonService.validateSkuListBelongsToRestaurant(
restaurant_id,
skuIds,
);
if (!isValidSkuList) {
throw new CustomRpcException(
3,
'item list does not belong to the resturant',
);
}
}
const validCoupon = await this.orderService.validateApplyingCouponCode(
coupon_code,
restaurant_id,
skuIds,
);
if (!validCoupon) {
throw new CustomRpcException(4, 'Coupon code is invalid');
}

const discountAmount: number = this.orderService.calculateDiscountAmount(
validCoupon,
items,
);

const unit = await this.orderService.getUnitById(restaurant.unit);

result.discount_amount = discountAmount;
result.currency = unit.symbol;
result.coupon_code = coupon_code;
result.restaurant_id = restaurant_id;
result.items = items;

return result;
}
}
69 changes: 67 additions & 2 deletions src/feature/order/order.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,20 @@ import {
} from '@nestjs/common';
import { InjectEntityManager, InjectRepository } from '@nestjs/typeorm';
import { Order } from 'src/entity/order.entity';
import { CouponFilterType, CouponType, OrderStatus } from 'src/enum';
import {
CalculationType,
CouponFilterType,
CouponType,
OrderStatus,
} from 'src/enum';
import { EntityManager, Repository } from 'typeorm';
import { GetApplicationFeeResponse } from './dto/get-application-fee-response.dto';
import { PaymentOption } from 'src/entity/payment-option.entity';
import { MoneyType } from 'src/type';
import { CouponAppliedItem, MoneyType } from 'src/type';
import { Restaurant } from 'src/entity/restaurant.entity';
import { CustomRpcException } from 'src/exceptions/custom-rpc.exception';
import { Coupon } from 'src/entity/coupon.entity';
import { Unit } from 'src/entity/unit.entity';

@Injectable()
export class OrderService {
Expand Down Expand Up @@ -317,4 +323,63 @@ export class OrderService {

return filteredCoupons;
}

async validateApplyingCouponCode(
coupon_code: string,
restaurant_id: number,
sku_ids: number[],
): Promise<Coupon | undefined> {
let validCoupon: Coupon;
//Get restaurant coupon
const restaurantCoupons: Coupon[] =
await this.getCouponInfoWithRestaurntIds([restaurant_id]);
validCoupon = restaurantCoupons.find((i) => i.coupon_code == coupon_code);
if (validCoupon) {
return validCoupon;
}
//get sku coupon
const skuCoupons: Coupon[] = await this.getCouponInfoWithSkus(sku_ids);

validCoupon = skuCoupons.find((i) => i.coupon_code == coupon_code);
if (validCoupon) {
return validCoupon;
}

return validCoupon;
}

calculateDiscountAmount(coupon: Coupon, items: CouponAppliedItem[]): number {
let discountAmount: number = 0;
//calculate the amount base to apply promotion code
let amount_base: number = 0;
for (const item of items) {
amount_base +=
(item.price_after_discount + item.packaging_price) * item.qty_ordered;
}

// check if the amount base is greater than minimum_order_value
if (amount_base < coupon.mininum_order_value) {
//the order does not reach the minimum order value
throw new CustomRpcException(5, {
minium_order_value: coupon.mininum_order_value,
});
}

//calculate the discount amount
if (coupon.calculation_type === CalculationType.PERCENTAGE) {
discountAmount = amount_base * (coupon.discount_value / 100);
discountAmount = Math.min(discountAmount, coupon.maximum_discount_amount);
} else if (coupon.calculation_type === CalculationType.FIXED) {
discountAmount = coupon.discount_value;
}

return discountAmount;
}

async getUnitById(unit_id: number): Promise<Unit> {
return await this.entityManager
.createQueryBuilder(Unit, 'unit')
.where('unit.unit_id = :unit_id', { unit_id })
.getOne();
}
}
7 changes: 7 additions & 0 deletions src/type/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,3 +197,10 @@ export interface MoneyType {
amount: number;
currency: string;
}

export interface CouponAppliedItem {
sku_id: number;
qty_ordered: number;
price_after_discount: number;
packaging_price: number;
}

0 comments on commit 316aeef

Please sign in to comment.