diff --git a/src/modules/payment/DTO/payment.dto.ts b/src/modules/payment/DTO/payment.dto.ts index 460d801..c58fafc 100644 --- a/src/modules/payment/DTO/payment.dto.ts +++ b/src/modules/payment/DTO/payment.dto.ts @@ -8,6 +8,9 @@ class PriceDetailDTO { @Expose() shipping_cost: number; + @Expose() + shipping_cost_on_delivery: number; + @Expose() process_cost: number; diff --git a/src/modules/payment/models/Abstraction/IPayments.ts b/src/modules/payment/models/Abstraction/IPayments.ts index e86680c..5b41d5d 100644 --- a/src/modules/payment/models/Abstraction/IPayments.ts +++ b/src/modules/payment/models/Abstraction/IPayments.ts @@ -16,6 +16,7 @@ export interface ICartPayment { export interface IPriceDetails { shipping_cost: number; + shipping_cost_on_delivery: number; process_cost: number; total_retail_price: number; total_payable_price: number; diff --git a/src/modules/payment/models/payments.model.ts b/src/modules/payment/models/payments.model.ts index 8a51f1b..65668b7 100644 --- a/src/modules/payment/models/payments.model.ts +++ b/src/modules/payment/models/payments.model.ts @@ -6,6 +6,7 @@ import { PaymentStatus } from "../../../common/enums/order.enum"; const PaymentPriceDetails = new Schema( { shipping_cost: { type: Number, required: true }, + shipping_cost_on_delivery: { type: Number, default: 0 }, process_cost: { type: Number, required: true }, total_retail_price: { type: Number, required: true }, total_discount: { type: Number, default: 0 }, diff --git a/src/modules/payment/providers/payment.service.ts b/src/modules/payment/providers/payment.service.ts index a142c8e..1b674f4 100644 --- a/src/modules/payment/providers/payment.service.ts +++ b/src/modules/payment/providers/payment.service.ts @@ -21,6 +21,7 @@ import { OrderService } from "../../order/order.service"; import { PricingRepository } from "../../pricing/pricing.repository"; import { ProductService } from "../../product/providers/product.service"; import { ShopRepo } from "../../shop/shop.repository"; +import { ShipmentRepository } from "../../shipment/shipment.repository"; import { IUser } from "../../user/models/Abstraction/IUser"; import { WalletService } from "../../wallet/wallet.service"; import { CartCheckoutDTO } from "../DTO/cartCheckout.dto"; @@ -46,6 +47,7 @@ class PaymentService { @inject(IOCTYPES.WalletService) walletService: WalletService; @inject(IOCTYPES.NotificationService) notificationService: NotificationService; @inject(IOCTYPES.ShopRepo) shopRepo: ShopRepo; + @inject(IOCTYPES.ShipmentRepository) shipmentRepo: ShipmentRepository; //################################################################## //################################################################## @@ -98,7 +100,7 @@ class PaymentService { if (!paymentMethods) throw new BadRequestError(PaymentMessage.PaymentMethodsNotFound); - const { price } = this.calculateTotalPrice(cart, cartShipmentItems); + const { price } = await this.calculateTotalPrice(cart, cartShipmentItems); return { cart, cartShipmentItems, @@ -135,7 +137,7 @@ class PaymentService { const paymentMethod = await this.paymentMethodRepo.findById(cartCheckoutDto.payment_method_id); if (!paymentMethod) throw new BadRequestError(PaymentMessage.PaymentMethodNotFound); - const { price, description } = this.calculateTotalPrice(cart, cartShipmentItems); + const { price, description } = await this.calculateTotalPrice(cart, cartShipmentItems); // extracted to handle payment creation const { payment, paymentData } = await this.handlePaymentCreation(paymentMethod, price, user, description, session); @@ -238,26 +240,33 @@ class PaymentService { //################################################################## //################################################################## // Helper method to calculate the total price of cart items - private calculateTotalPrice(cart: CartDTO, cartShipItem: ICartShipmentItem[]) { + private async calculateTotalPrice(cart: CartDTO, cartShipItem: ICartShipmentItem[]) { const totalShippingCost = cartShipItem.reduce( (sum, item) => sum + item.shipmentItems.reduce((subSum, shipmentItem) => subSum + shipmentItem.shipmentCost, 0), 0, ); - // const totalCouponDiscount = cartShipItem.reduce((sum, item) => sum + item.totalCouponDiscount, 0); - // const totalShippingCost = cartShipItem.reduce((sum, item) => sum + item.shipmentCost, 0); + const shipperIds = [...new Set(cartShipItem.map((item) => item.shipper))]; + const shippers = await this.shipmentRepo.model.find({ _id: { $in: shipperIds } }).select("_id payDeliveryFeeOnDelivery").lean(); + const payOnDeliveryShipperIds = new Set( + shippers.filter((shipper) => shipper.payDeliveryFeeOnDelivery).map((shipper) => shipper._id), + ); + + const onlineShippingCost = cartShipItem.reduce((sum, item) => { + const itemShippingCost = item.shipmentItems.reduce((subSum, shipmentItem) => subSum + shipmentItem.shipmentCost, 0); + return sum + (payOnDeliveryShipperIds.has(item.shipper) ? 0 : itemShippingCost); + }, 0); + const shippingCostOnDelivery = totalShippingCost - onlineShippingCost; const price = { - shipping_cost: totalShippingCost, - // process_cost: totalShippingCost, + shipping_cost: onlineShippingCost, + shipping_cost_on_delivery: shippingCostOnDelivery, process_cost: 0, cart_retail_price: cart.retail_price, - // cart_payable_price: cart.payable_price - cart.coupon_discount, cart_payable_price: cart.payable_price, total_discount: cart.total_discount, coupon_discount: cart.coupon_discount, - // total_payable_price: cart.payable_price + totalShippingCost * 2, - total_payable_price: cart.isWholeSale ? cart.retail_price + totalShippingCost : cart.payable_price + totalShippingCost, + total_payable_price: cart.isWholeSale ? cart.retail_price + onlineShippingCost : cart.payable_price + onlineShippingCost, }; const items = cart.items; @@ -357,6 +366,7 @@ class PaymentService { const priceDetails: Partial = { shipping_cost: price.shipping_cost, + shipping_cost_on_delivery: price.shipping_cost_on_delivery, process_cost: price.process_cost, total_retail_price: price.cart_retail_price, total_discount: price.total_discount, diff --git a/src/modules/shipment/DTO/createShipmentProviders.dto.ts b/src/modules/shipment/DTO/createShipmentProviders.dto.ts index eed0452..4b2c769 100644 --- a/src/modules/shipment/DTO/createShipmentProviders.dto.ts +++ b/src/modules/shipment/DTO/createShipmentProviders.dto.ts @@ -1,5 +1,5 @@ import { Expose, Type } from "class-transformer"; -import { ArrayMinSize, IsArray, IsNotEmpty, IsNumber, IsString, ValidateNested } from "class-validator"; +import { ArrayMinSize, IsArray, IsBoolean, IsNotEmpty, IsNumber, IsOptional, IsString, ValidateNested } from "class-validator"; import { ApiProperty } from "../../../common/decorator/swggerDocs"; @@ -71,4 +71,14 @@ export class CreateShipProvidersDTO { @IsNumber() @ApiProperty({ type: "number", description: "Delivery time in days", example: 3 }) deliveryTime: number; + + @Expose() + @IsOptional() + @IsBoolean() + @ApiProperty({ + type: "boolean", + description: "If true, delivery fee must be paid at delivery time instead of checkout", + example: false, + }) + payDeliveryFeeOnDelivery?: boolean; } diff --git a/src/modules/shipment/DTO/shipment.dto.ts b/src/modules/shipment/DTO/shipment.dto.ts index 213ea0e..69cfa7f 100644 --- a/src/modules/shipment/DTO/shipment.dto.ts +++ b/src/modules/shipment/DTO/shipment.dto.ts @@ -41,6 +41,9 @@ class ShipperInfoDTO { @Expose() totalShippingCost: number; + @Expose() + payDeliveryFeeOnDelivery: boolean; + @Expose() @Type(() => ShipperItemsDTO) items: ShipperItemsDTO[]; @@ -89,4 +92,7 @@ export class ShipmentMethodDTO { @Expose() deliveryType: string; + + @Expose() + payDeliveryFeeOnDelivery: boolean; } diff --git a/src/modules/shipment/models/Abstraction/IShipmentProviders.ts b/src/modules/shipment/models/Abstraction/IShipmentProviders.ts index d622402..f2550b1 100644 --- a/src/modules/shipment/models/Abstraction/IShipmentProviders.ts +++ b/src/modules/shipment/models/Abstraction/IShipmentProviders.ts @@ -7,6 +7,7 @@ export interface IShipmentProviders { costs: IShipmentCost[]; deliveryType: DeliveryType; deliveryTime: number; + payDeliveryFeeOnDelivery: boolean; deleted: boolean; } export interface IShipmentCost { diff --git a/src/modules/shipment/models/shipment.model.ts b/src/modules/shipment/models/shipment.model.ts index 91c6430..fabe748 100644 --- a/src/modules/shipment/models/shipment.model.ts +++ b/src/modules/shipment/models/shipment.model.ts @@ -27,6 +27,7 @@ const ShipmentSchema = new Schema( costs: [ShipmentCostSchema], deliveryType: { type: String, enum: DeliveryType, required: true }, deliveryTime: { type: Number, required: true }, + payDeliveryFeeOnDelivery: { type: Boolean, default: false }, deleted: { type: Boolean, default: false }, }, { timestamps: true, toJSON: { versionKey: false }, _id: false, id: false }, diff --git a/src/modules/shipment/shipment.service.ts b/src/modules/shipment/shipment.service.ts index bd6b8da..ca54925 100644 --- a/src/modules/shipment/shipment.service.ts +++ b/src/modules/shipment/shipment.service.ts @@ -35,7 +35,7 @@ class ShipmentService { //################################################################### //################################################################### async createShipmentProviderS(createDto: CreateShipProvidersDTO) { - const existShipper = await this.shipmentRepo.model.exists({ name: createDto.name }); + const existShipper = await this.shipmentRepo.model.exists({ name: createDto.name, deleted: false }); if (existShipper) throw new BadRequestError(CommonMessage.DuplicateName); const deliveryType = this.getDeliveryType(createDto.deliveryTime); @@ -48,7 +48,7 @@ class ShipmentService { //################################################################### async updateShipmentProvider(shipmentId: number, updateDto: UpdateShipmentProviderDTO) { - const existShipper = await this.shipmentRepo.model.exists({ name: updateDto.name, _id: { $ne: shipmentId } }); + const existShipper = await this.shipmentRepo.model.exists({ name: updateDto.name, _id: { $ne: shipmentId }, deleted: false }); if (existShipper) throw new BadRequestError(CommonMessage.DuplicateName); if (updateDto.deliveryTime) { @@ -157,6 +157,7 @@ class ShipmentService { shipperName: shipmentProvider.name, shippingDaysRange: this.calculateShippingDays(shipmentProvider.deliveryType, shipmentProvider.deliveryTime), totalShippingCost, + payDeliveryFeeOnDelivery: shipmentProvider.payDeliveryFeeOnDelivery ?? false, items: shipperItems, }); } @@ -230,15 +231,25 @@ class ShipmentService { })); if (existingCartShipmentItem) { - // update the existing CartShipmentItem + const totalSellingPrice = shipmentItems.reduce((acc, item) => acc + item.selling_price * item.quantity, 0); + const totalRetailPrice = shipmentItems.reduce((acc, item) => acc + item.retail_price * item.quantity, 0); + const totalShipmentCost = shipmentItems.reduce((acc, item) => acc + item.shipmentCost, 0); + const onlineShipmentCost = selectedShipper.payDeliveryFeeOnDelivery ? 0 : totalShipmentCost; + existingCartShipmentItem.shipmentItems = shipmentItems; existingCartShipmentItem.shipper = selectedShipper.shipperId; + existingCartShipmentItem.totalSellingPrice = totalSellingPrice; + existingCartShipmentItem.totalRetailPrice = totalRetailPrice; + existingCartShipmentItem.totalShipmentCost = totalShipmentCost; + existingCartShipmentItem.totalPaymentPrice = totalSellingPrice + onlineShipmentCost; + existingCartShipmentItem.itemsCount = shipmentItems.reduce((acc, item) => acc + item.quantity, 0); await existingCartShipmentItem.save(); } else { const totalSellingPrice = shipmentItems.reduce((acc, item) => acc + item.selling_price * item.quantity, 0); const totalRetailPrice = shipmentItems.reduce((acc, item) => acc + item.retail_price * item.quantity, 0); const totalShipmentCost = shipmentItems.reduce((acc, item) => acc + item.shipmentCost, 0); + const onlineShipmentCost = selectedShipper.payDeliveryFeeOnDelivery ? 0 : totalShipmentCost; // create a new CartShipmentItem await this.cartShipItemRepo.model.create({ @@ -249,7 +260,7 @@ class ShipmentService { totalSellingPrice, totalRetailPrice, totalShipmentCost, - totalPaymentPrice: totalShipmentCost + totalSellingPrice, + totalPaymentPrice: totalSellingPrice + onlineShipmentCost, itemsCount: shipmentItems.reduce((acc, item) => acc + item.quantity, 0), }); } @@ -261,12 +272,15 @@ class ShipmentService { (sum, item) => sum + item.shipmentItems.reduce((subSum, shipmentItem) => subSum + shipmentItem.shipmentCost, 0), 0, ); + const onlineShippingCost = cartShipmentItems.reduce((sum, item) => sum + (item.totalPaymentPrice - item.totalSellingPrice), 0); return { cartShipmentItems: cartShipmentItems, shippingCost: totalShippingCost, - processCost: totalShippingCost, - payable_price: cart.payable_price + totalShippingCost * 2, + onlineShippingCost, + shippingCostOnDelivery: totalShippingCost - onlineShippingCost, + processCost: onlineShippingCost, + payable_price: cart.payable_price + onlineShippingCost, }; }