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
10 changes: 10 additions & 0 deletions src/branch/entities/branch.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import { Order } from 'src/order/entities/order.entity';
import { User } from 'src/user/entities/user.entity';
import { BaseModel } from 'src/utils/entity';
import { Column, Entity, JoinColumn, ManyToOne, OneToMany } from 'typeorm';
import {
OrderDelivery,
OrderDetailDelivery,
} from 'src/order/entities/order_delivery.entity';

@Entity()
export class Branch extends BaseModel {
Expand Down Expand Up @@ -31,4 +35,10 @@ export class Branch extends BaseModel {

@OneToMany(() => Order, (order) => order.branch)
orders: Order[];

@OneToMany(() => OrderDelivery, (orderDelivery) => orderDelivery.branch)
orderDeliveries: OrderDelivery[];

@OneToMany(() => OrderDetailDelivery, (orderDelivery) => orderDelivery.branch)
orderDetailDeliveries: OrderDetailDelivery[];
}
173 changes: 173 additions & 0 deletions src/order/controllers/order-delivery.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
import {
Controller,
Body,
UseGuards,
Req,
HttpCode,
HttpStatus,
Get,
UseInterceptors,
Query,
Param,
Patch,
} from '@nestjs/common';
import { OrderService } from '../order.service';
import { AuthGuard, CustomRequest } from 'src/auth/auth.guard';
import {
ApiBearerAuth,
ApiOperation,
ApiQuery,
ApiResponse,
getSchemaPath,
} from '@nestjs/swagger';
import { PaginationInterceptor } from 'src/utils/pagination.interceptor';
import { PaginationDTO } from 'src/utils/dto/pagination.dto';
import {
OrderDeliveryDTO,
OrderDeliveryQueryDTO,
UpdateDeliveryDTO,
} from '../dto/order-delivery.dto';
import { User } from 'src/user/entities/user.entity';

@Controller('delivery')
export class OrderDeliveryController {
constructor(private readonly orderService: OrderService) {}
@Get()
@UseGuards(AuthGuard)
@ApiBearerAuth()
@UseInterceptors(PaginationInterceptor)
@ApiOperation({ summary: 'List all deliveries with filters' })
@ApiQuery({
name: 'page',
required: false,
description: 'Page number for pagination',
type: Number,
example: 1,
})
@ApiQuery({
name: 'limit',
required: false,
description: 'Items per page',
type: Number,
example: 10,
})
@ApiQuery({
name: 'status',
required: false,
description: 'status',
type: String,
})
@ApiQuery({
name: 'branchId',
required: false,
description: 'Filter by branch ID',
type: String,
})
@ApiQuery({
name: 'employeeId',
required: false,
description: 'For admin: Filter by employee (motorizado) ID',
type: String,
})
@ApiResponse({
status: HttpStatus.OK,
schema: {
allOf: [
{ $ref: getSchemaPath(PaginationDTO) },
{
properties: {
results: {
type: 'array',
items: { $ref: getSchemaPath(OrderDeliveryDTO) },
},
},
},
],
},
})
async findAllOD(
@Req() req: CustomRequest,
@Query() pagination: OrderDeliveryQueryDTO,
): Promise<{ data: OrderDeliveryDTO[]; total: number }> {
const { page, limit, status, branchId, employeeId } = pagination;
const data = await this.orderService.findAllOD(req.user, page, limit, {
status,
branchId,
employeeId,
});
const total = await this.orderService.countDeliveries(req.user, {
status,
branchId,
employeeId,
});

const results: OrderDeliveryDTO[] = data.map((delivery) => ({
id: delivery.id,
orderId: delivery.order.id,
deliveryStatus: delivery.deliveryStatus,
estimatedTime: delivery.estimatedTime,
branchId: delivery.branch ? delivery.branch.id : null,
employeeId: delivery.employee ? delivery.employee.id : null,
}));

return { data: results, total };
}

@Get('/:deliveryId')
@HttpCode(HttpStatus.OK)
@ApiOperation({
summary: 'Get detailed delivery info including contact and address',
})
@ApiResponse({ status: HttpStatus.OK, type: OrderDeliveryDTO })
async getDelivery(
@Param('deliveryId') deliveryId: string,
): Promise<OrderDeliveryDTO> {
const delivery = await this.orderService.getDelivery(deliveryId);

return {
id: delivery.id,
orderId: delivery.order.id,
deliveryStatus: delivery.deliveryStatus,
estimatedTime: delivery.estimatedTime,
branchId: delivery.branch ? delivery.branch.id : null,
employeeId: delivery.employee ? delivery.employee.id : null,
// Data of user:
userName:
delivery.order.user.firstName + ' ' + delivery.order.user.lastName,
userPhone: delivery.order.user.phoneNumber,
// data of the address:
address: delivery.adress.adress,
zipCode: delivery.adress.zipCode,
additionalInformation: delivery.adress.additionalInformation,
referencePoint: delivery.adress.referencePoint,
};
}

@Patch('/:deliveryId')
@HttpCode(HttpStatus.OK)
@ApiOperation({
summary: 'Update delivery status or reject delivery (unassign employee)',
})
@ApiResponse({ status: HttpStatus.OK, type: OrderDeliveryDTO })
async updateDelivery(
@Req() req: Request & { user?: User },
@Param('deliveryId') deliveryId: string,
@Body() updateDeliveryDto: UpdateDeliveryDTO,
): Promise<OrderDeliveryDTO> {
const user = req.user as User;
const updatedDelivery = await this.orderService.updateDelivery(
user,
deliveryId,
updateDeliveryDto,
);

return {
id: updatedDelivery.id,
orderId: updatedDelivery.order.id,
deliveryStatus: updatedDelivery.deliveryStatus,
estimatedTime: updatedDelivery.estimatedTime,
branchId: updatedDelivery.branch ? updatedDelivery.branch.id : null,
employeeId: updatedDelivery.employee ? updatedDelivery.employee.id : null,
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@ import {
ParseUUIDPipe,
Patch,
} from '@nestjs/common';
import { OrderService } from './order.service';
import { OrderService } from '../order.service';
import {
CreateOrderDTO,
OrderQueryDTO,
ResponseOrderDetailedDTO,
ResponseOrderDTO,
UpdateOrderStatusDTO,
} from './dto/order';
} from '../dto/order';
import { AuthGuard, CustomRequest } from 'src/auth/auth.guard';
import {
ApiBearerAuth,
Expand All @@ -31,7 +31,7 @@ import {
} from '@nestjs/swagger';
import { PaginationInterceptor } from 'src/utils/pagination.interceptor';
import { PaginationDTO } from 'src/utils/dto/pagination.dto';
import { OrderStatus, OrderType } from './entities/order.entity';
import { OrderStatus, OrderType } from '../entities/order.entity';
import { UserRole } from 'src/user/entities/user.entity';
import { Roles } from 'src/auth/roles.decorador';
import { RolesGuard } from 'src/auth/roles.guard';
Expand Down
86 changes: 86 additions & 0 deletions src/order/dto/order-delivery.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import {
IsDateString,
IsOptional,
IsString,
IsBoolean,
IsUUID,
} from 'class-validator';
import { PaginationQueryDTO } from 'src/utils/dto/pagination.dto';

export class OrderDeliveryDTO {
@ApiProperty({ description: 'Unique identifier of the delivery' })
id: string;

@ApiProperty({ description: 'Id of the associated order' })
orderId: string;

@ApiProperty({
description: 'Delivery status (e.g., pending, delivered, etc.)',
})
deliveryStatus: string;

@ApiProperty({ description: 'Estimated time for delivery' })
@IsDateString()
estimatedTime: Date;

@ApiProperty({
description: 'Id of the branch associated with the delivery',
nullable: true,
})
branchId: string | null;

@ApiProperty({
description: 'Id of the employee ("motorizado") assigned to the delivery',
nullable: true,
})
employeeId: string | null;

// User contact information (extracted from the order)
@ApiProperty({ description: 'Full name of the user' })
userName?: string;

@ApiProperty({ description: 'Phone number of the user', nullable: true })
userPhone?: string;

// Delivery address information
@ApiProperty({ description: 'Address street', nullable: true })
address?: string;
@ApiProperty({ description: 'Zip code', nullable: true })
zipCode?: string;
@ApiProperty({
description: 'Additional address information',
nullable: true,
})
additionalInformation?: string;
@ApiProperty({ description: 'Reference point for delivery', nullable: true })
referencePoint?: string;
}

export class UpdateDeliveryDTO {
@ApiPropertyOptional({ description: 'New delivery status' })
@IsOptional()
@IsString()
deliveryStatus?: string;

@ApiPropertyOptional({
description: 'indicate the delivery is rejected (employee unassign)',
})
@IsOptional()
@IsBoolean()
reject?: boolean;
}

export class OrderDeliveryQueryDTO extends PaginationQueryDTO {
@IsOptional()
@IsString()
status?: string;

@IsOptional()
@IsUUID()
branchId?: string;

@IsOptional()
@IsUUID()
employeeId?: string;
}
10 changes: 10 additions & 0 deletions src/order/entities/order.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ProductPresentation } from 'src/products/entities/product-presentation.
import { User } from 'src/user/entities/user.entity';
import { BaseModel, UUIDModel } from 'src/utils/entity';
import { Column, Entity, JoinColumn, ManyToOne, OneToMany } from 'typeorm';
import { OrderDelivery, OrderDetailDelivery } from './order_delivery.entity';

export enum OrderType {
PICKUP = 'pickup',
Expand Down Expand Up @@ -42,6 +43,9 @@ export class Order extends BaseModel {

@OneToMany(() => OrderDetail, (orderDetail) => orderDetail.order)
details: OrderDetail[];

@OneToMany(() => OrderDelivery, (orderDelivery) => orderDelivery.order)
orderDeliveries: OrderDelivery[];
}

@Entity()
Expand All @@ -64,4 +68,10 @@ export class OrderDetail extends UUIDModel {

@Column({ type: 'int', name: 'subtotal' })
subtotal: number;

@OneToMany(
() => OrderDetailDelivery,
(orderDeliveryDetail) => orderDeliveryDetail.orderDetail,
)
orderDetailDeliveries: OrderDetailDelivery[];
}
60 changes: 60 additions & 0 deletions src/order/entities/order_delivery.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { Entity, Column, ManyToOne, JoinColumn } from 'typeorm';

import { BaseModel } from 'src/utils/entity';
import { UserAddress } from 'src/user/entities/user-address.entity';
import { User } from 'src/user/entities/user.entity';
import { Branch } from 'src/branch/entities/branch.entity';
import { Order, OrderDetail } from './order.entity';

@Entity('order_delivery')
export class OrderDelivery extends BaseModel {
@ManyToOne(() => Order, (order) => order.orderDeliveries)
@JoinColumn({ name: 'order_id' })
order: Order;

@ManyToOne(() => UserAddress, (adress) => adress.orderDeliveries)
@JoinColumn({ name: 'address_id' })
adress: UserAddress;

@ManyToOne(() => User, (employee) => employee.orderDeliveries, {
nullable: true,
})
@JoinColumn({ name: 'employee_id' })
employee: User;

@Column({ type: 'timestamp with time zone', name: 'estimated_time' })
estimatedTime: Date;

@Column({ type: 'varchar', name: 'delivery_status' })
deliveryStatus: string;

@ManyToOne(() => Branch, (branch) => branch.orderDeliveries)
@JoinColumn({ name: 'branch_id' })
branch: Branch;
}

@Entity('order_detail_delivery')
export class OrderDetailDelivery extends BaseModel {
@ManyToOne(
() => OrderDetail,
(orderDetail) => orderDetail.orderDetailDeliveries,
)
@JoinColumn({ name: 'order_detail_id' })
orderDetail: OrderDetail;

@ManyToOne(() => User, (employee) => employee.orderDetailDeliveries, {
nullable: true,
})
@JoinColumn({ name: 'employee_id' })
employee: User;

@ManyToOne(() => Branch, (branch) => branch.orderDetailDeliveries)
@JoinColumn({ name: 'from_branch_id' })
branch: Branch;

@Column({ type: 'timestamp with time zone', name: 'estimated_time' })
estimatedTime: Date;

@Column({ type: 'varchar', name: 'delivery_status' })
deliveryStatus: string;
}
Loading
Loading