feat: implement project development module, transfer management features, and industrial AVM model integration
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
import type { ProjectDevelopmentStatus } from '@prisma/client';
|
||||
|
||||
export class CreateProjectCommand {
|
||||
constructor(
|
||||
public readonly name: string,
|
||||
public readonly slug: string,
|
||||
public readonly developer: string,
|
||||
public readonly developerLogo: string | null,
|
||||
public readonly totalUnits: number,
|
||||
public readonly status: ProjectDevelopmentStatus,
|
||||
public readonly latitude: number,
|
||||
public readonly longitude: number,
|
||||
public readonly address: string,
|
||||
public readonly ward: string,
|
||||
public readonly district: string,
|
||||
public readonly city: string,
|
||||
public readonly description: string | null,
|
||||
public readonly amenities: Record<string, unknown> | null,
|
||||
public readonly masterPlanUrl: string | null,
|
||||
public readonly minPrice: bigint | null,
|
||||
public readonly maxPrice: bigint | null,
|
||||
public readonly pricePerM2Range: Record<string, unknown> | null,
|
||||
public readonly totalArea: number | null,
|
||||
public readonly buildingCount: number | null,
|
||||
public readonly floorCount: number | null,
|
||||
public readonly unitTypes: Record<string, unknown> | null,
|
||||
public readonly tags: string[],
|
||||
public readonly startDate: Date | null,
|
||||
public readonly completionDate: Date | null,
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import { Inject } from '@nestjs/common';
|
||||
import { type ICommandHandler, CommandHandler } from '@nestjs/cqrs';
|
||||
import { createId } from '@paralleldrive/cuid2';
|
||||
import { ConflictException } from '@modules/shared';
|
||||
import { ProjectDevelopmentEntity } from '../../../domain/entities/project-development.entity';
|
||||
import {
|
||||
PROJECT_REPOSITORY,
|
||||
type IProjectRepository,
|
||||
} from '../../../domain/repositories/project-development.repository';
|
||||
import { CreateProjectCommand } from './create-project.command';
|
||||
|
||||
@CommandHandler(CreateProjectCommand)
|
||||
export class CreateProjectHandler implements ICommandHandler<CreateProjectCommand> {
|
||||
constructor(
|
||||
@Inject(PROJECT_REPOSITORY)
|
||||
private readonly repo: IProjectRepository,
|
||||
) {}
|
||||
|
||||
async execute(cmd: CreateProjectCommand): Promise<{ id: string; slug: string }> {
|
||||
const existing = await this.repo.findBySlug(cmd.slug);
|
||||
if (existing) {
|
||||
throw new ConflictException(`Dự án với slug "${cmd.slug}" đã tồn tại`);
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
const entity = new ProjectDevelopmentEntity(
|
||||
createId(),
|
||||
{
|
||||
name: cmd.name,
|
||||
slug: cmd.slug,
|
||||
developer: cmd.developer,
|
||||
developerLogo: cmd.developerLogo,
|
||||
totalUnits: cmd.totalUnits,
|
||||
completedUnits: 0,
|
||||
status: cmd.status,
|
||||
startDate: cmd.startDate,
|
||||
completionDate: cmd.completionDate,
|
||||
description: cmd.description,
|
||||
amenities: cmd.amenities,
|
||||
masterPlanUrl: cmd.masterPlanUrl,
|
||||
latitude: cmd.latitude,
|
||||
longitude: cmd.longitude,
|
||||
address: cmd.address,
|
||||
ward: cmd.ward,
|
||||
district: cmd.district,
|
||||
city: cmd.city,
|
||||
minPrice: cmd.minPrice,
|
||||
maxPrice: cmd.maxPrice,
|
||||
pricePerM2Range: cmd.pricePerM2Range,
|
||||
totalArea: cmd.totalArea,
|
||||
buildingCount: cmd.buildingCount,
|
||||
floorCount: cmd.floorCount,
|
||||
unitTypes: cmd.unitTypes,
|
||||
media: null,
|
||||
documents: null,
|
||||
tags: cmd.tags,
|
||||
isVerified: false,
|
||||
},
|
||||
now,
|
||||
now,
|
||||
);
|
||||
|
||||
await this.repo.save(entity);
|
||||
return { id: entity.id, slug: entity.slug };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import type { ProjectDevelopmentStatus } from '@prisma/client';
|
||||
|
||||
export class UpdateProjectCommand {
|
||||
constructor(
|
||||
public readonly id: string,
|
||||
public readonly name?: string,
|
||||
public readonly developer?: string,
|
||||
public readonly developerLogo?: string | null,
|
||||
public readonly totalUnits?: number,
|
||||
public readonly completedUnits?: number,
|
||||
public readonly status?: ProjectDevelopmentStatus,
|
||||
public readonly description?: string | null,
|
||||
public readonly amenities?: Record<string, unknown> | null,
|
||||
public readonly masterPlanUrl?: string | null,
|
||||
public readonly minPrice?: bigint | null,
|
||||
public readonly maxPrice?: bigint | null,
|
||||
public readonly pricePerM2Range?: Record<string, unknown> | null,
|
||||
public readonly totalArea?: number | null,
|
||||
public readonly buildingCount?: number | null,
|
||||
public readonly floorCount?: number | null,
|
||||
public readonly unitTypes?: Record<string, unknown> | null,
|
||||
public readonly media?: Record<string, unknown>[] | null,
|
||||
public readonly documents?: Record<string, unknown>[] | null,
|
||||
public readonly tags?: string[],
|
||||
public readonly isVerified?: boolean,
|
||||
public readonly startDate?: Date | null,
|
||||
public readonly completionDate?: Date | null,
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import { Inject } from '@nestjs/common';
|
||||
import { type ICommandHandler, CommandHandler } from '@nestjs/cqrs';
|
||||
import { NotFoundException } from '@modules/shared';
|
||||
import {
|
||||
PROJECT_REPOSITORY,
|
||||
type IProjectRepository,
|
||||
} from '../../../domain/repositories/project-development.repository';
|
||||
import { UpdateProjectCommand } from './update-project.command';
|
||||
|
||||
@CommandHandler(UpdateProjectCommand)
|
||||
export class UpdateProjectHandler implements ICommandHandler<UpdateProjectCommand> {
|
||||
constructor(
|
||||
@Inject(PROJECT_REPOSITORY)
|
||||
private readonly repo: IProjectRepository,
|
||||
) {}
|
||||
|
||||
async execute(cmd: UpdateProjectCommand): Promise<{ id: string }> {
|
||||
const entity = await this.repo.findById(cmd.id);
|
||||
if (!entity) {
|
||||
throw new NotFoundException('Dự án', cmd.id);
|
||||
}
|
||||
|
||||
entity.updateDetails({
|
||||
...(cmd.name !== undefined && { name: cmd.name }),
|
||||
...(cmd.developer !== undefined && { developer: cmd.developer }),
|
||||
...(cmd.developerLogo !== undefined && { developerLogo: cmd.developerLogo }),
|
||||
...(cmd.totalUnits !== undefined && { totalUnits: cmd.totalUnits }),
|
||||
...(cmd.completedUnits !== undefined && { completedUnits: cmd.completedUnits }),
|
||||
...(cmd.status !== undefined && { status: cmd.status }),
|
||||
...(cmd.description !== undefined && { description: cmd.description }),
|
||||
...(cmd.amenities !== undefined && { amenities: cmd.amenities }),
|
||||
...(cmd.masterPlanUrl !== undefined && { masterPlanUrl: cmd.masterPlanUrl }),
|
||||
...(cmd.minPrice !== undefined && { minPrice: cmd.minPrice }),
|
||||
...(cmd.maxPrice !== undefined && { maxPrice: cmd.maxPrice }),
|
||||
...(cmd.pricePerM2Range !== undefined && { pricePerM2Range: cmd.pricePerM2Range }),
|
||||
...(cmd.totalArea !== undefined && { totalArea: cmd.totalArea }),
|
||||
...(cmd.buildingCount !== undefined && { buildingCount: cmd.buildingCount }),
|
||||
...(cmd.floorCount !== undefined && { floorCount: cmd.floorCount }),
|
||||
...(cmd.unitTypes !== undefined && { unitTypes: cmd.unitTypes }),
|
||||
...(cmd.media !== undefined && { media: cmd.media }),
|
||||
...(cmd.documents !== undefined && { documents: cmd.documents }),
|
||||
...(cmd.tags !== undefined && { tags: cmd.tags }),
|
||||
...(cmd.isVerified !== undefined && { isVerified: cmd.isVerified }),
|
||||
...(cmd.startDate !== undefined && { startDate: cmd.startDate }),
|
||||
...(cmd.completionDate !== undefined && { completionDate: cmd.completionDate }),
|
||||
});
|
||||
|
||||
await this.repo.update(entity);
|
||||
return { id: entity.id };
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user