200 lines
6.2 KiB
TypeScript
200 lines
6.2 KiB
TypeScript
import {
|
|
Body,
|
|
Controller,
|
|
Delete,
|
|
Get,
|
|
Headers,
|
|
Inject,
|
|
Param,
|
|
Patch,
|
|
Post,
|
|
Query,
|
|
Req,
|
|
Res,
|
|
} from '@nestjs/common';
|
|
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
|
|
import { z } from 'zod';
|
|
import {
|
|
pricingPreviewRequestSchema,
|
|
pricingPolicyRequestSchema,
|
|
type Workspace,
|
|
} from '@atlas/contracts';
|
|
import { CurrentWorkspace } from '../workspace/current-workspace.decorator.js';
|
|
import type { AuthenticatedRequest } from '../auth/auth.guard.js';
|
|
import { ApiError } from '../common/api-error.js';
|
|
import { PricingService } from './pricing.service.js';
|
|
type Response = { status: (status: number) => unknown };
|
|
function parse<T>(schema: z.ZodType<T>, value: unknown): T {
|
|
const result = schema.safeParse(value);
|
|
if (!result.success)
|
|
throw new ApiError(
|
|
422,
|
|
'VALIDATION_ERROR',
|
|
'请检查定价输入',
|
|
result.error.issues.map((i) => ({
|
|
field: i.path.join('.'),
|
|
message: i.message,
|
|
})),
|
|
);
|
|
return result.data;
|
|
}
|
|
function key(value: string | undefined) {
|
|
if (!value || value.length > 200)
|
|
throw new ApiError(
|
|
400,
|
|
'IDEMPOTENCY_KEY_REQUIRED',
|
|
'需要 Idempotency-Key',
|
|
);
|
|
return value;
|
|
}
|
|
@ApiTags('Pricing')
|
|
@ApiBearerAuth('access-token')
|
|
@Controller()
|
|
export class PricingController {
|
|
constructor(
|
|
@Inject(PricingService) private readonly service: PricingService,
|
|
) {}
|
|
@Get('pricing/commission-rules') async commissionRules(
|
|
@CurrentWorkspace() w: Workspace,
|
|
@Query('store_id') store: string,
|
|
@Query('platform_product_category_id') category: string | undefined,
|
|
@Query('description_category_id') external: string | undefined,
|
|
) {
|
|
if (Boolean(category) === Boolean(external)) throw new ApiError(422,'VALIDATION_ERROR','请指定一种类目 ID');
|
|
return {data:await this.service.commissionRules(w.id,parse(z.uuid(),store),category ? parse(z.uuid(),category) : undefined,external ? parse(z.string().regex(/^[1-9]\d{0,17}$/),external) : undefined)};
|
|
}
|
|
@Get('pricing/categories') async categories(
|
|
@CurrentWorkspace() w: Workspace,
|
|
@Query('store_id') store: string,
|
|
@Query('q') q: string | undefined,
|
|
) {
|
|
return {
|
|
data: await this.service.categories(
|
|
w.id,
|
|
parse(z.uuid(), store),
|
|
parse(z.string().max(200), q ?? ''),
|
|
),
|
|
};
|
|
}
|
|
@Get('pricing/catalog') async catalog(
|
|
@CurrentWorkspace() w: Workspace,
|
|
@Query('store_id') store: string,
|
|
) {
|
|
return {
|
|
data: await this.service.catalog(w.id, parse(z.uuid(), store)),
|
|
};
|
|
}
|
|
@Get('stores/:id/pricing-policies') async policies(
|
|
@CurrentWorkspace() w: Workspace,
|
|
@Param('id') id: string,
|
|
) {
|
|
return {
|
|
data: (await this.service.catalog(w.id, parse(z.uuid(), id)))
|
|
.policies,
|
|
};
|
|
}
|
|
@Post('pricing/quote-previews') async preview(
|
|
@CurrentWorkspace() w: Workspace,
|
|
@Req() req: AuthenticatedRequest,
|
|
@Headers('idempotency-key') k: string | undefined,
|
|
@Body() body: unknown,
|
|
@Res({ passthrough: true }) res: Response,
|
|
) {
|
|
const r = await this.service.preview(
|
|
w.id,
|
|
req.auth.userId,
|
|
key(k),
|
|
parse(pricingPreviewRequestSchema, body),
|
|
);
|
|
res.status(r.status);
|
|
return r.body;
|
|
}
|
|
@Post('stores/:id/pricing-policies') async create(
|
|
@CurrentWorkspace() w: Workspace,
|
|
@Req() req: AuthenticatedRequest,
|
|
@Param('id') id: string,
|
|
@Headers('idempotency-key') k: string | undefined,
|
|
@Body() body: unknown,
|
|
@Res({ passthrough: true }) res: Response,
|
|
) {
|
|
const r = await this.service.writePolicy(
|
|
w.id,
|
|
req.auth.userId,
|
|
key(k),
|
|
parse(z.uuid(), id),
|
|
null,
|
|
'create',
|
|
parse(pricingPolicyRequestSchema, body),
|
|
null,
|
|
);
|
|
res.status(r.status);
|
|
return r.body;
|
|
}
|
|
@Patch('pricing-policies/:id') async update(
|
|
@CurrentWorkspace() w: Workspace,
|
|
@Req() req: AuthenticatedRequest,
|
|
@Param('id') id: string,
|
|
@Headers('idempotency-key') k: string | undefined,
|
|
@Headers('if-match') match: string | undefined,
|
|
@Body() body: unknown,
|
|
) {
|
|
if (!match || !/^(?:[1-9]\d*|"[1-9]\d*")$/.test(match))
|
|
throw new ApiError(
|
|
428,
|
|
'PRECONDITION_REQUIRED',
|
|
'需要方案版本 If-Match',
|
|
);
|
|
return (
|
|
await this.service.writePolicy(
|
|
w.id,
|
|
req.auth.userId,
|
|
key(k),
|
|
null,
|
|
parse(z.uuid(), id),
|
|
'update',
|
|
parse(pricingPolicyRequestSchema, body),
|
|
Number(match.replaceAll('"', '')),
|
|
)
|
|
).body;
|
|
}
|
|
@Post('pricing-policies/:id/set-default') async defaultPolicy(
|
|
@CurrentWorkspace() w: Workspace,
|
|
@Req() req: AuthenticatedRequest,
|
|
@Param('id') id: string,
|
|
@Headers('idempotency-key') k: string | undefined,
|
|
@Res({ passthrough: true }) res: Response,
|
|
) {
|
|
const r = await this.service.writePolicy(
|
|
w.id,
|
|
req.auth.userId,
|
|
key(k),
|
|
null,
|
|
parse(z.uuid(), id),
|
|
'default',
|
|
null,
|
|
null,
|
|
);
|
|
res.status(r.status);
|
|
return r.body;
|
|
}
|
|
@Delete('pricing-policies/:id') async archive(
|
|
@CurrentWorkspace() w: Workspace,
|
|
@Req() req: AuthenticatedRequest,
|
|
@Param('id') id: string,
|
|
@Headers('idempotency-key') k: string | undefined,
|
|
) {
|
|
return (
|
|
await this.service.writePolicy(
|
|
w.id,
|
|
req.auth.userId,
|
|
key(k),
|
|
null,
|
|
parse(z.uuid(), id),
|
|
'archive',
|
|
null,
|
|
null,
|
|
)
|
|
).body;
|
|
}
|
|
}
|