feat(api): 开发平台、门店相关代码
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
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<string, unknown>;
|
||||
supported_currencies: string[];
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class PlatformsService {
|
||||
public constructor(@Inject(DATABASE) private readonly database: Database) {}
|
||||
|
||||
public async list() {
|
||||
const rows = await this.database.query<PlatformRow>(
|
||||
`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<string, unknown>,
|
||||
key: string,
|
||||
): string | null {
|
||||
const value = capabilities[key];
|
||||
return typeof value === 'string' ? value : null;
|
||||
}
|
||||
Reference in New Issue
Block a user