| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292 |
- /**
- * 批量生成分润索引表记录脚本
- *
- * 功能:
- * 1. 如果 commissionDetails 有数据,直接使用
- * 2. 如果 commissionDetails 没有数据,根据字段生成2层分润:
- * - 第1层:agentId 和 incomeAmount
- * - 第2层:personalAgentId 和 personalIncomeAmount
- */
- import { DataSource } from 'typeorm'
- import { IncomeRecords } from '../src/entities/income-records.entity'
- import { AgentCommissionIndex } from '../src/entities/agent-commission-index.entity'
- import { CommissionDetail } from '../src/services/multi-level-commission.service'
- import { createApp } from '../src/app'
- interface MigrationStats {
- total: number
- processed: number
- success: number
- failed: number
- skipped: number
- withDetails: number
- withoutDetails: number
- }
- class CommissionIndexGenerator {
- private dataSource: DataSource
- private stats: MigrationStats = {
- total: 0,
- processed: 0,
- success: 0,
- failed: 0,
- skipped: 0,
- withDetails: 0,
- withoutDetails: 0
- }
- constructor(dataSource: DataSource) {
- this.dataSource = dataSource
- }
- /**
- * 从 commissionDetails JSON 字符串解析分润明细
- */
- private parseCommissionDetails(commissionDetails: string | null): CommissionDetail[] | null {
- if (!commissionDetails) {
- return null
- }
- try {
- const details = JSON.parse(commissionDetails)
- if (Array.isArray(details) && details.length > 0) {
- return details as CommissionDetail[]
- }
- return null
- } catch (error) {
- console.error('解析 commissionDetails 失败:', error)
- return null
- }
- }
- /**
- * 根据字段生成2层分润明细
- */
- private generateCommissionDetailsFromFields(
- record: IncomeRecords
- ): CommissionDetail[] {
- const details: CommissionDetail[] = []
- // 第1层:agentId 和 incomeAmount
- if (record.agentId && record.agentId > 0 && record.incomeAmount && Number(record.incomeAmount) > 0) {
- const orderPrice = Number(record.orderPrice) || 0
- const incomeAmount = Number(record.incomeAmount)
- // 计算分润比例
- const rate = orderPrice > 0 ? (incomeAmount / orderPrice) * 100 : 0
- details.push({
- level: 1,
- agentId: record.agentId,
- rate: Number(rate.toFixed(2)),
- amount: incomeAmount
- })
- }
- // 第2层:personalAgentId 和 personalIncomeAmount
- if (record.personalAgentId && record.personalAgentId > 0 && record.personalIncomeAmount && Number(record.personalIncomeAmount) > 0) {
- const orderPrice = Number(record.orderPrice) || 0
- const personalIncomeAmount = Number(record.personalIncomeAmount)
- // 计算分润比例
- const rate = orderPrice > 0 ? (personalIncomeAmount / orderPrice) * 100 : 0
- details.push({
- level: 2,
- agentId: record.personalAgentId,
- rate: Number(rate.toFixed(2)),
- amount: personalIncomeAmount
- })
- }
- return details
- }
- /**
- * 检查是否已存在索引记录
- */
- private async hasIndexRecords(incomeRecordId: number): Promise<boolean> {
- const indexRepository = this.dataSource.getRepository(AgentCommissionIndex)
- const count = await indexRepository.count({
- where: { incomeRecordId }
- })
- return count > 0
- }
- /**
- * 处理单条记录
- */
- private async processSingleRecord(record: IncomeRecords, skipExisting: boolean = true): Promise<void> {
- try {
- // 检查是否已存在索引记录
- if (skipExisting) {
- const exists = await this.hasIndexRecords(record.id)
- if (exists) {
- this.stats.skipped++
- return
- }
- }
- // 解析 commissionDetails
- let commissionDetails: CommissionDetail[] | null = null
-
- if (record.commissionDetails) {
- commissionDetails = this.parseCommissionDetails(record.commissionDetails)
- if (commissionDetails) {
- this.stats.withDetails++
- }
- }
- // 如果没有 commissionDetails,根据字段生成
- if (!commissionDetails || commissionDetails.length === 0) {
- commissionDetails = this.generateCommissionDetailsFromFields(record)
- this.stats.withoutDetails++
- }
- // 如果没有分润明细,跳过
- if (!commissionDetails || commissionDetails.length === 0) {
- this.stats.skipped++
- return
- }
- // 创建索引记录
- // 注意:createdAt 使用订单的创建时间(record.createdAt),而不是生成脚本时的时间
- const indexRepository = this.dataSource.getRepository(AgentCommissionIndex)
- const indexRecords = commissionDetails.map(detail =>
- indexRepository.create({
- incomeRecordId: record.id,
- agentId: detail.agentId,
- level: detail.level,
- commissionRate: detail.rate,
- commissionAmount: detail.amount,
- orderNo: record.orderNo,
- orderPrice: Number(record.orderPrice),
- userId: record.userId,
- status: record.status,
- createdAt: record.createdAt // 使用订单的创建时间
- })
- )
- await indexRepository.save(indexRecords)
- this.stats.success++
- if (this.stats.processed % 100 === 0) {
- console.log(`已处理: ${this.stats.processed} | 成功: ${this.stats.success} | 失败: ${this.stats.failed} | 跳过: ${this.stats.skipped} | 有明细: ${this.stats.withDetails} | 无明细: ${this.stats.withoutDetails}`)
- }
- } catch (error) {
- this.stats.failed++
- console.error(`处理记录失败 (ID: ${record.id}, OrderNo: ${record.orderNo}):`, error)
- }
- }
- /**
- * 批量处理订单记录
- */
- async generateIndex(batchSize: number = 100, startId?: number, skipExisting: boolean = true): Promise<void> {
- const incomeRecordsRepository = this.dataSource.getRepository(IncomeRecords)
- const indexRepository = this.dataSource.getRepository(AgentCommissionIndex)
- console.log('开始生成分润索引表记录...')
- console.log(`批次大小: ${batchSize}`)
- console.log(`起始ID: ${startId || '无'}`)
- console.log(`跳过已存在: ${skipExisting}`)
- // 先统计总数
- let countQuery = incomeRecordsRepository
- .createQueryBuilder('record')
- .where('record.delFlag = :delFlag', { delFlag: false })
- if (startId) {
- countQuery = countQuery.andWhere('record.id >= :startId', { startId })
- }
- const total = await countQuery.getCount()
- this.stats.total = total
- console.log(`总记录数: ${total}`)
- let offset = 0
- let hasMore = true
- while (hasMore) {
- const queryBuilder = incomeRecordsRepository
- .createQueryBuilder('record')
- .where('record.delFlag = :delFlag', { delFlag: false })
- .orderBy('record.id', 'ASC')
- .limit(batchSize)
- .offset(offset)
- if (startId) {
- queryBuilder.andWhere('record.id >= :startId', { startId })
- }
- const records = await queryBuilder.getMany()
- if (records.length === 0) {
- hasMore = false
- break
- }
- for (const record of records) {
- await this.processSingleRecord(record, skipExisting)
- this.stats.processed++
- }
- offset += batchSize
- // 如果这批数据少于批次大小,说明已经处理完所有数据
- if (records.length < batchSize) {
- hasMore = false
- }
- }
- console.log('\n生成完成!')
- console.log(`总计: ${this.stats.total}`)
- console.log(`已处理: ${this.stats.processed}`)
- console.log(`成功: ${this.stats.success}`)
- console.log(`失败: ${this.stats.failed}`)
- console.log(`跳过: ${this.stats.skipped}`)
- console.log(`有明细: ${this.stats.withDetails}`)
- console.log(`无明细: ${this.stats.withoutDetails}`)
- }
- }
- // 主函数
- async function main() {
- const app = await createApp()
- const dataSource = (app as any).dataSource
- if (!dataSource) {
- console.error('无法获取数据库连接')
- process.exit(1)
- }
- try {
- const generator = new CommissionIndexGenerator(dataSource)
- // 从命令行参数获取配置
- const args = process.argv.slice(2)
- const batchSize = parseInt(args[0]) || 100
- const startId = args[1] ? parseInt(args[1]) : undefined
- const skipExisting = args[2] !== 'false'
- console.log('配置参数:')
- console.log(` 批次大小: ${batchSize}`)
- console.log(` 起始ID: ${startId || '无'}`)
- console.log(` 跳过已存在: ${skipExisting}`)
- console.log('')
- await generator.generateIndex(batchSize, startId, skipExisting)
- } catch (error) {
- console.error('执行失败:', error)
- process.exit(1)
- } finally {
- await app.close()
- }
- }
- // 运行脚本
- if (require.main === module) {
- main().catch(console.error)
- }
- export { CommissionIndexGenerator }
|