# Inquiries Module - Chỉ Mục File Đầy Đủ **Tạo lúc:** Ngày 11 tháng 4 năm 2026 **Module:** `apps/api/src/modules/inquiries/` **Tổng số file:** 25 --- ## 📋 DANH SÁCH FILE ĐẦY ĐỦ THEO ĐƯỜNG DẪN ### **1. TẦNG ỨNG DỤNG (APPLICATION LAYER)** #### Commands (4 files) ``` 📄 apps/api/src/modules/inquiries/application/commands/create-inquiry/create-inquiry.command.ts Type: Command (CQRS) Purpose: Input DTO cho use case tạo yêu cầu tư vấn Exports: CreateInquiryCommand class Properties: userId, listingId, message, phone Lines: ~8 ``` ``` 📄 apps/api/src/modules/inquiries/application/commands/create-inquiry/create-inquiry.handler.ts Type: Command Handler (CQRS) Purpose: Điều phối quá trình tạo yêu cầu tư vấn Implements: ICommandHandler Returns: CreateInquiryResult { id, listingId, createdAt } Key Logic: • Xác thực tin đăng tồn tại (Prisma.listing.findUnique) • Tạo InquiryEntity qua phương thức factory • Lưu vào repository • Phát sự kiện InquiryCreatedEvent qua EventBus Lines: ~60 Dependencies: • INQUIRY_REPOSITORY (injected) • EventBus (injected) • PrismaService (injected) • LoggerService (injected) ``` ``` 📄 apps/api/src/modules/inquiries/application/commands/mark-inquiry-read/mark-inquiry-read.command.ts Type: Command (CQRS) Purpose: Input DTO để đánh dấu yêu cầu tư vấn đã đọc Exports: MarkInquiryReadCommand class Properties: inquiryId, agentUserId Lines: ~6 ``` ``` 📄 apps/api/src/modules/inquiries/application/commands/mark-inquiry-read/mark-inquiry-read.handler.ts Type: Command Handler (CQRS) Purpose: Điều phối thao tác đánh dấu đã đọc Implements: ICommandHandler Returns: void (không có giá trị trả về) Key Logic: • Tải entity yêu cầu tư vấn từ repository • Tải tin đăng và xác minh quyền sở hữu của môi giới • Gọi inquiry.markAsRead() để cập nhật trạng thái • Lưu qua repository.markAsRead() • Phát sự kiện InquiryReadEvent qua EventBus Authorization Checks: • Yêu cầu tư vấn tồn tại • Tin đăng tồn tại • Người dùng đã đăng ký làm môi giới • ID môi giới khớp với listing.agentId Lines: ~50 Dependencies: • INQUIRY_REPOSITORY (injected) • EventBus (injected) • PrismaService (injected) • LoggerService (injected) ``` #### Queries (4 files) ``` 📄 apps/api/src/modules/inquiries/application/queries/get-inquiries-by-agent/get-inquiries-by-agent.query.ts Type: Query (CQRS) Purpose: Input DTO để lấy danh sách yêu cầu tư vấn của môi giới Exports: GetInquiriesByAgentQuery class Properties: agentUserId, page, limit Lines: ~6 ``` ``` 📄 apps/api/src/modules/inquiries/application/queries/get-inquiries-by-agent/get-inquiries-by-agent.handler.ts Type: Query Handler (CQRS) Purpose: Phân giải danh sách yêu cầu tư vấn phân trang cho một môi giới Implements: IQueryHandler Returns: PaginatedResult Key Logic: • Phân giải ID môi giới từ userId qua Prisma • Ném NotFoundException nếu người dùng không phải môi giới • Ủy quyền cho repository.findByAgent() Lines: ~30 Dependencies: • INQUIRY_REPOSITORY (injected) • PrismaService (injected) ``` ``` 📄 apps/api/src/modules/inquiries/application/queries/get-inquiries-by-listing/get-inquiries-by-listing.query.ts Type: Query (CQRS) Purpose: Input DTO để lấy danh sách yêu cầu tư vấn của tin đăng Exports: GetInquiriesByListingQuery class Properties: listingId, page, limit Lines: ~6 ``` ``` 📄 apps/api/src/modules/inquiries/application/queries/get-inquiries-by-listing/get-inquiries-by-listing.handler.ts Type: Query Handler (CQRS) Purpose: Phân giải danh sách yêu cầu tư vấn phân trang cho một tin đăng Implements: IQueryHandler Returns: PaginatedResult Key Logic: • Ủy quyền trực tiếp cho repository.findByListing() Lines: ~20 Dependencies: • INQUIRY_REPOSITORY (injected) ``` #### Application Tests (4 files) ``` 📄 apps/api/src/modules/inquiries/application/__tests__/create-inquiry.handler.spec.ts Type: Unit Tests (Jest/Vitest) Test Framework: Vitest với hàm Mock (vi.fn) Test Count: 4 Tests: ✓ tạo yêu cầu tư vấn thành công ✓ ném NotFoundException khi không tìm thấy tin đăng ✓ phát sự kiện domain sau khi lưu Mocks: • mockInquiryRepo (IInquiryRepository implementation) • mockEventBus • mockPrisma.listing.findUnique Lines: ~98 ``` ``` 📄 apps/api/src/modules/inquiries/application/__tests__/mark-inquiry-read.handler.spec.ts Type: Unit Tests (Jest/Vitest) Test Count: 5 Tests: ✓ đánh dấu yêu cầu tư vấn là đã đọc thành công ✓ ném NotFoundException khi không tìm thấy yêu cầu tư vấn ✓ ném NotFoundException khi không tìm thấy tin đăng ✓ ném ForbiddenException khi người dùng không phải môi giới của tin đăng ✓ ném ForbiddenException khi không tìm thấy môi giới cho người dùng Mocks: • mockInquiryRepo • mockEventBus • mockPrisma.listing.findUnique • mockPrisma.agent.findUnique Lines: ~130 ``` ``` 📄 apps/api/src/modules/inquiries/application/__tests__/get-inquiries-by-listing.handler.spec.ts Type: Unit Tests (Jest/Vitest) Test Count: 2 Tests: ✓ trả về kết quả phân trang ✓ trả về dữ liệu rỗng khi không tìm thấy yêu cầu tư vấn nào Mocks: • mockInquiryRepo.findByListing Lines: ~69 ``` ``` 📄 apps/api/src/modules/inquiries/application/__tests__/get-inquiries-by-agent.handler.spec.ts Type: Unit Tests (Jest/Vitest) Test Count: 2 Tests: ✓ trả về kết quả phân trang ✓ ném NotFoundException khi không tìm thấy môi giới cho người dùng Mocks: • mockInquiryRepo.findByAgent • mockPrisma.agent.findUnique Lines: ~77 ``` --- ### **2. TẦNG DOMAIN (DOMAIN LAYER)** #### Entities (1 file) ``` 📄 apps/api/src/modules/inquiries/domain/entities/inquiry.entity.ts Type: Aggregate Root (DDD) Extends: AggregateRoot Purpose: Logic nghiệp vụ cốt lõi cho yêu cầu tư vấn Properties (Private): • _listingId: string • _userId: string • _message: string • _phone: string | null • _isRead: boolean Getters (Read-only): • listingId: string • userId: string • message: string • phone: string | null • isRead: boolean Factory Method: • static createNew(id, listingId, userId, message, phone): InquiryEntity - Tạo yêu cầu tư vấn mới với isRead=false - Phát InquiryCreatedEvent - Trả về instance của entity Domain Methods: • markAsRead(): void - Đặt _isRead thành true - Phát InquiryReadEvent Lines: ~63 Dependencies: • AggregateRoot from @modules/shared • InquiryCreatedEvent • InquiryReadEvent ``` #### Events (2 files) ``` 📄 apps/api/src/modules/inquiries/domain/events/inquiry-created.event.ts Type: Domain Event (DDD) Implements: DomainEvent interface Purpose: Báo hiệu rằng một yêu cầu tư vấn đã được tạo Properties: • eventName: string = 'inquiry.created' • occurredAt: Date = new Date() • aggregateId: string • listingId: string • userId: string Lines: ~13 ``` ``` 📄 apps/api/src/modules/inquiries/domain/events/inquiry-read.event.ts Type: Domain Event (DDD) Implements: DomainEvent interface Purpose: Báo hiệu rằng một yêu cầu tư vấn đã được đánh dấu là đã đọc Properties: • eventName: string = 'inquiry.read' • occurredAt: Date = new Date() • aggregateId: string • listingId: string • userId: string Lines: ~13 ``` #### Repositories (2 files) ``` 📄 apps/api/src/modules/inquiries/domain/repositories/inquiry.repository.ts Type: Repository Interface + Symbol (DDD) Purpose: Định nghĩa hợp đồng persistence Exports: • INQUIRY_REPOSITORY: Symbol (DI token) • IInquiryRepository: Interface • PaginatedResult: Interface IInquiryRepository Methods: • findById(id: string): Promise • save(inquiry: InquiryEntity): Promise • markAsRead(id: string): Promise • findByListing(listingId, page, limit): Promise> • findByAgent(agentId, page, limit): Promise> • countUnreadByAgent(agentId): Promise PaginatedResult Interface: • data: T[] • total: number • page: number • limit: number • totalPages: number Lines: ~22 ``` ``` 📄 apps/api/src/modules/inquiries/domain/repositories/inquiry-read.dto.ts Type: Data Transfer Object (Read Model) Purpose: DTO cho kết quả truy vấn Exports: InquiryReadDto interface InquiryReadDto Properties: • id: string • listingId: string • listingTitle: string • userId: string • userName: string • userPhone: string • message: string • phone: string | null • isRead: boolean • createdAt: string Lines: ~13 ``` #### Domain Tests (1 file) ``` 📄 apps/api/src/modules/inquiries/domain/__tests__/inquiry-domain.spec.ts Type: Unit Tests (Jest/Vitest) Test Count: 5 Tests: ✓ createNew() tạo yêu cầu tư vấn với các thuộc tính đúng ✓ createNew() tạo yêu cầu tư vấn với phone là null ✓ createNew() phát InquiryCreatedEvent ✓ markAsRead() đặt isRead thành true ✓ markAsRead() phát InquiryReadEvent Focus: Hành vi entity và việc phát sự kiện domain Lines: ~96 ``` --- ### **3. TẦNG HẠ TẦNG (INFRASTRUCTURE LAYER)** #### Repository Implementation (1 file) ``` 📄 apps/api/src/modules/inquiries/infrastructure/repositories/prisma-inquiry.repository.ts Type: Repository Implementation (Service) Implements: IInquiryRepository Decorator: @Injectable() Purpose: Persistence dựa trên Prisma Methods: 1. findById(id: string): Promise - Query: prisma.inquiry.findUnique({ where: { id } }) - Returns: InquiryEntity | null 2. save(entity: InquiryEntity): Promise - Query: prisma.inquiry.create({ data: {...} }) - Ánh xạ thuộc tính entity sang Prisma model 3. markAsRead(id: string): Promise - Query: prisma.inquiry.update({ where: { id }, data: { isRead: true } }) 4. findByListing(listingId, page, limit): Promise> - Query: prisma.inquiry.findMany() với joins - Includes: listing.property.title, user.fullName, user.phone - Pagination: mô hình skip/take - Sorting: orderBy: { createdAt: 'desc' } - Returns: Mảng InquiryReadDto đã ánh xạ kèm thông tin phân trang 5. findByAgent(agentId, page, limit): Promise> - Query: prisma.inquiry.findMany() lọc theo listing.agentId - Cùng includes và phân trang như findByListing - Filter: { listing: { agentId } } 6. countUnreadByAgent(agentId): Promise - Query: prisma.inquiry.count() - Filter: { isRead: false, listing: { agentId } } Helper Method: • toDomain(raw: PrismaInquiry): InquiryEntity - Ánh xạ bản ghi cơ sở dữ liệu sang domain entity Lines: ~146 Dependencies: • PrismaService (injected) • InquiryEntity • IInquiryRepository (implements) ``` --- ### **4. TẦNG TRÌNH BÀY (PRESENTATION LAYER)** #### Controller (1 file) ``` 📄 apps/api/src/modules/inquiries/presentation/controllers/inquiries.controller.ts Type: NestJS Controller Decorator: @Controller('inquiries') Decorator: @ApiTags('inquiries') Purpose: Các endpoint HTTP API Endpoints: 1. POST /inquiries Decorator: @Post() Auth: @UseGuards(JwtAuthGuard) Method: createInquiry(dto: CreateInquiryDto, user: JwtPayload) Body: CreateInquiryDto { listingId, message, phone? } Returns: CreateInquiryResult { id, listingId, createdAt } Status Codes: 201 Created, 400 Bad Request, 401 Unauthorized, 404 Not Found Logic: • Điều phối CreateInquiryCommand qua CommandBus • Truyền user.sub làm userId 2. GET /inquiries/listing/:listingId Decorator: @Get('listing/:listingId') Auth: @UseGuards(JwtAuthGuard) Method: getByListing(listingId: string, dto: ListInquiriesDto) Query: page?, limit? Returns: PaginatedResult Status Codes: 200 OK, 401 Unauthorized Logic: • Điều phối GetInquiriesByListingQuery qua QueryBus • Mặc định page=1, limit=20 3. GET /inquiries/agent/me Decorator: @Get('agent/me') Auth: @UseGuards(JwtAuthGuard, RolesGuard) Decorator: @Roles('AGENT') Method: getMyInquiries(user: JwtPayload, dto: ListInquiriesDto) Query: page?, limit? Returns: PaginatedResult Status Codes: 200 OK, 401 Unauthorized, 403 Forbidden Logic: • Điều phối GetInquiriesByAgentQuery qua QueryBus • Truyền user.sub làm agentUserId • Mặc định page=1, limit=20 4. PATCH /inquiries/:id/read Decorator: @Patch(':id/read') Auth: @UseGuards(JwtAuthGuard, RolesGuard) Decorator: @Roles('AGENT') Method: markAsRead(id: string, user: JwtPayload) Returns: { success: boolean } Status Codes: 200 OK, 401 Unauthorized, 403 Forbidden, 404 Not Found Logic: • Điều phối MarkInquiryReadCommand qua CommandBus • Truyền user.sub làm agentUserId • Trả về { success: true } Lines: ~121 Dependencies: • CommandBus (injected) • QueryBus (injected) • @nestjs/swagger decorators • @modules/auth guards and decorators ``` #### DTOs (2 files) ``` 📄 apps/api/src/modules/inquiries/presentation/dto/create-inquiry.dto.ts Type: Data Transfer Object (Validation) Purpose: Xác thực body của request POST /inquiries Decorators: @ApiProperty, @ApiPropertyOptional Properties: • listingId: string (required) Validators: @IsString(), @IsNotEmpty() API Doc: "ID of the listing" • message: string (required) Validators: @IsString(), @IsNotEmpty(), @MaxLength(2000) API Doc: "Tin nhắn yêu cầu tư vấn" (Consultation request message) Max: 2000 ký tự • phone?: string (optional) Validators: @IsOptional(), @IsString() API Doc: "Số điện thoại liên hệ" (Contact phone number) Lines: ~21 Dependencies: class-validator, @nestjs/swagger ``` ``` 📄 apps/api/src/modules/inquiries/presentation/dto/list-inquiries.dto.ts Type: Data Transfer Object (Validation) Purpose: Xác thực query parameter cho các endpoint danh sách Decorators: @ApiPropertyOptional, @Type Properties: • page?: number (optional) Validators: @IsOptional(), @IsInt(), @Min(1), @Type(() => Number) API Doc: "Số trang", mặc định: 1 Example: 1 • limit?: number (optional) Validators: @IsOptional(), @IsInt(), @Min(1), @Max(100), @Type(() => Number) API Doc: "Số mục mỗi trang", mặc định: 20 Example: 20 Max: 100 Lines: ~21 Dependencies: class-validator, @nestjs/swagger, class-transformer ``` #### Presentation Tests (1 file) ``` 📄 apps/api/src/modules/inquiries/presentation/__tests__/inquiries.controller.spec.ts Type: Unit Tests (Jest/Vitest) Test Count: 6 Tests: ✓ POST /inquiries điều phối CreateInquiryCommand với tham số đúng ✓ POST /inquiries truyền phone là null khi không được cung cấp ✓ GET /listing/:id điều phối GetInquiriesByListingQuery với giá trị mặc định ✓ GET /listing/:id truyền phân trang tùy chỉnh ✓ GET /agent/me điều phối GetInquiriesByAgentQuery với người dùng hiện tại ✓ PATCH /:id/read điều phối MarkInquiryReadCommand và trả về thành công Mocks: • mockCommandBus với hàm mock execute() • mockQueryBus với hàm mock execute() • mockBuyer user object (sub='buyer-1', role='BUYER') • mockAgent user object (sub='agent-1', role='AGENT') Lines: ~105 ``` --- ### **5. TẦNG MODULE (MODULE LAYER)** ``` 📄 apps/api/src/modules/inquiries/inquiries.module.ts Type: NestJS Module Decorator: @Module() Purpose: Cấu hình module, khai báo phụ thuộc, xuất khẩu Imports: [CqrsModule] Controllers: [InquiriesController] Providers: • { provide: INQUIRY_REPOSITORY, useClass: PrismaInquiryRepository } - Ánh xạ token dependency injection • CommandHandlers array: [CreateInquiryHandler, MarkInquiryReadHandler] • QueryHandlers array: [GetInquiriesByListingHandler, GetInquiriesByAgentHandler] Exports: [INQUIRY_REPOSITORY] - Cung cấp repository cho các module khác Lines: ~29 Dependencies: • @nestjs/common • @nestjs/cqrs • All handlers, repository, controller ``` ``` 📄 apps/api/src/modules/inquiries/index.ts Type: Barrel Export (Public API) Purpose: Định nghĩa giao diện công khai của module Exports: • InquiriesModule (default export for app imports) • INQUIRY_REPOSITORY (DI token) • IInquiryRepository (interface type) • InquiryEntity (for external access) Lines: ~4 ``` --- ## 📊 THỐNG KÊ FILE ### Theo Tầng | Tầng | Files | Loại | Mục đích | |------|-------|------|----------| | **Presentation** | 5 | Controller + DTOs + Tests | Endpoint HTTP và xác thực đầu vào | | **Application** | 8 | Commands/Queries + Handlers + Tests | Điều phối use case | | **Domain** | 6 | Entities + Events + Repository Interface + Tests | Logic nghiệp vụ và hợp đồng | | **Infrastructure** | 1 | Repository Implementation | Lưu trữ cơ sở dữ liệu | | **Module** | 2 | Module + Exports | Cấu hình và API công khai | | **TỔNG** | 25 | - | - | ### Theo Loại | Loại | Số lượng | |------|----------| | File nguồn (.ts, không phải test) | 19 | | File test (.spec.ts) | 6 | | **Tổng** | **25** | ### Theo Mục Đích | Mục đích | Số lượng | Files | |----------|----------|-------| | Controllers | 1 | inquiries.controller.ts | | DTOs | 2 | create-inquiry.dto.ts, list-inquiries.dto.ts | | Commands | 2 | create-inquiry.command.ts, mark-inquiry-read.command.ts | | Queries | 2 | get-inquiries-by-*.query.ts (2 files) | | Handlers | 4 | 2 command handlers + 2 query handlers | | Entities | 1 | inquiry.entity.ts | | Events | 2 | inquiry-created.event.ts, inquiry-read.event.ts | | Repositories | 3 | interface + 2 DTOs + 1 implementation | | Tests | 6 | 5 bộ test trải rộng các tầng | | Module | 2 | inquiries.module.ts, index.ts | --- ## 🔍 HƯỚNG DẪN TÌM KIẾM FILE ### Tìm kiếm Logic Nghiệp Vụ? → **`domain/entities/inquiry.entity.ts`** - Quy tắc nghiệp vụ cốt lõi → **`domain/events/*.event.ts`** - Sự kiện nghiệp vụ ### Tìm kiếm Use Case? → **`application/commands/*/`** - Thao tác ghi → **`application/queries/*/`** - Thao tác đọc → **`application/[type]/__tests__/`** - Các test case ### Tìm kiếm HTTP Endpoint? → **`presentation/controllers/inquiries.controller.ts`** - Toàn bộ 4 endpoint ### Tìm kiếm Xác Thực Đầu Vào? → **`presentation/dto/`** - Tất cả DTOs kèm validator ### Tìm kiếm Logic Cơ Sở Dữ Liệu? → **`infrastructure/repositories/prisma-inquiry.repository.ts`** - Tất cả Prisma query ### Tìm kiếm Tests? → **`domain/__tests__/`** - Hành vi domain → **`application/__tests__/`** - Test handler → **`presentation/__tests__/`** - Test controller --- ## 🔗 ĐỒ THỊ PHỤ THUỘC (DEPENDENCY GRAPH) ``` presentation/controllers/inquiries.controller.ts ├── application/commands/create-inquiry/create-inquiry.handler.ts ├── application/commands/mark-inquiry-read/mark-inquiry-read.handler.ts ├── application/queries/get-inquiries-by-*/[name].handler.ts └── CommandBus/QueryBus (NestJS CQRS) application/commands/*/[name].handler.ts ├── domain/entities/inquiry.entity.ts ├── domain/repositories/inquiry.repository.ts (interface) ├── domain/events/*.event.ts └── @modules/shared (AggregateRoot, exceptions) application/queries/*/[name].handler.ts ├── domain/repositories/inquiry.repository.ts └── domain/repositories/inquiry-read.dto.ts domain/entities/inquiry.entity.ts ├── @modules/shared (AggregateRoot) └── domain/events/*.event.ts infrastructure/repositories/prisma-inquiry.repository.ts ├── domain/entities/inquiry.entity.ts ├── domain/repositories/inquiry.repository.ts (implements) ├── domain/repositories/inquiry-read.dto.ts └── @prisma/client (Prisma) inquiries.module.ts ├── All handlers ├── All repositories ├── InquiriesController └── CqrsModule (NestJS) ``` --- ## 📝 THAM CHIẾU CHÉO FILE ### Các File Sử Dụng Symbol INQUIRY_REPOSITORY - `inquiries.module.ts` - Cung cấp implementation - `application/commands/create-inquiry/create-inquiry.handler.ts` - Injects - `application/commands/mark-inquiry-read/mark-inquiry-read.handler.ts` - Injects - `application/queries/get-inquiries-by-agent/get-inquiries-by-agent.handler.ts` - Injects - `application/queries/get-inquiries-by-listing/get-inquiries-by-listing.handler.ts` - Injects ### Các File Tạo/Chỉnh Sửa InquiryEntity - `domain/entities/inquiry.entity.ts` - Định nghĩa - `application/commands/create-inquiry/create-inquiry.handler.ts` - Tạo mới - `application/commands/mark-inquiry-read/mark-inquiry-read.handler.ts` - Chỉnh sửa - `infrastructure/repositories/prisma-inquiry.repository.ts` - Ánh xạ đến/từ ### Các File Phát Sự Kiện - `domain/entities/inquiry.entity.ts` - Phát ra (qua addDomainEvent) - `application/commands/create-inquiry/create-inquiry.handler.ts` - Xuất bản - `application/commands/mark-inquiry-read/mark-inquiry-read.handler.ts` - Xuất bản ### Các File Có Tests - `domain/entities/inquiry.entity.ts` → `domain/__tests__/inquiry-domain.spec.ts` - `application/commands/create-inquiry/create-inquiry.handler.ts` → `application/__tests__/create-inquiry.handler.spec.ts` - `application/commands/mark-inquiry-read/mark-inquiry-read.handler.ts` → `application/__tests__/mark-inquiry-read.handler.spec.ts` - `application/queries/get-inquiries-by-listing/` → `application/__tests__/get-inquiries-by-listing.handler.spec.ts` - `application/queries/get-inquiries-by-agent/` → `application/__tests__/get-inquiries-by-agent.handler.spec.ts` - `presentation/controllers/inquiries.controller.ts` → `presentation/__tests__/inquiries.controller.spec.ts` --- ## 🎯 ĐIỂM VÀO CHO VIỆC CHỈNH SỬA ### Thêm Endpoint Mới 1. Thêm phương thức vào `presentation/controllers/inquiries.controller.ts` 2. Tạo command/query trong `application/[type]/[name]/` 3. Tạo handler: `[name].handler.ts` 4. Thêm tests trong `application/__tests__/` 5. Tuỳ chọn: Cập nhật domain nếu cần logic nghiệp vụ mới ### Thêm Command Mới 1. Tạo `application/commands/[name]/[name].command.ts` (DTO) 2. Tạo `application/commands/[name]/[name].handler.ts` (@CommandHandler) 3. Đăng ký trong mảng CommandHandlers của `inquiries.module.ts` 4. Thêm tests trong `application/__tests__/[name].handler.spec.ts` 5. Tuỳ chọn: Định nghĩa sự kiện domain mới trong `domain/events/` ### Thêm Logic Domain 1. Chỉnh sửa `domain/entities/inquiry.entity.ts` 2. Thêm phương thức hoặc thuộc tính công khai mới 3. Phát sự kiện mới nếu cần: `domain/events/[name].event.ts` 4. Cập nhật tests trong `domain/__tests__/inquiry-domain.spec.ts` 5. Cập nhật các handler sử dụng entity ### Thêm Thao Tác Cơ Sở Dữ Liệu 1. Thêm phương thức vào `infrastructure/repositories/prisma-inquiry.repository.ts` 2. Cập nhật interface trong `domain/repositories/inquiry.repository.ts` 3. Gọi từ các handler liên quan --- **Tổng số dòng code:** 1.212 **Cập nhật lần cuối:** Ngày 11 tháng 4 năm 2026