Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/inventory/inventory.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,6 @@ import { InventorySubscriber } from '../inventory/subscribers/inventory.subscrib
CountryService,
InventorySubscriber,
],
exports: [InventoryService, TypeOrmModule],
})
export class InventoryModule {}
45 changes: 43 additions & 2 deletions src/inventory/inventory.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { Injectable, NotFoundException } from '@nestjs/common';
import {
Injectable,
NotFoundException,
BadRequestException,
} from '@nestjs/common';
import {
CreateInventoryDTO,
UpdateInventoryDTO,
Expand Down Expand Up @@ -136,7 +140,6 @@ export class InventoryService {

return this.inventoryRepository.save(toUpdate);
}

async updateBulkByBranch(
branchId: string,
bulkUpdateDto: BulkUpdateInventoryDTO,
Expand All @@ -146,4 +149,42 @@ export class InventoryService {
const inventoryMap = this.buildInventoryMap(inventories);
return this.applyBulkUpdate(branchId, bulkUpdateDto, inventoryMap);
}
async getBulkTotalInventory(
presentationIds: string[],
): Promise<Record<string, number>> {
const rows = await this.inventoryRepository
.createQueryBuilder('inv')
.select('inv.product_presentation_id', 'presentationId')
.addSelect('SUM(inv.stock_quantity)', 'totalStock')
.where('inv.product_presentation_id IN (:...ids)', {
ids: presentationIds,
})
.groupBy('inv.product_presentation_id')
.getRawMany<{ presentationId: string; totalStock: string }>();

const inventoryMap: Record<string, number> = {};
for (const { presentationId, totalStock } of rows) {
inventoryMap[presentationId] = Number(totalStock);
}
return inventoryMap;
}
async decrementInventory(
presentationId: string,
branchId: string,
amount: number,
): Promise<void> {
const inv = await this.inventoryRepository.findOne({
where: {
productPresentation: { id: presentationId },
branch: { id: branchId },
},
});
if (!inv || inv.stockQuantity < amount) {
throw new BadRequestException(
'Insufficient branch inventory to approve order',
);
}
inv.stockQuantity -= amount;
await this.inventoryRepository.save(inv);
}
}
9 changes: 9 additions & 0 deletions src/order/dto/order.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,15 @@ export class CreateOrderDTO {
@IsOptional()
userAddressId?: string;

@ApiProperty({
description: 'Código de cupón (opcional)',
example: 'SAVE10B',
required: false,
})
@IsOptional()
@IsString()
couponCode?: string;

@ApiProperty({
description: 'List of products in the order',
type: [CreateOrderDetailDTO],
Expand Down
8 changes: 7 additions & 1 deletion src/order/order.module.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Module } from '@nestjs/common';
import { Module, forwardRef } from '@nestjs/common';
import { OrderService } from './order.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Order, OrderDetail } from './entities/order.entity';
Expand All @@ -21,6 +21,9 @@ import {
} from './entities/order_delivery.entity';
import { OrderDeliveryController } from './controllers/order-delivery.controller';
import { OrderController } from './controllers/order.controller';
import { Coupon } from 'src/discount/entities/coupon.entity';
import { CouponService } from 'src/discount/services/coupon.service';
import { InventoryModule } from 'src/inventory/inventory.module';

@Module({
imports: [
Expand All @@ -35,8 +38,10 @@ import { OrderController } from './controllers/order.controller';
Promo,
OrderDelivery,
OrderDetailDelivery,
Coupon,
]),
AuthModule,
forwardRef(() => InventoryModule),
],
controllers: [OrderController, OrderDeliveryController],
providers: [
Expand All @@ -47,6 +52,7 @@ import { OrderController } from './controllers/order.controller';
StateService,
CountryService,
PromoService,
CouponService,
],
})
export class OrderModule {}
63 changes: 58 additions & 5 deletions src/order/order.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import {
import { UpdateDeliveryDTO } from './dto/order-delivery.dto';
import { UserAddress } from 'src/user/entities/user-address.entity';
import { UserService } from 'src/user/user.service';
import { CouponService } from 'src/discount/services/coupon.service';
import { InventoryService } from 'src/inventory/inventory.service';

@Injectable()
export class OrderService {
Expand All @@ -36,6 +38,8 @@ export class OrderService {
@InjectRepository(OrderDelivery)
private orderDeliveryRepository: Repository<OrderDelivery>,
private userService: UserService,
private readonly inventoryService: InventoryService,
private couponService: CouponService,
) {}

async create(user: User, createOrderDTO: CreateOrderDTO) {
Expand Down Expand Up @@ -78,7 +82,18 @@ export class OrderService {
...product,
quantity: productsById[product.id],
}));
const totalPrice = productsWithQuantity.reduce((acc, product) => {
const presentationIds = productsWithQuantity.map((p) => p.id);
const inventories =
await this.inventoryService.getBulkTotalInventory(presentationIds);
for (const item of productsWithQuantity) {
const available = inventories[item.id] ?? 0;
if (item.quantity > available) {
throw new BadRequestException(
`Insufficient inventory for productPresentation ${item.id}`,
);
}
}
let totalPrice = productsWithQuantity.reduce((acc, product) => {
const price = product.promo
? product.price - (product.price * product.promo.discount) / 100
: product.price;
Expand All @@ -88,7 +103,12 @@ export class OrderService {
if (productsWithQuantity.length == 0) {
throw new BadRequestException('No products found');
}

if (createOrderDTO.couponCode) {
totalPrice = await this.validateAndApplyCoupon(
createOrderDTO.couponCode,
totalPrice,
);
}
const orderToCreate = this.orderRepository.create({
user,
branch,
Expand Down Expand Up @@ -121,7 +141,30 @@ export class OrderService {
}
return order;
}
private async validateAndApplyCoupon(
couponCode: string,
totalPrice: number,
): Promise<number> {
const coupon = await this.couponService.findOne(couponCode);

if (coupon.expirationDate.getTime() < Date.now()) {
throw new BadRequestException('Coupon expired');
}
if (totalPrice < coupon.minPurchase) {
throw new BadRequestException(
`Minimum purchase of ${coupon.minPurchase} required to use this coupon`,
);
}
if (coupon.maxUses <= 0) {
throw new BadRequestException('Coupon has no remaining uses');
}
const discountAmount = Math.round((totalPrice * coupon.discount) / 100);
const newTotal = totalPrice - discountAmount;
await this.couponService.update(coupon.code, {
maxUses: coupon.maxUses - 1,
});
return newTotal;
}
async findAll(
page: number,
pageSize: number,
Expand Down Expand Up @@ -163,11 +206,21 @@ export class OrderService {
}
return order;
}
async update(id: string, status: OrderStatus) {
async update(id: string, status: OrderStatus): Promise<Order> {
const order = await this.findOne(id);
if (!order) {
throw new BadRequestException('Order not found');
if (order.status === OrderStatus.COMPLETED) {
throw new BadRequestException('A COMPLETED order cannot be modified');
}
if (status === OrderStatus.APPROVED) {
for (const detail of order.details) {
await this.inventoryService.decrementInventory(
detail.productPresentation.id,
order.branch.id,
detail.quantity,
);
}
}

order.status = status;
return await this.orderRepository.save(order);
}
Expand Down
Loading