Add products and sales management modules with database schema and validation
- Introduced `ProductsModule` to manage product data, including read and write controllers. - Created database migrations for the `products`, `sales_requests`, `sales_orders`, `sales_invoices`, and related tables, including constraints and unique indexes. - Implemented validation for product fields such as code, name, unit, and brand with corresponding utility functions. - Developed service and repository layers for handling product and sales data operations. - Added unit tests for the products and sales services, repositories, and controllers to ensure functionality and correctness. - Updated application module to include the new `ProductsModule` and related sales modules for better organization.
This commit is contained in:
@@ -0,0 +1,199 @@
|
||||
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 '../shared/sales-fields';
|
||||
import { SALES_INVOICE_PRIVILEGE_KEY } from './sales-invoices-read.controller';
|
||||
import { SalesInvoicesService } from './sales-invoices.service';
|
||||
import {
|
||||
BulkIdsDto,
|
||||
BulkStatusDto,
|
||||
CreateSalesInvoiceDto,
|
||||
SalesInvoiceDto,
|
||||
UpdateSalesInvoiceDto,
|
||||
UpdateSalesInvoiceStatusDto,
|
||||
} from './dto/sales-invoice.dto';
|
||||
|
||||
@ApiTags('sales-invoices')
|
||||
@ApiBearerAuth(BEARER_AUTH_NAME)
|
||||
@Controller('sales-invoices')
|
||||
export class SalesInvoicesWriteController {
|
||||
constructor(private readonly salesInvoicesService: SalesInvoicesService) {}
|
||||
|
||||
@Post('import')
|
||||
@RequirePrivilege(SALES_INVOICE_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 sales invoices 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.salesInvoicesService.importCsv(csv, userId);
|
||||
}
|
||||
|
||||
@Post('bulk-delete')
|
||||
@HttpCode(200)
|
||||
@RequirePrivilege(SALES_INVOICE_PRIVILEGE_KEY, 'delete')
|
||||
@ApiOperation({ summary: 'Bulk delete sales invoices' })
|
||||
@ApiOkResponse({
|
||||
schema: { properties: { deleted: { type: 'number' } } },
|
||||
})
|
||||
@ApiUnauthorizedResponse()
|
||||
@ApiForbiddenResponse()
|
||||
bulkDelete(@Body() dto: BulkIdsDto): Promise<{ deleted: number }> {
|
||||
return this.salesInvoicesService.bulkDelete(dto.ids);
|
||||
}
|
||||
|
||||
@Post('bulk-status')
|
||||
@HttpCode(200)
|
||||
@RequirePrivilege(SALES_INVOICE_PRIVILEGE_KEY, 'update')
|
||||
@ApiOperation({ summary: 'Bulk update sales invoice status' })
|
||||
@ApiOkResponse({
|
||||
schema: { properties: { updated: { type: 'number' } } },
|
||||
})
|
||||
@ApiUnauthorizedResponse()
|
||||
@ApiForbiddenResponse()
|
||||
bulkStatus(
|
||||
@Body() dto: BulkStatusDto,
|
||||
@CurrentUser('id') userId: string,
|
||||
): Promise<{ updated: number }> {
|
||||
return this.salesInvoicesService.bulkUpdateStatus(
|
||||
dto.ids,
|
||||
dto.status,
|
||||
userId,
|
||||
);
|
||||
}
|
||||
|
||||
@Post()
|
||||
@RequirePrivilege(SALES_INVOICE_PRIVILEGE_KEY, 'create')
|
||||
@ApiOperation({ summary: 'Create sales invoice' })
|
||||
@ApiCreatedResponse({ type: SalesInvoiceDto })
|
||||
@ApiUnauthorizedResponse()
|
||||
@ApiForbiddenResponse()
|
||||
create(
|
||||
@Body() dto: CreateSalesInvoiceDto,
|
||||
@CurrentUser('id') userId: string,
|
||||
): Promise<SalesInvoiceDto> {
|
||||
return this.salesInvoicesService.create({
|
||||
code: dto.code,
|
||||
salesOrderId: dto.salesOrderId,
|
||||
packingSlipId: dto.packingSlipId,
|
||||
date: dto.date,
|
||||
salesPersonId: dto.salesPersonId,
|
||||
branchId: dto.branchId,
|
||||
divisionId: dto.divisionId,
|
||||
customerId: dto.customerId,
|
||||
notes: dto.notes,
|
||||
products: dto.products,
|
||||
status: dto.status,
|
||||
userId,
|
||||
});
|
||||
}
|
||||
|
||||
@Patch(':id/status')
|
||||
@RequirePrivilege(SALES_INVOICE_PRIVILEGE_KEY, 'update')
|
||||
@ApiOperation({ summary: 'Update sales invoice status' })
|
||||
@ApiOkResponse({ type: SalesInvoiceDto })
|
||||
@ApiNotFoundResponse()
|
||||
@ApiUnauthorizedResponse()
|
||||
@ApiForbiddenResponse()
|
||||
updateStatus(
|
||||
@Param('id', ParseUUIDPipe) id: string,
|
||||
@Body() dto: UpdateSalesInvoiceStatusDto,
|
||||
@CurrentUser('id') userId: string,
|
||||
): Promise<SalesInvoiceDto> {
|
||||
return this.salesInvoicesService.updateStatus(id, dto.status, userId);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
@RequirePrivilege(SALES_INVOICE_PRIVILEGE_KEY, 'update')
|
||||
@ApiOperation({ summary: 'Update sales invoice (not status)' })
|
||||
@ApiOkResponse({ type: SalesInvoiceDto })
|
||||
@ApiNotFoundResponse()
|
||||
@ApiUnauthorizedResponse()
|
||||
@ApiForbiddenResponse()
|
||||
update(
|
||||
@Param('id', ParseUUIDPipe) id: string,
|
||||
@Body() dto: UpdateSalesInvoiceDto,
|
||||
@CurrentUser('id') userId: string,
|
||||
): Promise<SalesInvoiceDto> {
|
||||
return this.salesInvoicesService.update(id, {
|
||||
code: dto.code,
|
||||
salesOrderId: dto.salesOrderId,
|
||||
packingSlipId: dto.packingSlipId,
|
||||
date: dto.date,
|
||||
salesPersonId: dto.salesPersonId,
|
||||
branchId: dto.branchId,
|
||||
divisionId: dto.divisionId,
|
||||
customerId: dto.customerId,
|
||||
notes: dto.notes,
|
||||
products: dto.products,
|
||||
userId,
|
||||
});
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@HttpCode(204)
|
||||
@RequirePrivilege(SALES_INVOICE_PRIVILEGE_KEY, 'delete')
|
||||
@ApiOperation({ summary: 'Delete sales invoice' })
|
||||
@ApiNoContentResponse()
|
||||
@ApiNotFoundResponse()
|
||||
@ApiUnauthorizedResponse()
|
||||
@ApiForbiddenResponse()
|
||||
async delete(@Param('id', ParseUUIDPipe) id: string): Promise<void> {
|
||||
await this.salesInvoicesService.delete(id);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user