rwadurian/backend/services/planting-service/src/pre-planting/infrastructure/external/pre-planting-authorization....

223 lines
6.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Injectable, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { HttpService } from '@nestjs/axios';
import { firstValueFrom } from 'rxjs';
import { SYSTEM_ACCOUNTS } from '../../domain/value-objects/pre-planting-right-amounts';
export interface RewardDistributionResult {
recipientAccountSequence: string;
isFallback: boolean;
}
/**
* authorization-service 省/市区域 API 返回的 distributions 数组项
*/
interface AreaDistributionItem {
accountSequence: string;
treeCount: number;
reason: string;
isSystemAccount: boolean;
}
/**
* authorization-service 全局 TransformInterceptor 包装格式
* 所有响应都被包装为 { success: boolean, data: T, timestamp: string }
*/
interface WrappedResponse<T> {
success: boolean;
data: T;
timestamp: string;
}
@Injectable()
export class PrePlantingAuthorizationClient {
private readonly logger = new Logger(PrePlantingAuthorizationClient.name);
private readonly baseUrl: string;
constructor(
private readonly configService: ConfigService,
private readonly httpService: HttpService,
) {
this.baseUrl =
this.configService.get<string>('AUTHORIZATION_SERVICE_URL') ||
'http://localhost:3006';
}
/**
* 社区权益分配对象
*/
async getCommunityDistribution(
accountSequence: string,
): Promise<RewardDistributionResult> {
try {
const response = await firstValueFrom(
this.httpService.get<WrappedResponse<{ accountSequence: string }>>(
`${this.baseUrl}/api/v1/authorization/community-reward-distribution`,
{ params: { accountSequence } },
),
);
return {
recipientAccountSequence: response.data.data.accountSequence,
isFallback: false,
};
} catch (error) {
this.logger.warn(
`Failed to get community distribution for ${accountSequence}, fallback to HQ`,
);
return {
recipientAccountSequence: SYSTEM_ACCOUNTS.HEADQUARTERS,
isFallback: true,
};
}
}
/**
* 省区域权益分配对象
*
* [2026-02-28] 修复:
* 1. 传 treeCount=0预种阶段不计入省公司月度考核等合成1棵树后再累计
* 2. 正确解析 { success, data: { distributions: [...] } } 包装格式
* 3. fallback 路径使用 padEnd(6,'0') 右补零,确保生成 7 位标准格式(如 9440000
*/
async getProvinceAreaDistribution(
provinceCode: string,
): Promise<RewardDistributionResult> {
try {
const response = await firstValueFrom(
this.httpService.get<WrappedResponse<{ distributions: AreaDistributionItem[] }>>(
`${this.baseUrl}/api/v1/authorization/province-area-reward-distribution`,
{ params: { provinceCode, treeCount: 0 } },
),
);
const distributions = response.data.data.distributions;
if (!distributions || distributions.length === 0) {
throw new Error('Empty distributions returned');
}
// 预种不拆分金额,取第一个分配对象的 accountSequence
// 如果跨考核达标点有多个 distribution优先取非系统账户即省公司
const target = distributions.find(d => !d.isSystemAccount) || distributions[0];
this.logger.debug(
`Province area distribution for ${provinceCode}: ${target.accountSequence} (${target.reason})`,
);
return {
recipientAccountSequence: target.accountSequence,
isFallback: false,
};
} catch (error) {
this.logger.warn(
`Failed to get province area distribution for ${provinceCode}, fallback to system province account`,
);
return {
recipientAccountSequence: `9${provinceCode.padEnd(6, '0')}`,
isFallback: true,
};
}
}
/**
* 省团队权益分配对象
*/
async getProvinceTeamDistribution(
accountSequence: string,
): Promise<RewardDistributionResult> {
try {
const response = await firstValueFrom(
this.httpService.get<WrappedResponse<{ accountSequence: string }>>(
`${this.baseUrl}/api/v1/authorization/province-team-reward-distribution`,
{ params: { accountSequence } },
),
);
return {
recipientAccountSequence: response.data.data.accountSequence,
isFallback: false,
};
} catch (error) {
this.logger.warn(
`Failed to get province team distribution, fallback to HQ`,
);
return {
recipientAccountSequence: SYSTEM_ACCOUNTS.HEADQUARTERS,
isFallback: true,
};
}
}
/**
* 市区域权益分配对象
*
* [2026-02-28] 修复:与省区域同样的处理
* 1. 传 treeCount=0预种阶段不计入市公司月度考核等合成1棵树后再累计
* 2. 正确解析 { success, data: { distributions: [...] } } 包装格式
* 3. fallback 路径使用 padEnd(6,'0') 右补零,确保生成 7 位标准格式(如 8440100
*/
async getCityAreaDistribution(
cityCode: string,
): Promise<RewardDistributionResult> {
try {
const response = await firstValueFrom(
this.httpService.get<WrappedResponse<{ distributions: AreaDistributionItem[] }>>(
`${this.baseUrl}/api/v1/authorization/city-area-reward-distribution`,
{ params: { cityCode, treeCount: 0 } },
),
);
const distributions = response.data.data.distributions;
if (!distributions || distributions.length === 0) {
throw new Error('Empty distributions returned');
}
// 预种不拆分金额,取第一个分配对象的 accountSequence
const target = distributions.find(d => !d.isSystemAccount) || distributions[0];
this.logger.debug(
`City area distribution for ${cityCode}: ${target.accountSequence} (${target.reason})`,
);
return {
recipientAccountSequence: target.accountSequence,
isFallback: false,
};
} catch (error) {
this.logger.warn(
`Failed to get city area distribution for ${cityCode}, fallback to system city account`,
);
return {
recipientAccountSequence: `8${cityCode.padEnd(6, '0')}`,
isFallback: true,
};
}
}
/**
* 市团队权益分配对象
*/
async getCityTeamDistribution(
accountSequence: string,
): Promise<RewardDistributionResult> {
try {
const response = await firstValueFrom(
this.httpService.get<WrappedResponse<{ accountSequence: string }>>(
`${this.baseUrl}/api/v1/authorization/city-team-reward-distribution`,
{ params: { accountSequence } },
),
);
return {
recipientAccountSequence: response.data.data.accountSequence,
isFallback: false,
};
} catch (error) {
this.logger.warn(
`Failed to get city team distribution, fallback to HQ`,
);
return {
recipientAccountSequence: SYSTEM_ACCOUNTS.HEADQUARTERS,
isFallback: true,
};
}
}
}