feat(mining): 批量补发30%分配到运营和总部账户,并添加交易筛选器
- 批量补发时将剩余30%分配到运营(12%)和总部(18%)系统账户 - SystemMiningAccountRepository.mine()支持referenceId/referenceType参数 - BatchMiningExecution新增operationAmount/headquartersAmount字段(含DB迁移) - 三层架构(mining-service→admin-service→admin-web)全链路支持referenceType筛选 - 系统账户交易记录页面增加"全部/批量补发"筛选按钮 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
338321b3a2
commit
bf772967f5
|
|
@ -44,11 +44,13 @@ export class SystemAccountsController {
|
|||
@ApiOperation({ summary: '获取系统账户交易记录' })
|
||||
@ApiParam({ name: 'accountType', type: String, description: '系统账户类型(OPERATION/PROVINCE/CITY/HEADQUARTERS)' })
|
||||
@ApiQuery({ name: 'regionCode', required: false, type: String, description: '区域代码(省/市代码)' })
|
||||
@ApiQuery({ name: 'referenceType', required: false, type: String, description: '关联类型筛选(如 BATCH_MINING)' })
|
||||
@ApiQuery({ name: 'page', required: false, type: Number })
|
||||
@ApiQuery({ name: 'pageSize', required: false, type: Number })
|
||||
async getSystemAccountTransactions(
|
||||
@Param('accountType') accountType: string,
|
||||
@Query('regionCode') regionCode?: string,
|
||||
@Query('referenceType') referenceType?: string,
|
||||
@Query('page') page?: number,
|
||||
@Query('pageSize') pageSize?: number,
|
||||
) {
|
||||
|
|
@ -57,6 +59,7 @@ export class SystemAccountsController {
|
|||
regionCode || null,
|
||||
page ?? 1,
|
||||
pageSize ?? 20,
|
||||
referenceType || null,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -342,6 +342,7 @@ export class SystemAccountsService {
|
|||
regionCode: string | null,
|
||||
page: number = 1,
|
||||
pageSize: number = 20,
|
||||
referenceType: string | null = null,
|
||||
) {
|
||||
const miningServiceUrl = this.configService.get<string>(
|
||||
'MINING_SERVICE_URL',
|
||||
|
|
@ -353,6 +354,9 @@ export class SystemAccountsService {
|
|||
if (regionCode) {
|
||||
params.regionCode = regionCode;
|
||||
}
|
||||
if (referenceType) {
|
||||
params.referenceType = referenceType;
|
||||
}
|
||||
|
||||
const response = await firstValueFrom(
|
||||
this.httpService.get(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
-- AlterTable: 批量补发执行记录增加系统账户分配金额
|
||||
ALTER TABLE "batch_mining_executions" ADD COLUMN "operation_amount" DECIMAL(30,8) NOT NULL DEFAULT 0;
|
||||
ALTER TABLE "batch_mining_executions" ADD COLUMN "headquarters_amount" DECIMAL(30,8) NOT NULL DEFAULT 0;
|
||||
|
|
@ -605,7 +605,9 @@ model BatchMiningExecution {
|
|||
totalBatches Int @map("total_batches")
|
||||
successCount Int @default(0) @map("success_count")
|
||||
failedCount Int @default(0) @map("failed_count")
|
||||
totalAmount Decimal @default(0) @db.Decimal(30, 8) @map("total_amount")
|
||||
totalAmount Decimal @default(0) @db.Decimal(30, 8) @map("total_amount")
|
||||
operationAmount Decimal @default(0) @db.Decimal(30, 8) @map("operation_amount")
|
||||
headquartersAmount Decimal @default(0) @db.Decimal(30, 8) @map("headquarters_amount")
|
||||
|
||||
executedAt DateTime @map("executed_at")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
|
|
|||
|
|
@ -258,11 +258,13 @@ export class AdminController {
|
|||
@ApiOperation({ summary: '获取系统账户交易记录' })
|
||||
@ApiParam({ name: 'accountType', type: String, description: '系统账户类型(OPERATION/PROVINCE/CITY/HEADQUARTERS)' })
|
||||
@ApiQuery({ name: 'regionCode', required: false, type: String, description: '区域代码(省/市代码)' })
|
||||
@ApiQuery({ name: 'referenceType', required: false, type: String, description: '关联类型筛选(如 BATCH_MINING)' })
|
||||
@ApiQuery({ name: 'page', required: false, type: Number })
|
||||
@ApiQuery({ name: 'pageSize', required: false, type: Number })
|
||||
async getSystemAccountTransactions(
|
||||
@Param('accountType') accountType: string,
|
||||
@Query('regionCode') regionCode?: string,
|
||||
@Query('referenceType') referenceType?: string,
|
||||
@Query('page') page?: number,
|
||||
@Query('pageSize') pageSize?: number,
|
||||
) {
|
||||
|
|
@ -291,16 +293,19 @@ export class AdminController {
|
|||
};
|
||||
}
|
||||
|
||||
const where: any = { systemAccountId: account.id };
|
||||
if (referenceType) {
|
||||
where.referenceType = referenceType;
|
||||
}
|
||||
|
||||
const [transactions, total] = await Promise.all([
|
||||
this.prisma.systemMiningTransaction.findMany({
|
||||
where: { systemAccountId: account.id },
|
||||
where,
|
||||
orderBy: { createdAt: 'desc' },
|
||||
skip,
|
||||
take: pageSizeNum,
|
||||
}),
|
||||
this.prisma.systemMiningTransaction.count({
|
||||
where: { systemAccountId: account.id },
|
||||
}),
|
||||
this.prisma.systemMiningTransaction.count({ where }),
|
||||
]);
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { Injectable, Logger, ConflictException, BadRequestException } from '@nestjs/common';
|
||||
import { PrismaService } from '../../infrastructure/persistence/prisma/prisma.service';
|
||||
import { MiningConfigRepository } from '../../infrastructure/persistence/repositories/mining-config.repository';
|
||||
import { SystemMiningAccountRepository } from '../../infrastructure/persistence/repositories/system-mining-account.repository';
|
||||
import { ShareAmount } from '../../domain/value-objects/share-amount.vo';
|
||||
import Decimal from 'decimal.js';
|
||||
|
||||
|
|
@ -54,6 +55,8 @@ export interface BatchMiningResult {
|
|||
successCount: number;
|
||||
failedCount: number;
|
||||
totalAmount: string;
|
||||
operationAmount: string; // 运营账户12%金额
|
||||
headquartersAmount: string; // 总部账户18%金额
|
||||
results: BatchMiningItemResult[];
|
||||
message: string;
|
||||
}
|
||||
|
|
@ -83,6 +86,8 @@ export interface BatchMiningPreviewResult {
|
|||
batchTotalAmount: string;
|
||||
}[];
|
||||
grandTotalAmount: string;
|
||||
operationAmount: string; // 运营账户12%金额
|
||||
headquartersAmount: string; // 总部账户18%金额
|
||||
message: string;
|
||||
calculatedStartDate: string; // 计算使用的起始日期 (YYYY-MM-DD)
|
||||
}
|
||||
|
|
@ -93,6 +98,10 @@ const BASE_CONTRIBUTION_PER_TREE = new Decimal('22617'); // 每棵树的基础
|
|||
const SECONDS_PER_DAY = 86400;
|
||||
// 每天产出的70%分给补发用户
|
||||
const DAILY_DISTRIBUTION_RATIO = new Decimal('0.70');
|
||||
// 每天产出的12%分给运营账户
|
||||
const OPERATION_DISTRIBUTION_RATIO = new Decimal('0.12');
|
||||
// 每天产出的18%分给总部账户
|
||||
const HEADQUARTERS_DISTRIBUTION_RATIO = new Decimal('0.18');
|
||||
|
||||
/**
|
||||
* 挖矿阶段信息
|
||||
|
|
@ -111,10 +120,11 @@ interface MiningPhase {
|
|||
*
|
||||
* 核心逻辑(分阶段计算):
|
||||
* 1. 根据各批次的挖矿开始时间,划分挖矿阶段
|
||||
* 2. 每天产出的70%固定分给当前参与的用户
|
||||
* 2. 每天产出的70%固定分给当前参与的用户,12%分给运营账户,18%分给总部账户
|
||||
* 3. 用户算力 = 认种棵数 × 基础算力/棵
|
||||
* 4. 用户在每个阶段的收益 = 每日产出 × 70% × 阶段天数 × (用户算力 / 当前参与总算力)
|
||||
* 5. 用户总收益 = 用户参与的各阶段收益之和
|
||||
* 6. 运营/总部收益 = 每日产出 × 对应比例 × 总挖矿天数
|
||||
*/
|
||||
@Injectable()
|
||||
export class BatchMiningService {
|
||||
|
|
@ -123,6 +133,7 @@ export class BatchMiningService {
|
|||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly miningConfigRepository: MiningConfigRepository,
|
||||
private readonly systemMiningAccountRepository: SystemMiningAccountRepository,
|
||||
) {}
|
||||
|
||||
/**
|
||||
|
|
@ -159,6 +170,8 @@ export class BatchMiningService {
|
|||
totalUsers: 0,
|
||||
batches: [],
|
||||
grandTotalAmount: '0',
|
||||
operationAmount: '0',
|
||||
headquartersAmount: '0',
|
||||
message: `批量补发已于 ${existing?.executedAt?.toISOString()} 执行过,操作人: ${existing?.operatorName}`,
|
||||
calculatedStartDate: DEFAULT_MINING_START_DATE,
|
||||
};
|
||||
|
|
@ -235,10 +248,15 @@ export class BatchMiningService {
|
|||
participatingContribution: p.participatingContribution.toFixed(2)
|
||||
})))}`);
|
||||
|
||||
// 每天补发额度 = 日产出 × 70%
|
||||
// 每天补发额度 = 日产出 × 70%(用户)/ 12%(运营)/ 18%(总部)
|
||||
const dailyDistribution = secondDistribution.times(SECONDS_PER_DAY);
|
||||
const dailyAllocation = dailyDistribution.times(DAILY_DISTRIBUTION_RATIO);
|
||||
this.logger.log(`[preview] 每日产出: ${dailyDistribution.toFixed(8)}, 每日补发额度(70%): ${dailyAllocation.toFixed(8)}`);
|
||||
const dailyOperationAllocation = dailyDistribution.times(OPERATION_DISTRIBUTION_RATIO);
|
||||
const dailyHeadquartersAllocation = dailyDistribution.times(HEADQUARTERS_DISTRIBUTION_RATIO);
|
||||
this.logger.log(`[preview] 每日产出: ${dailyDistribution.toFixed(8)}, 用户70%: ${dailyAllocation.toFixed(8)}, 运营12%: ${dailyOperationAllocation.toFixed(8)}, 总部18%: ${dailyHeadquartersAllocation.toFixed(8)}`);
|
||||
|
||||
// 计算总挖矿天数(用于系统账户分配计算)
|
||||
const totalMiningDays = phases.reduce((sum, p) => sum + p.daysInPhase, 0);
|
||||
|
||||
// 计算每个用户在各阶段的收益
|
||||
// 使用 Set 记录已处理的用户,避免重复计算
|
||||
|
|
@ -349,14 +367,20 @@ export class BatchMiningService {
|
|||
});
|
||||
}
|
||||
|
||||
const result = {
|
||||
// 计算运营和总部账户的补发金额
|
||||
const operationAmount = dailyOperationAllocation.times(totalMiningDays);
|
||||
const headquartersAmount = dailyHeadquartersAllocation.times(totalMiningDays);
|
||||
|
||||
const result: BatchMiningPreviewResult = {
|
||||
canExecute: true,
|
||||
alreadyExecuted: false,
|
||||
totalBatches: sortedBatches.length,
|
||||
totalUsers: items.length,
|
||||
batches: batchResults,
|
||||
grandTotalAmount: grandTotalAmount.toFixed(8),
|
||||
message: `预览成功: ${sortedBatches.length} 个批次, ${items.length} 个用户, 总补发金额 ${grandTotalAmount.toFixed(8)}`,
|
||||
operationAmount: operationAmount.toFixed(8),
|
||||
headquartersAmount: headquartersAmount.toFixed(8),
|
||||
message: `预览成功: ${sortedBatches.length} 个批次, ${items.length} 个用户, 用户补发 ${grandTotalAmount.toFixed(8)}, 运营 ${operationAmount.toFixed(8)}, 总部 ${headquartersAmount.toFixed(8)}`,
|
||||
calculatedStartDate,
|
||||
};
|
||||
this.logger.log(`[preview] 预览完成: ${result.message}, 起始日期: ${calculatedStartDate}`);
|
||||
|
|
@ -485,6 +509,10 @@ export class BatchMiningService {
|
|||
const secondDistribution = config.secondDistribution.value;
|
||||
const now = new Date();
|
||||
|
||||
// 预检查: 确保系统账户存在
|
||||
await this.systemMiningAccountRepository.ensureSystemAccountsExist();
|
||||
this.logger.log('[execute] 系统账户预检查通过');
|
||||
|
||||
// 按批次分组并排序
|
||||
const batchGroups = this.groupByBatch(items);
|
||||
const sortedBatches = Array.from(batchGroups.keys()).sort((a, b) => a - b);
|
||||
|
|
@ -532,9 +560,17 @@ export class BatchMiningService {
|
|||
// 构建挖矿阶段
|
||||
const phases = this.buildMiningPhases(items, sortedBatches, batchContributions, calculatedStartDate);
|
||||
|
||||
// 每天补发额度 = 日产出 × 70%
|
||||
// 每天补发额度 = 日产出 × 70%(用户)/ 12%(运营)/ 18%(总部)
|
||||
const dailyDistribution = secondDistribution.times(SECONDS_PER_DAY);
|
||||
const dailyAllocation = dailyDistribution.times(DAILY_DISTRIBUTION_RATIO);
|
||||
const dailyOperationAllocation = dailyDistribution.times(OPERATION_DISTRIBUTION_RATIO);
|
||||
const dailyHeadquartersAllocation = dailyDistribution.times(HEADQUARTERS_DISTRIBUTION_RATIO);
|
||||
|
||||
// 计算总挖矿天数(用于系统账户分配)
|
||||
const totalMiningDays = phases.reduce((sum, p) => sum + p.daysInPhase, 0);
|
||||
const operationTotalAmount = dailyOperationAllocation.times(totalMiningDays);
|
||||
const headquartersTotalAmount = dailyHeadquartersAllocation.times(totalMiningDays);
|
||||
this.logger.log(`[execute] 系统账户分配: 运营=${operationTotalAmount.toFixed(8)}, 总部=${headquartersTotalAmount.toFixed(8)}, 总挖矿天数=${totalMiningDays}`);
|
||||
|
||||
// 计算每个用户在各阶段的收益
|
||||
const userAmounts = new Map<string, Decimal>();
|
||||
|
|
@ -793,6 +829,37 @@ export class BatchMiningService {
|
|||
}
|
||||
}
|
||||
|
||||
// 系统账户补发 (30%部分)
|
||||
// 运营账户 12%
|
||||
if (!operationTotalAmount.isZero()) {
|
||||
const operationMemo = `批量补发挖矿 - 运营账户12%份额 - ${totalMiningDays}天 × ${dailyOperationAllocation.toFixed(8)}/天 - 操作人:${operatorName} - ${reason}`;
|
||||
await this.systemMiningAccountRepository.mine(
|
||||
'OPERATION',
|
||||
null,
|
||||
new ShareAmount(operationTotalAmount),
|
||||
operationMemo,
|
||||
tx,
|
||||
execution.id,
|
||||
'BATCH_MINING',
|
||||
);
|
||||
this.logger.log(`[execute] 运营账户补发完成: ${operationTotalAmount.toFixed(8)}`);
|
||||
}
|
||||
|
||||
// 总部账户 18%
|
||||
if (!headquartersTotalAmount.isZero()) {
|
||||
const headquartersMemo = `批量补发挖矿 - 总部账户18%份额 - ${totalMiningDays}天 × ${dailyHeadquartersAllocation.toFixed(8)}/天 - 操作人:${operatorName} - ${reason}`;
|
||||
await this.systemMiningAccountRepository.mine(
|
||||
'HEADQUARTERS',
|
||||
null,
|
||||
new ShareAmount(headquartersTotalAmount),
|
||||
headquartersMemo,
|
||||
tx,
|
||||
execution.id,
|
||||
'BATCH_MINING',
|
||||
);
|
||||
this.logger.log(`[execute] 总部账户补发完成: ${headquartersTotalAmount.toFixed(8)}`);
|
||||
}
|
||||
|
||||
// 更新执行记录
|
||||
await tx.batchMiningExecution.update({
|
||||
where: { id: execution.id },
|
||||
|
|
@ -800,6 +867,8 @@ export class BatchMiningService {
|
|||
successCount,
|
||||
failedCount,
|
||||
totalAmount,
|
||||
operationAmount: operationTotalAmount,
|
||||
headquartersAmount: headquartersTotalAmount,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -809,7 +878,7 @@ export class BatchMiningService {
|
|||
});
|
||||
|
||||
this.logger.log(
|
||||
`Batch mining executed: batchId=${batchId}, total=${items.length}, success=${successCount}, failed=${failedCount}, amount=${totalAmount.toFixed(8)}`,
|
||||
`Batch mining executed: batchId=${batchId}, total=${items.length}, success=${successCount}, failed=${failedCount}, 用户=${totalAmount.toFixed(8)}, 运营=${operationTotalAmount.toFixed(8)}, 总部=${headquartersTotalAmount.toFixed(8)}`,
|
||||
);
|
||||
|
||||
return {
|
||||
|
|
@ -819,8 +888,10 @@ export class BatchMiningService {
|
|||
successCount,
|
||||
failedCount,
|
||||
totalAmount: totalAmount.toFixed(8),
|
||||
operationAmount: operationTotalAmount.toFixed(8),
|
||||
headquartersAmount: headquartersTotalAmount.toFixed(8),
|
||||
results,
|
||||
message: `批量补发完成: 成功 ${successCount} 个, 失败 ${failedCount} 个, 总金额 ${totalAmount.toFixed(8)}`,
|
||||
message: `批量补发完成: 成功 ${successCount} 个, 失败 ${failedCount} 个, 用户 ${totalAmount.toFixed(8)}, 运营 ${operationTotalAmount.toFixed(8)}, 总部 ${headquartersTotalAmount.toFixed(8)}`,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -147,6 +147,8 @@ export class SystemMiningAccountRepository {
|
|||
amount: ShareAmount,
|
||||
memo: string,
|
||||
tx?: TransactionClient,
|
||||
referenceId?: string,
|
||||
referenceType?: string,
|
||||
): Promise<void> {
|
||||
const executeInTx = async (client: TransactionClient) => {
|
||||
// 使用 findFirst 替代 findUnique,因为 regionCode 可以为 null
|
||||
|
|
@ -180,6 +182,8 @@ export class SystemMiningAccountRepository {
|
|||
amount: amount.value,
|
||||
balanceBefore,
|
||||
balanceAfter,
|
||||
referenceId: referenceId ?? null,
|
||||
referenceType: referenceType ?? null,
|
||||
memo,
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ export default function SystemAccountDetailPage() {
|
|||
|
||||
const [miningPage, setMiningPage] = useState(1);
|
||||
const [transactionPage, setTransactionPage] = useState(1);
|
||||
const [transactionFilter, setTransactionFilter] = useState<string | null>(null);
|
||||
const [contributionPage, setContributionPage] = useState(1);
|
||||
const pageSize = 20;
|
||||
|
||||
|
|
@ -77,7 +78,7 @@ export default function SystemAccountDetailPage() {
|
|||
data: transactions,
|
||||
isLoading: transactionsLoading,
|
||||
error: transactionsError,
|
||||
} = useSystemAccountTransactions(accountType, transactionPage, pageSize);
|
||||
} = useSystemAccountTransactions(accountType, transactionPage, pageSize, transactionFilter);
|
||||
|
||||
// 获取当前账户的 regionCode
|
||||
const regionCode = currentAccount?.regionCode ?? null;
|
||||
|
|
@ -353,14 +354,32 @@ export default function SystemAccountDetailPage() {
|
|||
<TabsContent value="transactions">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-lg flex items-center gap-2">
|
||||
交易记录
|
||||
{transactions && (
|
||||
<Badge variant="secondary" className="text-xs">
|
||||
共 {transactions.total} 条
|
||||
</Badge>
|
||||
)}
|
||||
</CardTitle>
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="text-lg flex items-center gap-2">
|
||||
交易记录
|
||||
{transactions && (
|
||||
<Badge variant="secondary" className="text-xs">
|
||||
共 {transactions.total} 条
|
||||
</Badge>
|
||||
)}
|
||||
</CardTitle>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
variant={transactionFilter === null ? 'default' : 'outline'}
|
||||
size="sm"
|
||||
onClick={() => { setTransactionFilter(null); setTransactionPage(1); }}
|
||||
>
|
||||
全部
|
||||
</Button>
|
||||
<Button
|
||||
variant={transactionFilter === 'BATCH_MINING' ? 'default' : 'outline'}
|
||||
size="sm"
|
||||
onClick={() => { setTransactionFilter('BATCH_MINING'); setTransactionPage(1); }}
|
||||
>
|
||||
批量补发
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="p-0">
|
||||
{transactionsError ? (
|
||||
|
|
@ -419,9 +438,16 @@ export default function SystemAccountDetailPage() {
|
|||
)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Badge className={`${typeInfo.color} text-xs`}>
|
||||
{typeInfo.label}
|
||||
</Badge>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Badge className={`${typeInfo.color} text-xs`}>
|
||||
{typeInfo.label}
|
||||
</Badge>
|
||||
{tx.referenceType === 'BATCH_MINING' && (
|
||||
<Badge variant="outline" className="text-xs bg-violet-50 text-violet-700 border-violet-200">
|
||||
批量补发
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
className={`text-right font-mono ${
|
||||
|
|
|
|||
|
|
@ -139,11 +139,16 @@ export const systemAccountsApi = {
|
|||
getTransactions: async (
|
||||
accountType: string,
|
||||
page: number = 1,
|
||||
pageSize: number = 20
|
||||
pageSize: number = 20,
|
||||
referenceType?: string | null,
|
||||
): Promise<SystemTransactionsResponse> => {
|
||||
const params: Record<string, any> = { page, pageSize };
|
||||
if (referenceType) {
|
||||
params.referenceType = referenceType;
|
||||
}
|
||||
const response = await apiClient.get(
|
||||
`/system-accounts/${accountType}/transactions`,
|
||||
{ params: { page, pageSize } }
|
||||
{ params }
|
||||
);
|
||||
return response.data.data;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -68,11 +68,12 @@ export function useSystemAccountMiningRecords(
|
|||
export function useSystemAccountTransactions(
|
||||
accountType: string,
|
||||
page: number = 1,
|
||||
pageSize: number = 20
|
||||
pageSize: number = 20,
|
||||
referenceType?: string | null,
|
||||
) {
|
||||
return useQuery({
|
||||
queryKey: ['system-accounts', accountType, 'transactions', page, pageSize],
|
||||
queryFn: () => systemAccountsApi.getTransactions(accountType, page, pageSize),
|
||||
queryKey: ['system-accounts', accountType, 'transactions', page, pageSize, referenceType],
|
||||
queryFn: () => systemAccountsApi.getTransactions(accountType, page, pageSize, referenceType),
|
||||
enabled: !!accountType,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue