import { Provide } from '@midwayjs/core'; import { BaseService } from '@cool-midway/core'; import { InjectEntityModel } from '@midwayjs/typeorm'; import { Repository } from 'typeorm'; import * as moment from 'moment'; import { TeamInvoiceEntity } from '../entity/invoice'; import { TeamSubscriptionEntity } from '../entity/subscription'; import { ManualRenewDTO, PlanConfigItem, PlatformOverviewResult, RenewalOrderItem, SubscriberItem, TenantPlanType, UpdateSubscriberDTO, } from '../../../comm/types/omni'; /** * 平台总后台超级管理员 (Platform Super Admin) * 会员、续费与订阅全周期运营管控服务 */ @Provide() export class PlatformSubscriptionService extends BaseService { @InjectEntityModel(TeamInvoiceEntity) teamInvoiceEntity?: Repository; @InjectEntityModel(TeamSubscriptionEntity) teamSubscriptionEntity?: Repository; // 内存化种子会员数据池 (保障无缝离线测试与即席演示) private subscribersStore: SubscriberItem[] = [ { id: 1, tenantId: 1, tenantName: 'Vanguard Global Trade Ltd. (出海先锋外贸)', adminName: 'David Zhang', adminEmail: 'david@vanguard-trade.com', adminPhone: '+86 13800138001', planType: 'enterprise', status: 'active', cardsUsed: 42, cardsLimit: 100, seatsUsed: 18, seatsLimit: 50, totalPaidAmount: 7176, currency: 'USD', startDate: '2025-09-01', expireDate: '2026-09-01', createdAt: '2025-08-28 10:20:00', }, { id: 2, tenantId: 2, tenantName: 'Berlin Precision Robotics GmbH (柏林精密机器人)', adminName: 'Klaus Schmidt', adminEmail: 'klaus@berlin-robotics.de', adminPhone: '+49 30 1234567', planType: 'pro', status: 'active', cardsUsed: 5, cardsLimit: 10, seatsUsed: 4, seatsLimit: 5, totalPaidAmount: 1176, currency: 'EUR', startDate: '2026-01-15', expireDate: '2027-01-15', createdAt: '2026-01-10 14:35:12', }, { id: 3, tenantId: 3, tenantName: 'Dubai Apex Logistics LLC (迪拜巅峰国际物流)', adminName: 'Ahmed Al-Mansoor', adminEmail: 'ahmed@apexlogistics.ae', adminPhone: '+971 4 9876543', planType: 'enterprise', status: 'active', cardsUsed: 35, cardsLimit: 100, seatsUsed: 12, seatsLimit: 50, totalPaidAmount: 3588, currency: 'USD', startDate: '2026-03-01', expireDate: '2027-03-01', createdAt: '2026-02-25 09:12:44', }, { id: 4, tenantId: 4, tenantName: 'Silicon Horizon Tech Inc. (硅谷地平线科技)', adminName: 'Sarah Jenkins', adminEmail: 'sarah@siliconhorizon.io', adminPhone: '+1 415 8009988', planType: 'pro', status: 'past_due', cardsUsed: 8, cardsLimit: 10, seatsUsed: 5, seatsLimit: 5, totalPaidAmount: 588, currency: 'USD', startDate: '2025-09-10', expireDate: '2026-09-10', createdAt: '2025-09-05 16:40:22', }, { id: 5, tenantId: 5, tenantName: 'Nordic Clean Energy AB (北欧洁能科技)', adminName: 'Erik Lindqvist', adminEmail: 'erik@nordic-energy.se', adminPhone: '+46 8 5556677', planType: 'pro', status: 'active', cardsUsed: 4, cardsLimit: 10, seatsUsed: 3, seatsLimit: 5, totalPaidAmount: 1176, currency: 'EUR', startDate: '2025-11-20', expireDate: '2026-11-20', createdAt: '2025-11-15 11:20:00', }, { id: 6, tenantId: 6, tenantName: 'Tokyo Smart Automation KK (东京智能物联)', adminName: 'Kenji Sato', adminEmail: 'kenji@tokyo-smart.jp', adminPhone: '+81 3 33445566', planType: 'free', status: 'active', cardsUsed: 1, cardsLimit: 1, seatsUsed: 1, seatsLimit: 1, totalPaidAmount: 0, currency: 'USD', startDate: '2026-08-01', expireDate: '2099-12-31', createdAt: '2026-08-01 10:00:00', }, ]; // 内存化种子续费流水池 private renewalsStore: RenewalOrderItem[] = [ { id: 1, orderNo: 'ORD-20260921-0089', tenantId: 1, tenantName: 'Vanguard Global Trade Ltd.', planType: 'enterprise', billingCycle: 'yearly', amount: 3588, currency: 'USD', channel: 'stripe', status: 'paid', paidAt: '2026-09-21 00:15:20', createdAt: '2026-09-21 00:15:10', }, { id: 2, orderNo: 'ORD-20260920-0088', tenantId: 2, tenantName: 'Berlin Precision Robotics GmbH', planType: 'pro', billingCycle: 'yearly', amount: 588, currency: 'EUR', channel: 'stripe', status: 'paid', paidAt: '2026-09-20 18:42:10', createdAt: '2026-09-20 18:42:00', }, { id: 3, orderNo: 'ORD-20260920-0087', tenantId: 3, tenantName: 'Dubai Apex Logistics LLC', planType: 'enterprise', billingCycle: 'monthly', amount: 299, currency: 'USD', channel: 'wire_transfer', status: 'paid', paidAt: '2026-09-20 14:22:05', createdAt: '2026-09-20 14:10:00', }, { id: 4, orderNo: 'ORD-20260919-0086', tenantId: 4, tenantName: 'Silicon Horizon Tech Inc.', planType: 'pro', billingCycle: 'monthly', amount: 49, currency: 'USD', channel: 'stripe', status: 'pending', paidAt: '', createdAt: '2026-09-19 22:11:34', }, { id: 5, orderNo: 'ORD-20260919-0085', tenantId: 5, tenantName: 'Nordic Clean Energy AB', planType: 'pro', billingCycle: 'yearly', amount: 588, currency: 'EUR', channel: 'manual_admin', status: 'paid', paidAt: '2026-09-19 11:05:49', createdAt: '2026-09-19 11:05:00', }, ]; // SaaS 套餐规格与定价方案配置 private planConfigs: PlanConfigItem[] = [ { planType: 'free', name: 'Free 基础体验版', description: '适合个人自由职业者与初创独立人,轻量体验出海名片微官网', monthlyPrice: 0, yearlyPrice: 0, cardsLimit: 1, seatsLimit: 1, aiTokensPerMonth: 5000, nfcDynamicRouting: false, supportCustomDomain: false, crmLeadLimit: 50, isPopular: false, }, { planType: 'pro', name: 'Pro 专业商务版', description: '面向出海业务精英、外贸销售主管与跨国顾问的高性能全功能套件', monthlyPrice: 49, yearlyPrice: 490, cardsLimit: 10, seatsLimit: 5, aiTokensPerMonth: 200000, nfcDynamicRouting: true, supportCustomDomain: true, crmLeadLimit: 2000, isPopular: true, }, { planType: 'enterprise', name: 'Enterprise 商业企业版', description: '为出海跨国集团、参展销售天团定制的多租户公海池与品牌统一管控', monthlyPrice: 299, yearlyPrice: 2990, cardsLimit: 100, seatsLimit: 50, aiTokensPerMonth: 1000000, nfcDynamicRouting: true, supportCustomDomain: true, crmLeadLimit: 50000, isPopular: false, }, ]; /** * 1. 平台总览与核心 KPI 概览 */ async getOverview(): Promise { const totalSubscribers = this.subscribersStore.length; const activeSubscribers = this.subscribersStore.filter( s => s.status === 'active' || s.status === 'trialing' ).length; const proCount = this.subscribersStore.filter(s => s.planType === 'pro').length; const enterpriseCount = this.subscribersStore.filter(s => s.planType === 'enterprise').length; // 即将到期判定 (当前日期后 30 天内到期) const now = moment(); const expiringSoonCount = this.subscribersStore.filter(s => { if (s.planType === 'free') return false; const exp = moment(s.expireDate); const diffDays = exp.diff(now, 'days'); return diffDays >= -5 && diffDays <= 30; }).length; const totalRenewalRevenue = this.renewalsStore .filter(r => r.status === 'paid') .reduce((sum, r) => sum + Number(r.amount || 0), 0); return { totalSubscribers, activeSubscribers, proCount, enterpriseCount, expiringSoonCount, totalRenewalRevenue, currency: 'USD', }; } /** * 2. 分页及多条件查询全网会员列表 */ async getSubscribers(query: { page?: number; size?: number; planType?: TenantPlanType; status?: string; keyword?: string; } = {}): Promise<{ list: SubscriberItem[]; pagination: { page: number; size: number; total: number }; }> { const page = Number(query.page || 1); const size = Number(query.size || 20); let filtered = [...this.subscribersStore]; if (query.planType) { filtered = filtered.filter(s => s.planType === query.planType); } if (query.status) { filtered = filtered.filter(s => s.status === query.status); } if (query.keyword) { const kw = query.keyword.toLowerCase().trim(); filtered = filtered.filter( s => String(s.tenantId).includes(kw) || s.tenantName.toLowerCase().includes(kw) || s.adminEmail.toLowerCase().includes(kw) || s.adminName.toLowerCase().includes(kw) ); } const total = filtered.length; const start = (page - 1) * size; const list = filtered.slice(start, start + size).map(item => ({ ...item })); return { list, pagination: { page, size, total, }, }; } /** * 3. 超级管理员修改会员信息 (套餐、配额、延期、状态) */ async updateSubscriber(dto: UpdateSubscriberDTO): Promise { const item = this.subscribersStore.find(s => s.tenantId === Number(dto.tenantId)); if (!item) { throw new Error(`未找到租户 ID 为 ${dto.tenantId} 的会员`); } if (dto.planType) { item.planType = dto.planType; // 联动规格默认配额 const targetPlan = this.planConfigs.find(p => p.planType === dto.planType); if (targetPlan) { item.cardsLimit = targetPlan.cardsLimit; item.seatsLimit = targetPlan.seatsLimit; } } if (typeof dto.cardsLimit === 'number') { item.cardsLimit = dto.cardsLimit; } if (typeof dto.seatsLimit === 'number') { item.seatsLimit = dto.seatsLimit; } if (dto.status) { item.status = dto.status; } if (dto.extendDays && dto.extendDays > 0) { const baseDate = moment(item.expireDate).isValid() ? moment(item.expireDate) : moment(); item.expireDate = baseDate.add(dto.extendDays, 'days').format('YYYY-MM-DD'); item.status = 'active'; } else if (dto.expireDate) { item.expireDate = dto.expireDate; } return { ...item }; } /** * 4. 查询全网续费订单流水台账 */ async getRenewals(query: { page?: number; size?: number; status?: string; keyword?: string; } = {}): Promise<{ list: RenewalOrderItem[]; pagination: { page: number; size: number; total: number }; }> { const page = Number(query.page || 1); const size = Number(query.size || 20); let filtered = [...this.renewalsStore]; if (query.status) { filtered = filtered.filter(r => r.status === query.status); } if (query.keyword) { const kw = query.keyword.toLowerCase().trim(); filtered = filtered.filter( r => r.orderNo.toLowerCase().includes(kw) || r.tenantName.toLowerCase().includes(kw) || String(r.tenantId).includes(kw) ); } const total = filtered.length; const start = (page - 1) * size; const list = filtered.slice(start, start + size); return { list, pagination: { page, size, total, }, }; } /** * 5. 超管手动为会员代续费 / 补录订单 */ async manualRenew(dto: ManualRenewDTO): Promise<{ success: boolean; orderNo: string; item: RenewalOrderItem; subscriber: SubscriberItem; }> { const subscriber = this.subscribersStore.find(s => s.tenantId === Number(dto.tenantId)); if (!subscriber) { throw new Error(`租户 ${dto.tenantId} 不存在`); } const orderNo = `ORD-${moment().format('YYYYMMDD')}-${Math.floor( 1000 + Math.random() * 9000 )}`; const nowStr = moment().format('YYYY-MM-DD HH:mm:ss'); const newOrder: RenewalOrderItem = { id: this.renewalsStore.length + 1, orderNo, tenantId: subscriber.tenantId, tenantName: subscriber.tenantName, planType: dto.planType, billingCycle: dto.billingCycle, amount: dto.amount, currency: dto.currency || 'USD', channel: dto.channel || 'manual_admin', status: 'paid', paidAt: nowStr, createdAt: nowStr, }; this.renewalsStore.unshift(newOrder); // 更新会员期限和套餐 subscriber.planType = dto.planType; const addDays = dto.billingCycle === 'yearly' ? 365 : 30; const baseExpire = moment(subscriber.expireDate).isAfter(moment()) ? moment(subscriber.expireDate) : moment(); subscriber.expireDate = baseExpire.add(addDays, 'days').format('YYYY-MM-DD'); subscriber.status = 'active'; subscriber.totalPaidAmount = Number(subscriber.totalPaidAmount || 0) + Number(dto.amount); return { success: true, orderNo, item: newOrder, subscriber, }; } /** * 6. SaaS 套餐定价规格列表 */ async getPlans(): Promise { return [...this.planConfigs]; } /** * 7. 更新指定套餐定价与配额参数 */ async updatePlan( planType: TenantPlanType, params: Partial ): Promise { const plan = this.planConfigs.find(p => p.planType === planType); if (!plan) { throw new Error(`套餐类型 ${planType} 不存在`); } Object.assign(plan, params); return plan; } }