import { type CreateCustomerInput, createCustomerInputSchema, type CustomerDetail, type CustomerListQuery, customerListQuerySchema, type CustomerSummary, ErrorCode, type ListResponse, Permission, type SetCustomerGroupsInput, setCustomerGroupsInputSchema, type SetCustomerStatusInput, setCustomerStatusInputSchema, type TransferCustomerInput, transferCustomerInputSchema, type TransferCustomersInput, transferCustomersInputSchema, type UpdateCustomerInput, updateCustomerInputSchema, } from '@lingxi/shared'; import { Body, Controller, Get, HttpCode, Inject, Param, Patch, Post, Put, Query, } from '@nestjs/common'; import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; import { AppException } from '../../common/errors/app-exception'; import { ZodValidationPipe } from '../../common/pipes/zod-validation.pipe'; import type { AuthContext } from '../../common/security/auth.types'; import { Audit, CurrentScope, CurrentUser, RequirePermissions, } from '../../common/security/decorators'; import type { ScopeContext } from '../../common/security/scope/scope-context'; import { CustomersService } from './customers.service'; function requireAuth(auth: AuthContext | null): AuthContext { if (auth === null) { throw new AppException({ code: ErrorCode.UNAUTHENTICATED }); } return auth; } @ApiTags('client-customers') @ApiBearerAuth() @Controller('customers') export class CustomersController { constructor(@Inject(CustomersService) private readonly customers: CustomersService) {} @Get() @RequirePermissions(Permission.CUSTOMER_READ) @ApiOperation({ summary: '客户列表(游标分页 + 行业 / 状态 / 负责人 / 客户组筛选)' }) list( @Query(new ZodValidationPipe(customerListQuerySchema)) query: CustomerListQuery, @CurrentScope() scope: ScopeContext | null, ): Promise> { return this.customers.list(query, scope); } @Get(':id') @RequirePermissions(Permission.CUSTOMER_READ) @ApiOperation({ summary: '客户详情(跨客户访问统一 404)' }) detail( @Param('id') id: string, @CurrentScope() scope: ScopeContext | null, ): Promise { return this.customers.detail(id, scope); } @Post() @RequirePermissions(Permission.CUSTOMER_MANAGE) @Audit('client.customer.create') @ApiOperation({ summary: '新建客户(简称必填;行业必须为末级行业)' }) create( @Body(new ZodValidationPipe(createCustomerInputSchema)) input: CreateCustomerInput, @CurrentUser() auth: AuthContext | null, @CurrentScope() scope: ScopeContext | null, ): Promise { return this.customers.create(input, requireAuth(auth).userId, scope); } @Patch(':id') @RequirePermissions(Permission.CUSTOMER_MANAGE) @Audit('client.customer.update') @ApiOperation({ summary: '更新客户档案(所属行业不可改;暂停 / 结束只读)' }) update( @Param('id') id: string, @Body(new ZodValidationPipe(updateCustomerInputSchema)) input: UpdateCustomerInput, @CurrentUser() auth: AuthContext | null, @CurrentScope() scope: ScopeContext | null, ): Promise { return this.customers.update(id, input, scope, requireAuth(auth).permissions); } @Patch(':id/status') @RequirePermissions(Permission.CUSTOMER_MANAGE) @Audit('client.customer.status') @ApiOperation({ summary: '客户状态:在服务 ⇄ 暂停 → 结束(结束不可逆)' }) setStatus( @Param('id') id: string, @Body(new ZodValidationPipe(setCustomerStatusInputSchema)) input: SetCustomerStatusInput, @CurrentScope() scope: ScopeContext | null, ): Promise { return this.customers.setStatus(id, input, scope); } @Post(':id/transfer') @HttpCode(200) @RequirePermissions(Permission.CUSTOMER_TRANSFER) @Audit('client.customer.transfer') @ApiOperation({ summary: '变更客户负责人(写审计)' }) transfer( @Param('id') id: string, @Body(new ZodValidationPipe(transferCustomerInputSchema)) input: TransferCustomerInput, @CurrentUser() auth: AuthContext | null, @CurrentScope() scope: ScopeContext | null, ): Promise { return this.customers.transfer(id, input, scope, requireAuth(auth).permissions); } @Put(':id/groups') @RequirePermissions(Permission.CUSTOMER_GROUP_MANAGE, Permission.CUSTOMER_MANAGE) @Audit('client.customer.groups') @ApiOperation({ summary: '设置客户所属客户组(覆盖式)' }) setGroups( @Param('id') id: string, @Body(new ZodValidationPipe(setCustomerGroupsInputSchema)) input: SetCustomerGroupsInput, @CurrentScope() scope: ScopeContext | null, ): Promise { return this.customers.setGroups(id, input, scope); } @Post(':id/apply-industry-pack') @HttpCode(200) @RequirePermissions(Permission.CUSTOMER_MANAGE) @Audit('client.customer.apply_industry_pack') @ApiOperation({ summary: '套用行业资产包(B0 批次提供;当前返回业务提示)' }) applyIndustryPack(): never { return this.customers.applyIndustryPack(); } } /** 用户停用前的客户转交:保留 A1 路径 /users/{id}/transfer-customers,由 client 模块落地 */ @ApiTags('client-customers') @ApiBearerAuth() @Controller('users') export class CustomerTransferController { constructor(@Inject(CustomersService) private readonly customers: CustomersService) {} @Post(':id/transfer-customers') @HttpCode(200) @RequirePermissions(Permission.CUSTOMER_TRANSFER, Permission.USER_MANAGE) @Audit('identity.user.transfer_customers') @ApiOperation({ summary: '把用户名下客户转交给另一负责人' }) async transfer( @Param('id') fromUserId: string, @Body(new ZodValidationPipe(transferCustomersInputSchema)) input: TransferCustomersInput, @CurrentUser() auth: AuthContext | null, ): Promise<{ transferred: number }> { return this.customers.transferByUser( fromUserId, input.toUserId, input.customerIds, requireAuth(auth).permissions, ); } }