- 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.
299 lines
6.2 KiB
TypeScript
299 lines
6.2 KiB
TypeScript
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
|
import { Type } from 'class-transformer';
|
|
import {
|
|
ArrayNotEmpty,
|
|
IsArray,
|
|
IsIn,
|
|
IsNotEmpty,
|
|
IsNumber,
|
|
IsOptional,
|
|
IsString,
|
|
IsUUID,
|
|
Matches,
|
|
Max,
|
|
MaxLength,
|
|
Min,
|
|
ValidateNested,
|
|
} from 'class-validator';
|
|
import { PaginationQueryDto } from '../../../../common/http/response';
|
|
import {
|
|
DOCUMENT_ADDRESS_MAX_LENGTH,
|
|
DOCUMENT_CODE_MAX_LENGTH,
|
|
DOCUMENT_CODE_PATTERN,
|
|
DOCUMENT_NOTES_MAX_LENGTH,
|
|
IMAGE_DESCRIPTION_MAX_LENGTH,
|
|
IMAGE_URL_MAX_LENGTH,
|
|
SALES_REQUEST_STATUSES,
|
|
} from '../../shared/sales-fields';
|
|
|
|
export class SalesLineDto {
|
|
@ApiProperty({ format: 'uuid' })
|
|
@IsUUID('4')
|
|
productId!: string;
|
|
|
|
@ApiProperty({ example: '2.0000' })
|
|
@IsString()
|
|
@IsNotEmpty()
|
|
quantity!: string;
|
|
|
|
@ApiPropertyOptional({ example: '12500.0000' })
|
|
@IsOptional()
|
|
@IsString()
|
|
price?: string;
|
|
}
|
|
|
|
export class SalesImageDto {
|
|
@ApiProperty({ example: 'https://cdn.example.com/a.png' })
|
|
@IsString()
|
|
@IsNotEmpty()
|
|
@MaxLength(IMAGE_URL_MAX_LENGTH)
|
|
url!: string;
|
|
|
|
@ApiPropertyOptional()
|
|
@IsOptional()
|
|
@IsString()
|
|
@MaxLength(IMAGE_DESCRIPTION_MAX_LENGTH)
|
|
description?: string;
|
|
}
|
|
|
|
export class CreateSalesRequestDto {
|
|
@ApiPropertyOptional({ example: 'SR-20260824-0001' })
|
|
@IsOptional()
|
|
@IsString()
|
|
@MaxLength(DOCUMENT_CODE_MAX_LENGTH)
|
|
@Matches(DOCUMENT_CODE_PATTERN)
|
|
code?: string;
|
|
|
|
@ApiProperty({ example: '2026-08-24T10:00:00+07:00' })
|
|
@IsString()
|
|
@IsNotEmpty()
|
|
date!: string;
|
|
|
|
@ApiProperty({ format: 'uuid' })
|
|
@IsUUID('4')
|
|
salesPersonId!: string;
|
|
|
|
@ApiProperty({ format: 'uuid' })
|
|
@IsUUID('4')
|
|
branchId!: string;
|
|
|
|
@ApiProperty({ format: 'uuid' })
|
|
@IsUUID('4')
|
|
divisionId!: string;
|
|
|
|
@ApiProperty({ format: 'uuid' })
|
|
@IsUUID('4')
|
|
customerId!: string;
|
|
|
|
@ApiProperty({ example: 'Jl Sudirman 1' })
|
|
@IsString()
|
|
@IsNotEmpty()
|
|
@MaxLength(DOCUMENT_ADDRESS_MAX_LENGTH)
|
|
address!: string;
|
|
|
|
@ApiPropertyOptional()
|
|
@IsOptional()
|
|
@IsNumber()
|
|
@Min(-90)
|
|
@Max(90)
|
|
latitude?: number;
|
|
|
|
@ApiPropertyOptional()
|
|
@IsOptional()
|
|
@IsNumber()
|
|
@Min(-180)
|
|
@Max(180)
|
|
longitude?: number;
|
|
|
|
@ApiPropertyOptional()
|
|
@IsOptional()
|
|
@IsString()
|
|
@MaxLength(DOCUMENT_NOTES_MAX_LENGTH)
|
|
notes?: string;
|
|
|
|
@ApiProperty({ type: [SalesLineDto] })
|
|
@IsArray()
|
|
@ValidateNested({ each: true })
|
|
@Type(() => SalesLineDto)
|
|
products!: SalesLineDto[];
|
|
|
|
@ApiPropertyOptional({ type: [SalesImageDto] })
|
|
@IsOptional()
|
|
@IsArray()
|
|
@ValidateNested({ each: true })
|
|
@Type(() => SalesImageDto)
|
|
images?: SalesImageDto[];
|
|
|
|
@ApiPropertyOptional({ enum: SALES_REQUEST_STATUSES })
|
|
@IsOptional()
|
|
@IsIn([...SALES_REQUEST_STATUSES])
|
|
status?: string;
|
|
}
|
|
|
|
export class UpdateSalesRequestDto {
|
|
@ApiPropertyOptional()
|
|
@IsOptional()
|
|
@IsString()
|
|
@MaxLength(DOCUMENT_CODE_MAX_LENGTH)
|
|
@Matches(DOCUMENT_CODE_PATTERN)
|
|
code?: string;
|
|
|
|
@ApiPropertyOptional()
|
|
@IsOptional()
|
|
@IsString()
|
|
date?: string;
|
|
|
|
@ApiPropertyOptional({ format: 'uuid' })
|
|
@IsOptional()
|
|
@IsUUID('4')
|
|
salesPersonId?: string;
|
|
|
|
@ApiPropertyOptional({ format: 'uuid' })
|
|
@IsOptional()
|
|
@IsUUID('4')
|
|
branchId?: string;
|
|
|
|
@ApiPropertyOptional({ format: 'uuid' })
|
|
@IsOptional()
|
|
@IsUUID('4')
|
|
divisionId?: string;
|
|
|
|
@ApiPropertyOptional({ format: 'uuid' })
|
|
@IsOptional()
|
|
@IsUUID('4')
|
|
customerId?: string;
|
|
|
|
@ApiPropertyOptional()
|
|
@IsOptional()
|
|
@IsString()
|
|
@MaxLength(DOCUMENT_ADDRESS_MAX_LENGTH)
|
|
address?: string;
|
|
|
|
@ApiPropertyOptional({ nullable: true })
|
|
@IsOptional()
|
|
@IsNumber()
|
|
latitude?: number | null;
|
|
|
|
@ApiPropertyOptional({ nullable: true })
|
|
@IsOptional()
|
|
@IsNumber()
|
|
longitude?: number | null;
|
|
|
|
@ApiPropertyOptional({ nullable: true })
|
|
@IsOptional()
|
|
@IsString()
|
|
notes?: string | null;
|
|
|
|
@ApiPropertyOptional({ type: [SalesLineDto] })
|
|
@IsOptional()
|
|
@IsArray()
|
|
@ValidateNested({ each: true })
|
|
@Type(() => SalesLineDto)
|
|
products?: SalesLineDto[];
|
|
|
|
@ApiPropertyOptional({ type: [SalesImageDto] })
|
|
@IsOptional()
|
|
@IsArray()
|
|
@ValidateNested({ each: true })
|
|
@Type(() => SalesImageDto)
|
|
images?: SalesImageDto[];
|
|
}
|
|
|
|
export class UpdateSalesRequestStatusDto {
|
|
@ApiProperty({ enum: SALES_REQUEST_STATUSES })
|
|
@IsIn([...SALES_REQUEST_STATUSES])
|
|
status!: string;
|
|
}
|
|
|
|
export class BulkIdsDto {
|
|
@ApiProperty({ type: [String], format: 'uuid' })
|
|
@IsArray()
|
|
@ArrayNotEmpty()
|
|
@IsUUID('4', { each: true })
|
|
ids!: string[];
|
|
}
|
|
|
|
export class BulkStatusDto {
|
|
@ApiProperty({ type: [String], format: 'uuid' })
|
|
@IsArray()
|
|
@ArrayNotEmpty()
|
|
@IsUUID('4', { each: true })
|
|
ids!: string[];
|
|
|
|
@ApiProperty({ enum: SALES_REQUEST_STATUSES })
|
|
@IsIn([...SALES_REQUEST_STATUSES])
|
|
status!: string;
|
|
}
|
|
|
|
export class ListSalesRequestsQueryDto extends PaginationQueryDto {
|
|
@ApiPropertyOptional()
|
|
@IsOptional()
|
|
@IsString()
|
|
code?: string;
|
|
|
|
@ApiPropertyOptional({ enum: SALES_REQUEST_STATUSES })
|
|
@IsOptional()
|
|
@IsIn([...SALES_REQUEST_STATUSES])
|
|
status?: string;
|
|
|
|
@ApiPropertyOptional({ format: 'uuid' })
|
|
@IsOptional()
|
|
@IsUUID('4')
|
|
customerId?: string;
|
|
|
|
@ApiPropertyOptional({ format: 'uuid' })
|
|
@IsOptional()
|
|
@IsUUID('4')
|
|
salesPersonId?: string;
|
|
|
|
@ApiPropertyOptional({ format: 'uuid' })
|
|
@IsOptional()
|
|
@IsUUID('4')
|
|
branchId?: string;
|
|
|
|
@ApiPropertyOptional({ format: 'uuid' })
|
|
@IsOptional()
|
|
@IsUUID('4')
|
|
divisionId?: string;
|
|
|
|
@ApiPropertyOptional()
|
|
@IsOptional()
|
|
@IsString()
|
|
search?: string;
|
|
}
|
|
|
|
export class SalesRequestDto {
|
|
@ApiProperty({ format: 'uuid' })
|
|
id!: string;
|
|
@ApiProperty()
|
|
code!: string;
|
|
@ApiProperty()
|
|
date!: number;
|
|
@ApiProperty({ format: 'uuid' })
|
|
salesPersonId!: string;
|
|
@ApiProperty({ format: 'uuid' })
|
|
branchId!: string;
|
|
@ApiProperty({ format: 'uuid' })
|
|
divisionId!: string;
|
|
@ApiProperty({ format: 'uuid' })
|
|
customerId!: string;
|
|
@ApiProperty()
|
|
address!: string;
|
|
@ApiPropertyOptional({ nullable: true })
|
|
latitude!: number | null;
|
|
@ApiPropertyOptional({ nullable: true })
|
|
longitude!: number | null;
|
|
@ApiPropertyOptional({ nullable: true })
|
|
notes!: string | null;
|
|
@ApiProperty({ enum: SALES_REQUEST_STATUSES })
|
|
status!: string;
|
|
@ApiProperty()
|
|
createdAt!: number;
|
|
@ApiProperty()
|
|
updatedAt!: number;
|
|
@ApiProperty({ format: 'uuid' })
|
|
createdBy!: string;
|
|
@ApiProperty({ format: 'uuid' })
|
|
updatedBy!: string;
|
|
}
|