538 lines
15 KiB
TypeScript
538 lines
15 KiB
TypeScript
|
|
import db from '../config/database';
|
|||
|
|
import { logger } from '../utils/logger';
|
|||
|
|
import { UserAssetService } from './UserAssetService';
|
|||
|
|
import {
|
|||
|
|
MemberLevel,
|
|||
|
|
MemberLevelRule,
|
|||
|
|
MemberLevelRuleCreateInput,
|
|||
|
|
} from '../models/UserAsset';
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* MemberLevelService - 会员等级系统
|
|||
|
|
*
|
|||
|
|
* [BE-UA003] 会员等级系统
|
|||
|
|
* @description 管理会员等级规则、等级升降、权益配置
|
|||
|
|
*
|
|||
|
|
* 功能定位:
|
|||
|
|
* - 会员等级规则管理
|
|||
|
|
* - 等级自动升降
|
|||
|
|
* - 会员权益配置
|
|||
|
|
* - 等级统计报表
|
|||
|
|
*
|
|||
|
|
* 安全约束:
|
|||
|
|
* - 所有操作必须携带五元组追踪信息
|
|||
|
|
* - 等级变更必须记录日志
|
|||
|
|
*
|
|||
|
|
* @author AI-Backend-7
|
|||
|
|
* @taskId BE-UA003
|
|||
|
|
*/
|
|||
|
|
export class MemberLevelService {
|
|||
|
|
private static readonly RULE_TABLE = 'cf_member_level_rule';
|
|||
|
|
private static readonly LOG_TABLE = 'cf_member_level_log';
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 初始化数据库表
|
|||
|
|
* 遵循幂等性原则:使用 hasTable 前置校验
|
|||
|
|
*/
|
|||
|
|
static async initTable(): Promise<void> {
|
|||
|
|
const hasRuleTable = await db.schema.hasTable(this.RULE_TABLE);
|
|||
|
|
if (!hasRuleTable) {
|
|||
|
|
logger.info(`[MemberLevelService] Creating ${this.RULE_TABLE} table...`);
|
|||
|
|
await db.schema.createTable(this.RULE_TABLE, (table) => {
|
|||
|
|
table.string('id', 36).primary();
|
|||
|
|
table.string('tenant_id', 64).index();
|
|||
|
|
table.enum('level', ['BRONZE', 'SILVER', 'GOLD', 'PLATINUM', 'DIAMOND']).notNullable();
|
|||
|
|
table.integer('min_score').notNullable();
|
|||
|
|
table.integer('max_score').notNullable();
|
|||
|
|
table.decimal('discount', 5, 4).defaultTo(1.0).notNullable();
|
|||
|
|
table.decimal('points_multiplier', 5, 4).defaultTo(1.0).notNullable();
|
|||
|
|
table.json('benefits').notNullable();
|
|||
|
|
table.boolean('is_active').defaultTo(true).notNullable();
|
|||
|
|
table.timestamp('created_at').defaultTo(db.fn.now());
|
|||
|
|
table.timestamp('updated_at').defaultTo(db.fn.now());
|
|||
|
|
|
|||
|
|
table.index(['tenant_id', 'level'], 'idx_rule_tenant_level');
|
|||
|
|
});
|
|||
|
|
logger.info(`[MemberLevelService] Table ${this.RULE_TABLE} created`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const hasLogTable = await db.schema.hasTable(this.LOG_TABLE);
|
|||
|
|
if (!hasLogTable) {
|
|||
|
|
logger.info(`[MemberLevelService] Creating ${this.LOG_TABLE} table...`);
|
|||
|
|
await db.schema.createTable(this.LOG_TABLE, (table) => {
|
|||
|
|
table.string('id', 36).primary();
|
|||
|
|
table.string('tenant_id', 64).notNullable().index();
|
|||
|
|
table.string('shop_id', 64).notNullable().index();
|
|||
|
|
table.string('user_id', 64).notNullable().index();
|
|||
|
|
table.string('trace_id', 64).notNullable();
|
|||
|
|
table.enum('old_level', ['BRONZE', 'SILVER', 'GOLD', 'PLATINUM', 'DIAMOND']);
|
|||
|
|
table.enum('new_level', ['BRONZE', 'SILVER', 'GOLD', 'PLATINUM', 'DIAMOND']).notNullable();
|
|||
|
|
table.integer('old_score');
|
|||
|
|
table.integer('new_score').notNullable();
|
|||
|
|
table.string('change_type', 32).notNullable();
|
|||
|
|
table.text('reason');
|
|||
|
|
table.timestamp('created_at').defaultTo(db.fn.now());
|
|||
|
|
|
|||
|
|
table.index(['tenant_id', 'user_id'], 'idx_log_user');
|
|||
|
|
table.index(['created_at'], 'idx_log_time');
|
|||
|
|
});
|
|||
|
|
logger.info(`[MemberLevelService] Table ${this.LOG_TABLE} created`);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 创建或更新等级规则
|
|||
|
|
* @param input 规则创建输入
|
|||
|
|
* @returns 规则ID
|
|||
|
|
*/
|
|||
|
|
static async createOrUpdateRule(input: MemberLevelRuleCreateInput): Promise<string> {
|
|||
|
|
const existing = await db(this.RULE_TABLE)
|
|||
|
|
.where({
|
|||
|
|
tenant_id: input.tenantId || null,
|
|||
|
|
level: input.level,
|
|||
|
|
})
|
|||
|
|
.first();
|
|||
|
|
|
|||
|
|
if (existing) {
|
|||
|
|
await db(this.RULE_TABLE)
|
|||
|
|
.where({ id: existing.id })
|
|||
|
|
.update({
|
|||
|
|
min_score: input.minScore,
|
|||
|
|
max_score: input.maxScore,
|
|||
|
|
discount: input.discount,
|
|||
|
|
points_multiplier: input.pointsMultiplier,
|
|||
|
|
benefits: JSON.stringify(input.benefits),
|
|||
|
|
is_active: input.isActive !== undefined ? input.isActive : true,
|
|||
|
|
updated_at: new Date(),
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
logger.info(`[MemberLevelService] Rule updated: level=${input.level}, tenantId=${input.tenantId || 'global'}`);
|
|||
|
|
return existing.id;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const id = this.generateId();
|
|||
|
|
const now = new Date();
|
|||
|
|
|
|||
|
|
await db(this.RULE_TABLE).insert({
|
|||
|
|
id,
|
|||
|
|
tenant_id: input.tenantId || null,
|
|||
|
|
level: input.level,
|
|||
|
|
min_score: input.minScore,
|
|||
|
|
max_score: input.maxScore,
|
|||
|
|
discount: input.discount,
|
|||
|
|
points_multiplier: input.pointsMultiplier,
|
|||
|
|
benefits: JSON.stringify(input.benefits),
|
|||
|
|
is_active: input.isActive !== undefined ? input.isActive : true,
|
|||
|
|
created_at: now,
|
|||
|
|
updated_at: now,
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
logger.info(`[MemberLevelService] Rule created: id=${id}, level=${input.level}, tenantId=${input.tenantId || 'global'}`);
|
|||
|
|
|
|||
|
|
return id;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取等级规则
|
|||
|
|
* @param tenantId 租户ID(可选,为空则获取全局规则)
|
|||
|
|
* @param level 等级
|
|||
|
|
* @returns 等级规则
|
|||
|
|
*/
|
|||
|
|
static async getRule(tenantId: string | undefined, level: MemberLevel): Promise<MemberLevelRule | null> {
|
|||
|
|
let query = db(this.RULE_TABLE)
|
|||
|
|
.where('level', level)
|
|||
|
|
.where('is_active', true);
|
|||
|
|
|
|||
|
|
if (tenantId) {
|
|||
|
|
query = query.where(function(this: any) {
|
|||
|
|
this.whereNull('tenant_id').orWhere('tenant_id', tenantId);
|
|||
|
|
});
|
|||
|
|
} else {
|
|||
|
|
query = query.whereNull('tenant_id');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const row = await query.orderBy('tenant_id', 'desc').first();
|
|||
|
|
|
|||
|
|
if (!row) return null;
|
|||
|
|
|
|||
|
|
return this.mapRowToRule(row);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取所有等级规则
|
|||
|
|
* @param tenantId 租户ID(可选)
|
|||
|
|
* @returns 等级规则列表
|
|||
|
|
*/
|
|||
|
|
static async getAllRules(tenantId?: string): Promise<MemberLevelRule[]> {
|
|||
|
|
let query = db(this.RULE_TABLE)
|
|||
|
|
.where('is_active', true);
|
|||
|
|
|
|||
|
|
if (tenantId) {
|
|||
|
|
query = query.where(function(this: any) {
|
|||
|
|
this.whereNull('tenant_id').orWhere('tenant_id', tenantId);
|
|||
|
|
});
|
|||
|
|
} else {
|
|||
|
|
query = query.whereNull('tenant_id');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const rows = await query.orderBy('min_score', 'asc');
|
|||
|
|
|
|||
|
|
const rules: MemberLevelRule[] = [];
|
|||
|
|
const seenLevels = new Set<string>();
|
|||
|
|
|
|||
|
|
for (const row of rows) {
|
|||
|
|
if (!seenLevels.has(row.level)) {
|
|||
|
|
rules.push(this.mapRowToRule(row));
|
|||
|
|
seenLevels.add(row.level);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return rules;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 根据积分计算等级
|
|||
|
|
* @param tenantId 租户ID
|
|||
|
|
* @param score 积分
|
|||
|
|
* @returns 等级
|
|||
|
|
*/
|
|||
|
|
static async calculateLevel(tenantId: string, score: number): Promise<MemberLevel> {
|
|||
|
|
const rules = await this.getAllRules(tenantId);
|
|||
|
|
|
|||
|
|
if (rules.length === 0) {
|
|||
|
|
return UserAssetService.calculateLevel(score);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for (const rule of rules) {
|
|||
|
|
if (score >= rule.minScore && score <= rule.maxScore) {
|
|||
|
|
return rule.level;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const sortedRules = [...rules].sort((a, b) => b.minScore - a.minScore);
|
|||
|
|
for (const rule of sortedRules) {
|
|||
|
|
if (score >= rule.minScore) {
|
|||
|
|
return rule.level;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return 'BRONZE';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取等级权益
|
|||
|
|
* @param tenantId 租户ID
|
|||
|
|
* @param level 等级
|
|||
|
|
* @returns 权益列表
|
|||
|
|
*/
|
|||
|
|
static async getLevelBenefits(tenantId: string, level: MemberLevel): Promise<string[]> {
|
|||
|
|
const rule = await this.getRule(tenantId, level);
|
|||
|
|
return rule?.benefits || [];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取等级折扣
|
|||
|
|
* @param tenantId 租户ID
|
|||
|
|
* @param level 等级
|
|||
|
|
* @returns 折扣率(0-1)
|
|||
|
|
*/
|
|||
|
|
static async getLevelDiscount(tenantId: string, level: MemberLevel): Promise<number> {
|
|||
|
|
const rule = await this.getRule(tenantId, level);
|
|||
|
|
return rule?.discount || 1.0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取等级积分倍率
|
|||
|
|
* @param tenantId 租户ID
|
|||
|
|
* @param level 等级
|
|||
|
|
* @returns 积分倍率
|
|||
|
|
*/
|
|||
|
|
static async getLevelPointsMultiplier(tenantId: string, level: MemberLevel): Promise<number> {
|
|||
|
|
const rule = await this.getRule(tenantId, level);
|
|||
|
|
return rule?.pointsMultiplier || 1.0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 升级用户等级
|
|||
|
|
* @param tenantId 租户ID
|
|||
|
|
* @param shopId 店铺ID
|
|||
|
|
* @param userId 用户ID
|
|||
|
|
* @param newLevel 新等级
|
|||
|
|
* @param reason 原因
|
|||
|
|
* @param traceId 追踪ID
|
|||
|
|
*/
|
|||
|
|
static async upgradeLevel(
|
|||
|
|
tenantId: string,
|
|||
|
|
shopId: string,
|
|||
|
|
userId: string,
|
|||
|
|
newLevel: MemberLevel,
|
|||
|
|
reason: string,
|
|||
|
|
traceId: string
|
|||
|
|
): Promise<void> {
|
|||
|
|
const asset = await UserAssetService.getByUserId(tenantId, shopId, userId);
|
|||
|
|
if (!asset) {
|
|||
|
|
throw new Error(`User asset not found: userId=${userId}`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const oldLevel = asset.memberLevel;
|
|||
|
|
|
|||
|
|
if (oldLevel === newLevel) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
await db('cf_user_asset')
|
|||
|
|
.where({ tenant_id: tenantId, shop_id: shopId, user_id: userId })
|
|||
|
|
.update({
|
|||
|
|
member_level: newLevel,
|
|||
|
|
updated_at: new Date(),
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
await this.logLevelChange({
|
|||
|
|
tenantId,
|
|||
|
|
shopId,
|
|||
|
|
userId,
|
|||
|
|
oldLevel,
|
|||
|
|
newLevel,
|
|||
|
|
oldScore: asset.memberScore,
|
|||
|
|
newScore: asset.memberScore,
|
|||
|
|
changeType: this.getLevelOrder(newLevel) > this.getLevelOrder(oldLevel) ? 'UPGRADE' : 'DOWNGRADE',
|
|||
|
|
reason,
|
|||
|
|
traceId,
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
logger.info(`[MemberLevelService] Level changed: userId=${userId}, oldLevel=${oldLevel}, newLevel=${newLevel}, traceId=${traceId}`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 批量重新计算用户等级
|
|||
|
|
* @param tenantId 租户ID
|
|||
|
|
* @param shopId 店铺ID
|
|||
|
|
* @returns 更新的用户数
|
|||
|
|
*/
|
|||
|
|
static async batchRecalculateLevels(
|
|||
|
|
tenantId: string,
|
|||
|
|
shopId?: string
|
|||
|
|
): Promise<number> {
|
|||
|
|
let query = db('cf_user_asset').where('tenant_id', tenantId);
|
|||
|
|
if (shopId) {
|
|||
|
|
query = query.where('shop_id', shopId);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const users = await query;
|
|||
|
|
let updatedCount = 0;
|
|||
|
|
|
|||
|
|
for (const user of users) {
|
|||
|
|
try {
|
|||
|
|
const newLevel = await this.calculateLevel(tenantId, user.member_score);
|
|||
|
|
|
|||
|
|
if (newLevel !== user.member_level) {
|
|||
|
|
await this.upgradeLevel(
|
|||
|
|
tenantId,
|
|||
|
|
user.shop_id,
|
|||
|
|
user.user_id,
|
|||
|
|
newLevel,
|
|||
|
|
'系统自动计算',
|
|||
|
|
`AUTO-RECALC-${Date.now()}`
|
|||
|
|
);
|
|||
|
|
updatedCount++;
|
|||
|
|
}
|
|||
|
|
} catch (error: any) {
|
|||
|
|
logger.error(`[MemberLevelService] Failed to recalculate level: userId=${user.user_id}, error=${error.message}`);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
logger.info(`[MemberLevelService] Batch recalculate completed: updated=${updatedCount}`);
|
|||
|
|
|
|||
|
|
return updatedCount;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取等级变更历史
|
|||
|
|
* @param tenantId 租户ID
|
|||
|
|
* @param userId 用户ID
|
|||
|
|
* @param limit 限制条数
|
|||
|
|
* @returns 变更历史
|
|||
|
|
*/
|
|||
|
|
static async getLevelHistory(
|
|||
|
|
tenantId: string,
|
|||
|
|
userId: string,
|
|||
|
|
limit: number = 20
|
|||
|
|
): Promise<Array<{
|
|||
|
|
oldLevel: MemberLevel | null;
|
|||
|
|
newLevel: MemberLevel;
|
|||
|
|
changeType: string;
|
|||
|
|
reason: string;
|
|||
|
|
createdAt: Date;
|
|||
|
|
}>> {
|
|||
|
|
const rows = await db(this.LOG_TABLE)
|
|||
|
|
.where({ tenant_id: tenantId, user_id: userId })
|
|||
|
|
.orderBy('created_at', 'desc')
|
|||
|
|
.limit(limit);
|
|||
|
|
|
|||
|
|
return rows.map(row => ({
|
|||
|
|
oldLevel: row.old_level,
|
|||
|
|
newLevel: row.new_level,
|
|||
|
|
changeType: row.change_type,
|
|||
|
|
reason: row.reason,
|
|||
|
|
createdAt: row.created_at,
|
|||
|
|
}));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取等级分布统计
|
|||
|
|
* @param tenantId 租户ID
|
|||
|
|
* @param shopId 店铺ID(可选)
|
|||
|
|
* @returns 等级分布
|
|||
|
|
*/
|
|||
|
|
static async getLevelDistribution(
|
|||
|
|
tenantId: string,
|
|||
|
|
shopId?: string
|
|||
|
|
): Promise<Record<MemberLevel, number>> {
|
|||
|
|
let query = db('cf_user_asset').where('tenant_id', tenantId);
|
|||
|
|
if (shopId) {
|
|||
|
|
query = query.where('shop_id', shopId);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const rows = await query;
|
|||
|
|
|
|||
|
|
const distribution: Record<MemberLevel, number> = {
|
|||
|
|
BRONZE: 0,
|
|||
|
|
SILVER: 0,
|
|||
|
|
GOLD: 0,
|
|||
|
|
PLATINUM: 0,
|
|||
|
|
DIAMOND: 0,
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
for (const row of rows) {
|
|||
|
|
distribution[row.member_level as MemberLevel]++;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return distribution;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 初始化默认等级规则
|
|||
|
|
*/
|
|||
|
|
static async initDefaultRules(): Promise<void> {
|
|||
|
|
const defaultRules: MemberLevelRuleCreateInput[] = [
|
|||
|
|
{
|
|||
|
|
level: 'BRONZE',
|
|||
|
|
minScore: 0,
|
|||
|
|
maxScore: 499,
|
|||
|
|
discount: 1.0,
|
|||
|
|
pointsMultiplier: 1.0,
|
|||
|
|
benefits: ['基础积分获取', '生日优惠'],
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
level: 'SILVER',
|
|||
|
|
minScore: 500,
|
|||
|
|
maxScore: 1999,
|
|||
|
|
discount: 0.98,
|
|||
|
|
pointsMultiplier: 1.2,
|
|||
|
|
benefits: ['基础积分获取', '生日优惠', '专属客服', '优先发货'],
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
level: 'GOLD',
|
|||
|
|
minScore: 2000,
|
|||
|
|
maxScore: 4999,
|
|||
|
|
discount: 0.95,
|
|||
|
|
pointsMultiplier: 1.5,
|
|||
|
|
benefits: ['基础积分获取', '生日优惠', '专属客服', '优先发货', '会员专享价', '免费退换货'],
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
level: 'PLATINUM',
|
|||
|
|
minScore: 5000,
|
|||
|
|
maxScore: 9999,
|
|||
|
|
discount: 0.92,
|
|||
|
|
pointsMultiplier: 2.0,
|
|||
|
|
benefits: ['基础积分获取', '生日优惠', '专属客服', '优先发货', '会员专享价', '免费退换货', '专属活动', '积分双倍日'],
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
level: 'DIAMOND',
|
|||
|
|
minScore: 10000,
|
|||
|
|
maxScore: 999999,
|
|||
|
|
discount: 0.88,
|
|||
|
|
pointsMultiplier: 3.0,
|
|||
|
|
benefits: ['基础积分获取', '生日优惠', '专属客服', '优先发货', '会员专享价', '免费退换货', '专属活动', '积分双倍日', '一对一顾问', '新品优先体验', '年度礼包'],
|
|||
|
|
},
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
for (const rule of defaultRules) {
|
|||
|
|
await this.createOrUpdateRule(rule);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
logger.info(`[MemberLevelService] Default rules initialized: count=${defaultRules.length}`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 记录等级变更日志
|
|||
|
|
*/
|
|||
|
|
private static async logLevelChange(params: {
|
|||
|
|
tenantId: string;
|
|||
|
|
shopId: string;
|
|||
|
|
userId: string;
|
|||
|
|
oldLevel: MemberLevel;
|
|||
|
|
newLevel: MemberLevel;
|
|||
|
|
oldScore: number;
|
|||
|
|
newScore: number;
|
|||
|
|
changeType: string;
|
|||
|
|
reason: string;
|
|||
|
|
traceId: string;
|
|||
|
|
}): Promise<void> {
|
|||
|
|
await db(this.LOG_TABLE).insert({
|
|||
|
|
id: this.generateId(),
|
|||
|
|
tenant_id: params.tenantId,
|
|||
|
|
shop_id: params.shopId,
|
|||
|
|
user_id: params.userId,
|
|||
|
|
trace_id: params.traceId,
|
|||
|
|
old_level: params.oldLevel,
|
|||
|
|
new_level: params.newLevel,
|
|||
|
|
old_score: params.oldScore,
|
|||
|
|
new_score: params.newScore,
|
|||
|
|
change_type: params.changeType,
|
|||
|
|
reason: params.reason,
|
|||
|
|
created_at: new Date(),
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取等级顺序
|
|||
|
|
*/
|
|||
|
|
private static getLevelOrder(level: MemberLevel): number {
|
|||
|
|
const order: Record<MemberLevel, number> = {
|
|||
|
|
BRONZE: 1,
|
|||
|
|
SILVER: 2,
|
|||
|
|
GOLD: 3,
|
|||
|
|
PLATINUM: 4,
|
|||
|
|
DIAMOND: 5,
|
|||
|
|
};
|
|||
|
|
return order[level] || 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 生成唯一ID
|
|||
|
|
*/
|
|||
|
|
private static generateId(): string {
|
|||
|
|
return `MLR-${Date.now()}-${Math.random().toString(36).substring(2, 10)}`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 将数据库行映射为规则对象
|
|||
|
|
*/
|
|||
|
|
private static mapRowToRule(row: Record<string, any>): MemberLevelRule {
|
|||
|
|
return {
|
|||
|
|
id: row.id,
|
|||
|
|
tenantId: row.tenant_id,
|
|||
|
|
level: row.level,
|
|||
|
|
minScore: Number(row.min_score),
|
|||
|
|
maxScore: Number(row.max_score),
|
|||
|
|
discount: Number(row.discount),
|
|||
|
|
pointsMultiplier: Number(row.points_multiplier),
|
|||
|
|
benefits: JSON.parse(row.benefits),
|
|||
|
|
isActive: Boolean(row.is_active),
|
|||
|
|
createdAt: row.created_at,
|
|||
|
|
updatedAt: row.updated_at,
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
}
|