import { PlantingOrderStatus } from '../value-objects/planting-order-status.enum'; import { ProvinceCitySelection } from '../value-objects/province-city-selection.vo'; import { FundAllocation } from '../value-objects/fund-allocation.vo'; import { TreeCount } from '../value-objects/tree-count.vo'; import { PRICE_PER_TREE } from '../value-objects/fund-allocation-target-type.enum'; import { DomainEvent } from '../events/domain-event.interface'; import { PlantingOrderCreatedEvent } from '../events/planting-order-created.event'; import { ProvinceCityConfirmedEvent } from '../events/province-city-confirmed.event'; import { PlantingOrderPaidEvent } from '../events/planting-order-paid.event'; import { FundsAllocatedEvent } from '../events/funds-allocated.event'; import { PoolInjectedEvent } from '../events/pool-injected.event'; import { MiningEnabledEvent } from '../events/mining-enabled.event'; export interface PlantingOrderData { id?: bigint; orderNo: string; userId: bigint; treeCount: number; totalAmount: number; status: PlantingOrderStatus; selectedProvince?: string | null; selectedCity?: string | null; provinceCitySelectedAt?: Date | null; provinceCityConfirmedAt?: Date | null; poolInjectionBatchId?: bigint | null; poolInjectionScheduledTime?: Date | null; poolInjectionActualTime?: Date | null; poolInjectionTxHash?: string | null; miningEnabledAt?: Date | null; createdAt?: Date; paidAt?: Date | null; fundAllocatedAt?: Date | null; } export class PlantingOrder { private _id: bigint | null; private readonly _orderNo: string; private readonly _userId: bigint; private readonly _treeCount: TreeCount; private readonly _totalAmount: number; private _provinceCitySelection: ProvinceCitySelection | null; private _status: PlantingOrderStatus; private _fundAllocations: FundAllocation[]; private _poolInjectionBatchId: bigint | null; private _poolInjectionScheduledTime: Date | null; private _poolInjectionActualTime: Date | null; private _poolInjectionTxHash: string | null; private _miningEnabledAt: Date | null; private readonly _createdAt: Date; private _paidAt: Date | null; private _fundAllocatedAt: Date | null; // 领域事件 private _domainEvents: DomainEvent[] = []; private constructor( orderNo: string, userId: bigint, treeCount: TreeCount, totalAmount: number, createdAt?: Date, ) { this._id = null; this._orderNo = orderNo; this._userId = userId; this._treeCount = treeCount; this._totalAmount = totalAmount; this._status = PlantingOrderStatus.CREATED; this._provinceCitySelection = null; this._fundAllocations = []; this._poolInjectionBatchId = null; this._poolInjectionScheduledTime = null; this._poolInjectionActualTime = null; this._poolInjectionTxHash = null; this._miningEnabledAt = null; this._createdAt = createdAt || new Date(); this._paidAt = null; this._fundAllocatedAt = null; } // Getters get id(): bigint | null { return this._id; } get orderNo(): string { return this._orderNo; } get userId(): bigint { return this._userId; } get treeCount(): TreeCount { return this._treeCount; } get totalAmount(): number { return this._totalAmount; } get status(): PlantingOrderStatus { return this._status; } get provinceCitySelection(): ProvinceCitySelection | null { return this._provinceCitySelection; } get fundAllocations(): ReadonlyArray { return this._fundAllocations; } get poolInjectionBatchId(): bigint | null { return this._poolInjectionBatchId; } get poolInjectionScheduledTime(): Date | null { return this._poolInjectionScheduledTime; } get poolInjectionActualTime(): Date | null { return this._poolInjectionActualTime; } get poolInjectionTxHash(): string | null { return this._poolInjectionTxHash; } get miningEnabledAt(): Date | null { return this._miningEnabledAt; } get isMiningEnabled(): boolean { return this._miningEnabledAt !== null; } get createdAt(): Date { return this._createdAt; } get paidAt(): Date | null { return this._paidAt; } get fundAllocatedAt(): Date | null { return this._fundAllocatedAt; } get domainEvents(): ReadonlyArray { return this._domainEvents; } /** * 工厂方法:创建认种订单 */ static create(userId: bigint, treeCount: number): PlantingOrder { if (treeCount <= 0) { throw new Error('认种数量必须大于0'); } const orderNo = `PLT${Date.now()}${Math.random().toString(36).substring(2, 8).toUpperCase()}`; const tree = TreeCount.create(treeCount); const totalAmount = treeCount * PRICE_PER_TREE; const order = new PlantingOrder(orderNo, userId, tree, totalAmount); // 发布领域事件 order._domainEvents.push( new PlantingOrderCreatedEvent(orderNo, { orderNo: order.orderNo, userId: order.userId.toString(), treeCount: order.treeCount.value, totalAmount: order.totalAmount, }), ); return order; } /** * 选择省市 (5秒倒计时前) */ selectProvinceCity( provinceCode: string, provinceName: string, cityCode: string, cityName: string, ): void { this.ensureStatus(PlantingOrderStatus.CREATED); if (this._provinceCitySelection?.isConfirmed) { throw new Error('省市已确认,不可修改'); } this._provinceCitySelection = ProvinceCitySelection.create( provinceCode, provinceName, cityCode, cityName, ); } /** * 确认省市选择 (5秒后) */ confirmProvinceCity(): void { this.ensureStatus(PlantingOrderStatus.CREATED); if (!this._provinceCitySelection) { throw new Error('请先选择省市'); } if (!this._provinceCitySelection.canConfirm()) { throw new Error('请等待5秒确认时间'); } this._provinceCitySelection = this._provinceCitySelection.confirm(); this._status = PlantingOrderStatus.PROVINCE_CITY_CONFIRMED; this._domainEvents.push( new ProvinceCityConfirmedEvent(this.orderNo, { orderNo: this.orderNo, userId: this.userId.toString(), provinceCode: this._provinceCitySelection.provinceCode, provinceName: this._provinceCitySelection.provinceName, cityCode: this._provinceCitySelection.cityCode, cityName: this._provinceCitySelection.cityName, }), ); } /** * 标记为已支付 */ markAsPaid(): void { this.ensureStatus(PlantingOrderStatus.PROVINCE_CITY_CONFIRMED); this._status = PlantingOrderStatus.PAID; this._paidAt = new Date(); this._domainEvents.push( new PlantingOrderPaidEvent(this.orderNo, { orderNo: this.orderNo, userId: this.userId.toString(), treeCount: this.treeCount.value, totalAmount: this.totalAmount, provinceCode: this._provinceCitySelection!.provinceCode, cityCode: this._provinceCitySelection!.cityCode, }), ); } /** * 分配资金 */ allocateFunds(allocations: FundAllocation[]): void { this.ensureStatus(PlantingOrderStatus.PAID); // 验证分配总额 const totalAllocated = allocations.reduce((sum, a) => sum + a.amount, 0); if (Math.abs(totalAllocated - this.totalAmount) > 0.01) { throw new Error( `资金分配总额不匹配: 期望 ${this.totalAmount}, 实际 ${totalAllocated}`, ); } this._fundAllocations = allocations; this._status = PlantingOrderStatus.FUND_ALLOCATED; this._fundAllocatedAt = new Date(); this._domainEvents.push( new FundsAllocatedEvent(this.orderNo, { orderNo: this.orderNo, allocations: allocations.map((a) => a.toDTO()), }), ); } /** * 安排底池注入 * 注意:资金分配已移至 reward-service 异步处理,所以支付后即可安排底池注入 */ schedulePoolInjection(batchId: bigint, scheduledTime: Date): void { this.ensureStatus(PlantingOrderStatus.PAID); this._poolInjectionBatchId = batchId; this._poolInjectionScheduledTime = scheduledTime; this._status = PlantingOrderStatus.POOL_SCHEDULED; } /** * 确认底池注入完成 */ confirmPoolInjection(txHash: string): void { this.ensureStatus(PlantingOrderStatus.POOL_SCHEDULED); this._poolInjectionActualTime = new Date(); this._poolInjectionTxHash = txHash; this._status = PlantingOrderStatus.POOL_INJECTED; this._domainEvents.push( new PoolInjectedEvent(this.orderNo, { orderNo: this.orderNo, userId: this.userId.toString(), amount: this.treeCount.value * 800, // 800 USDT/棵 txHash, }), ); } /** * 开启挖矿 */ enableMining(): void { this.ensureStatus(PlantingOrderStatus.POOL_INJECTED); this._miningEnabledAt = new Date(); this._status = PlantingOrderStatus.MINING_ENABLED; this._domainEvents.push( new MiningEnabledEvent(this.orderNo, { orderNo: this.orderNo, userId: this.userId.toString(), treeCount: this.treeCount.value, }), ); } /** * 取消订单 */ cancel(): void { if ( this._status !== PlantingOrderStatus.CREATED && this._status !== PlantingOrderStatus.PROVINCE_CITY_CONFIRMED ) { throw new Error('只有未支付的订单才能取消'); } this._status = PlantingOrderStatus.CANCELLED; } /** * 清除领域事件 */ clearDomainEvents(): void { this._domainEvents = []; } private ensureStatus(...allowedStatuses: PlantingOrderStatus[]): void { if (!allowedStatuses.includes(this._status)) { throw new Error( `订单状态错误: 当前 ${this._status}, 期望 ${allowedStatuses.join(' 或 ')}`, ); } } /** * 设置ID(用于持久化后回填) */ setId(id: bigint): void { if (this._id !== null) { throw new Error('ID已设置,不可修改'); } this._id = id; } /** * 用于从数据库重建 */ static reconstitute(data: PlantingOrderData): PlantingOrder { const order = new PlantingOrder( data.orderNo, data.userId, TreeCount.create(data.treeCount), data.totalAmount, data.createdAt, ); if (data.id) { order._id = data.id; } order._status = data.status; order._paidAt = data.paidAt || null; order._fundAllocatedAt = data.fundAllocatedAt || null; order._poolInjectionBatchId = data.poolInjectionBatchId || null; order._poolInjectionScheduledTime = data.poolInjectionScheduledTime || null; order._poolInjectionActualTime = data.poolInjectionActualTime || null; order._poolInjectionTxHash = data.poolInjectionTxHash || null; order._miningEnabledAt = data.miningEnabledAt || null; if (data.selectedProvince && data.selectedCity) { order._provinceCitySelection = ProvinceCitySelection.reconstitute( data.selectedProvince, '', data.selectedCity, '', data.provinceCitySelectedAt || new Date(), data.provinceCityConfirmedAt || null, ); } return order; } /** * 转换为可持久化的数据对象 */ toPersistence(): PlantingOrderData { return { id: this._id || undefined, orderNo: this._orderNo, userId: this._userId, treeCount: this._treeCount.value, totalAmount: this._totalAmount, status: this._status, selectedProvince: this._provinceCitySelection?.provinceCode || null, selectedCity: this._provinceCitySelection?.cityCode || null, provinceCitySelectedAt: this._provinceCitySelection?.selectedAt || null, provinceCityConfirmedAt: this._provinceCitySelection?.confirmedAt || null, poolInjectionBatchId: this._poolInjectionBatchId, poolInjectionScheduledTime: this._poolInjectionScheduledTime, poolInjectionActualTime: this._poolInjectionActualTime, poolInjectionTxHash: this._poolInjectionTxHash, miningEnabledAt: this._miningEnabledAt, createdAt: this._createdAt, paidAt: this._paidAt, fundAllocatedAt: this._fundAllocatedAt, }; } }