- Introduced `CustomersModule` to manage customer data, including read and write controllers. - Created database migrations for the `customers` and `customer_contacts` tables, including constraints and unique indexes. - Implemented validation for customer fields such as name, code, and address with corresponding utility functions. - Developed service and repository layers for handling customer data operations. - Added unit tests for the customers service, repository, and controllers to ensure functionality and correctness. - Updated application module to include the new `CustomersModule` for better organization.
228 lines
6.5 KiB
TypeScript
228 lines
6.5 KiB
TypeScript
import {
|
|
BadRequestException,
|
|
Body,
|
|
Controller,
|
|
Delete,
|
|
HttpCode,
|
|
Param,
|
|
ParseUUIDPipe,
|
|
Patch,
|
|
Post,
|
|
UploadedFile,
|
|
UseInterceptors,
|
|
} from '@nestjs/common';
|
|
import { FileInterceptor } from '@nestjs/platform-express';
|
|
import {
|
|
ApiBearerAuth,
|
|
ApiBody,
|
|
ApiConsumes,
|
|
ApiCreatedResponse,
|
|
ApiForbiddenResponse,
|
|
ApiNoContentResponse,
|
|
ApiNotFoundResponse,
|
|
ApiOkResponse,
|
|
ApiOperation,
|
|
ApiTags,
|
|
ApiUnauthorizedResponse,
|
|
} from '@nestjs/swagger';
|
|
import { CurrentUser } from '../../../common/decorators/current-user.decorator';
|
|
import { RequirePrivilege } from '../../../common/decorators/require-privilege.decorator';
|
|
import { BEARER_AUTH_NAME } from '../../../common/swagger/setup-swagger';
|
|
import { isAllowedCsvUpload } from './customer-fields';
|
|
import { CUSTOMER_PRIVILEGE_KEY } from './customers-read.controller';
|
|
import { CustomersService } from './customers.service';
|
|
import {
|
|
BulkIdsDto,
|
|
BulkStatusDto,
|
|
CreateCustomerContactDto,
|
|
CreateCustomerDto,
|
|
CustomerDto,
|
|
UpdateCustomerContactDto,
|
|
UpdateCustomerDto,
|
|
UpdateCustomerStatusDto,
|
|
} from './dto/customer.dto';
|
|
|
|
@ApiTags('customers')
|
|
@ApiBearerAuth(BEARER_AUTH_NAME)
|
|
@Controller('customers')
|
|
export class CustomersWriteController {
|
|
constructor(private readonly customersService: CustomersService) {}
|
|
|
|
@Post('import')
|
|
@RequirePrivilege(CUSTOMER_PRIVILEGE_KEY, 'import')
|
|
@UseInterceptors(
|
|
FileInterceptor('file', {
|
|
limits: { fileSize: 1_048_576 },
|
|
fileFilter: (_req, file, cb) => {
|
|
if (!isAllowedCsvUpload(file)) {
|
|
cb(new BadRequestException('Only CSV files are allowed'), false);
|
|
return;
|
|
}
|
|
cb(null, true);
|
|
},
|
|
}),
|
|
)
|
|
@ApiConsumes('multipart/form-data')
|
|
@ApiBody({
|
|
schema: {
|
|
type: 'object',
|
|
properties: {
|
|
file: { type: 'string', format: 'binary' },
|
|
},
|
|
required: ['file'],
|
|
},
|
|
})
|
|
@ApiOperation({ summary: 'Import customers from CSV' })
|
|
@ApiOkResponse({
|
|
schema: { properties: { imported: { type: 'number' } } },
|
|
})
|
|
@ApiUnauthorizedResponse()
|
|
@ApiForbiddenResponse()
|
|
importCsv(
|
|
@UploadedFile() file: { buffer?: Buffer } | undefined,
|
|
@CurrentUser('id') userId: string,
|
|
): Promise<{ imported: number }> {
|
|
const csv = file?.buffer?.toString('utf8') ?? '';
|
|
return this.customersService.importCsv(csv, userId);
|
|
}
|
|
|
|
@Post('bulk-delete')
|
|
@HttpCode(200)
|
|
@RequirePrivilege(CUSTOMER_PRIVILEGE_KEY, 'delete')
|
|
@ApiOperation({ summary: 'Bulk delete customers' })
|
|
@ApiOkResponse({
|
|
schema: { properties: { deleted: { type: 'number' } } },
|
|
})
|
|
@ApiUnauthorizedResponse()
|
|
@ApiForbiddenResponse()
|
|
bulkDelete(@Body() dto: BulkIdsDto): Promise<{ deleted: number }> {
|
|
return this.customersService.bulkDelete(dto.ids);
|
|
}
|
|
|
|
@Post('bulk-status')
|
|
@HttpCode(200)
|
|
@RequirePrivilege(CUSTOMER_PRIVILEGE_KEY, 'update')
|
|
@ApiOperation({ summary: 'Bulk update customer status' })
|
|
@ApiOkResponse({
|
|
schema: { properties: { updated: { type: 'number' } } },
|
|
})
|
|
@ApiUnauthorizedResponse()
|
|
@ApiForbiddenResponse()
|
|
bulkStatus(
|
|
@Body() dto: BulkStatusDto,
|
|
@CurrentUser('id') userId: string,
|
|
): Promise<{ updated: number }> {
|
|
return this.customersService.bulkUpdateStatus(dto.ids, dto.status, userId);
|
|
}
|
|
|
|
@Post()
|
|
@RequirePrivilege(CUSTOMER_PRIVILEGE_KEY, 'create')
|
|
@ApiOperation({ summary: 'Create customer' })
|
|
@ApiCreatedResponse({ type: CustomerDto })
|
|
@ApiUnauthorizedResponse()
|
|
@ApiForbiddenResponse()
|
|
create(
|
|
@Body() dto: CreateCustomerDto,
|
|
@CurrentUser('id') userId: string,
|
|
): Promise<CustomerDto> {
|
|
return this.customersService.create({
|
|
...dto,
|
|
userId,
|
|
});
|
|
}
|
|
|
|
@Post(':id/contacts')
|
|
@RequirePrivilege(CUSTOMER_PRIVILEGE_KEY, 'update')
|
|
@ApiOperation({ summary: 'Add a customer contact' })
|
|
@ApiOkResponse({ type: CustomerDto })
|
|
@ApiNotFoundResponse()
|
|
@ApiUnauthorizedResponse()
|
|
@ApiForbiddenResponse()
|
|
addContact(
|
|
@Param('id', ParseUUIDPipe) id: string,
|
|
@Body() dto: CreateCustomerContactDto,
|
|
@CurrentUser('id') userId: string,
|
|
): Promise<CustomerDto> {
|
|
return this.customersService.addContact(id, dto, userId);
|
|
}
|
|
|
|
@Patch(':id/contacts/:contactId')
|
|
@RequirePrivilege(CUSTOMER_PRIVILEGE_KEY, 'update')
|
|
@ApiOperation({ summary: 'Update a customer contact' })
|
|
@ApiOkResponse({ type: CustomerDto })
|
|
@ApiNotFoundResponse()
|
|
@ApiUnauthorizedResponse()
|
|
@ApiForbiddenResponse()
|
|
updateContact(
|
|
@Param('id', ParseUUIDPipe) id: string,
|
|
@Param('contactId', ParseUUIDPipe) contactId: string,
|
|
@Body() dto: UpdateCustomerContactDto,
|
|
@CurrentUser('id') userId: string,
|
|
): Promise<CustomerDto> {
|
|
return this.customersService.updateContact(id, contactId, {
|
|
...dto,
|
|
userId,
|
|
});
|
|
}
|
|
|
|
@Delete(':id/contacts/:contactId')
|
|
@HttpCode(204)
|
|
@RequirePrivilege(CUSTOMER_PRIVILEGE_KEY, 'update')
|
|
@ApiOperation({ summary: 'Delete a customer contact' })
|
|
@ApiNoContentResponse()
|
|
@ApiNotFoundResponse()
|
|
@ApiUnauthorizedResponse()
|
|
@ApiForbiddenResponse()
|
|
async deleteContact(
|
|
@Param('id', ParseUUIDPipe) id: string,
|
|
@Param('contactId', ParseUUIDPipe) contactId: string,
|
|
): Promise<void> {
|
|
await this.customersService.deleteContact(id, contactId);
|
|
}
|
|
|
|
@Patch(':id/status')
|
|
@RequirePrivilege(CUSTOMER_PRIVILEGE_KEY, 'update')
|
|
@ApiOperation({ summary: 'Update customer status' })
|
|
@ApiOkResponse({ type: CustomerDto })
|
|
@ApiNotFoundResponse()
|
|
@ApiUnauthorizedResponse()
|
|
@ApiForbiddenResponse()
|
|
updateStatus(
|
|
@Param('id', ParseUUIDPipe) id: string,
|
|
@Body() dto: UpdateCustomerStatusDto,
|
|
@CurrentUser('id') userId: string,
|
|
): Promise<CustomerDto> {
|
|
return this.customersService.updateStatus(id, dto.status, userId);
|
|
}
|
|
|
|
@Patch(':id')
|
|
@RequirePrivilege(CUSTOMER_PRIVILEGE_KEY, 'update')
|
|
@ApiOperation({ summary: 'Update customer (not status)' })
|
|
@ApiOkResponse({ type: CustomerDto })
|
|
@ApiNotFoundResponse()
|
|
@ApiUnauthorizedResponse()
|
|
@ApiForbiddenResponse()
|
|
update(
|
|
@Param('id', ParseUUIDPipe) id: string,
|
|
@Body() dto: UpdateCustomerDto,
|
|
@CurrentUser('id') userId: string,
|
|
): Promise<CustomerDto> {
|
|
return this.customersService.update(id, {
|
|
...dto,
|
|
userId,
|
|
});
|
|
}
|
|
|
|
@Delete(':id')
|
|
@HttpCode(204)
|
|
@RequirePrivilege(CUSTOMER_PRIVILEGE_KEY, 'delete')
|
|
@ApiOperation({ summary: 'Delete customer' })
|
|
@ApiNoContentResponse()
|
|
@ApiNotFoundResponse()
|
|
@ApiUnauthorizedResponse()
|
|
@ApiForbiddenResponse()
|
|
async delete(@Param('id', ParseUUIDPipe) id: string): Promise<void> {
|
|
await this.customersService.delete(id);
|
|
}
|
|
}
|