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(schema: z.ZodType, 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; } }