import { Inject, Injectable } from '@nestjs/common'; import type { Database, DatabaseTransaction } from '@atlas/database'; import { ApiError } from '../common/api-error.js'; import { DATABASE } from '../database/database.module.js'; type PlatformRow = { id: string; code: string; name: string; capabilities: Record; supported_currencies: string[]; }; @Injectable() export class PlatformsService { public constructor(@Inject(DATABASE) private readonly database: Database) {} public async list() { const rows = await this.database.query( `SELECT p.id, p.code, p.name, p.capabilities, coalesce(array_agg(pcs.currency_code ORDER BY pcs.currency_code) FILTER (WHERE pcs.is_enabled AND c.is_enabled), ARRAY[]::char(3)[]) AS supported_currencies FROM atlas.platforms p LEFT JOIN atlas.platform_currency_support pcs ON pcs.platform_id = p.id LEFT JOIN atlas.currencies c ON c.code = pcs.currency_code WHERE p.status = 'active' GROUP BY p.id ORDER BY CASE WHEN p.code = 'OZON' THEN 0 ELSE 1 END, p.name`, ); return rows.rows.map((platform) => ({ id: platform.id, code: platform.code, name: platform.name, supported_currencies: platform.supported_currencies, // 能力未配置时使用 null,而不是把 Ozon 的市场或语言假设扩散到通用模型。 default_market_code: stringCapability( platform.capabilities, 'default_market_code', ), default_locale: stringCapability( platform.capabilities, 'default_locale', ), })); } public async context(workspaceId: string, platformId: string) { return this.database.workspaceTransaction( workspaceId, async (transaction) => { const platform = await this.requireActivePlatform( transaction, platformId, ); const stores = await transaction.query<{ id: string; name: string; status: string; credential_status: string; count: string; }>( `SELECT s.id, s.name, s.status, s.credential_status, count(*) OVER ()::text AS count FROM atlas.stores s WHERE s.platform_id = $1 AND s.deleted_at IS NULL AND s.is_default LIMIT 1`, [platformId], ); // 默认门店不存在时仍要正确返回总数,不能用默认行的窗口函数猜测。 const count = await transaction.query<{ count: string }>( `SELECT count(*)::text AS count FROM atlas.stores WHERE platform_id = $1 AND deleted_at IS NULL`, [platformId], ); const defaultStore = stores.rows[0]; return { platform: { id: platform.id, code: platform.code, name: platform.name, }, default_store: defaultStore ? { id: defaultStore.id, name: defaultStore.name, status: defaultStore.status, credential_status: defaultStore.credential_status, } : null, store_count: Number(count.rows[0]?.count ?? 0), }; }, ); } private async requireActivePlatform( transaction: DatabaseTransaction, platformId: string, ) { const rows = await transaction.query<{ id: string; code: string; name: string; }>( `SELECT id, code, name FROM atlas.platforms WHERE id = $1 AND status = 'active'`, [platformId], ); if (!rows.rows[0]) { throw new ApiError( 422, 'PLATFORM_UNAVAILABLE', '平台不存在或已停用', ); } return rows.rows[0]; } } function stringCapability( capabilities: Record, key: string, ): string | null { const value = capabilities[key]; return typeof value === 'string' ? value : null; }