Add customers management module with database schema and validation
- 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.
This commit is contained in:
@@ -0,0 +1,227 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user