- Updated pagination-response and read-write-controllers documentation to include `orderBy` and `orderType` parameters for sorting results. - Introduced new `order-clause` module to handle ordering logic, including validation for order types and columns. - Enhanced `PaginationQueryDto` to support ordering fields in API requests. - Updated various repository and service classes to implement ordering in database queries. - Added unit tests for new ordering functionality and ensured existing tests cover the updated behavior. - Refactored related DTOs to include user and code relations for better data representation in responses.
359 lines
7.8 KiB
TypeScript
359 lines
7.8 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,
|
|
UserRelationDto,
|
|
} from '../../../../common/http/response';
|
|
import { CORE_STATUSES } from '../../../../common/value-objects/status/status';
|
|
import {
|
|
CONTACT_JOB_TITLE_MAX_LENGTH,
|
|
CONTACT_NAME_MAX_LENGTH,
|
|
CONTACT_NOTES_MAX_LENGTH,
|
|
CUSTOMER_ADDRESS_MAX_LENGTH,
|
|
CUSTOMER_CODE_MAX_LENGTH,
|
|
CUSTOMER_CODE_PATTERN,
|
|
CUSTOMER_NAME_MAX_LENGTH,
|
|
CUSTOMER_NAME_PATTERN,
|
|
CUSTOMER_NFC_ID_MAX_LENGTH,
|
|
} from '../customer-fields';
|
|
|
|
export class CreateCustomerContactDto {
|
|
@ApiProperty({ example: 'Jean Luc', maxLength: CONTACT_NAME_MAX_LENGTH })
|
|
@IsString()
|
|
@IsNotEmpty()
|
|
@MaxLength(CONTACT_NAME_MAX_LENGTH)
|
|
name!: string;
|
|
|
|
@ApiPropertyOptional({ example: 'Purchasing Manager' })
|
|
@IsOptional()
|
|
@IsString()
|
|
@MaxLength(CONTACT_JOB_TITLE_MAX_LENGTH)
|
|
jobTitle?: string;
|
|
|
|
@ApiPropertyOptional({ example: '+6281234567890' })
|
|
@IsOptional()
|
|
@IsString()
|
|
@IsNotEmpty()
|
|
phone?: string;
|
|
|
|
@ApiPropertyOptional({ example: '+6281234567891' })
|
|
@IsOptional()
|
|
@IsString()
|
|
@IsNotEmpty()
|
|
mobilePhone?: string;
|
|
|
|
@ApiPropertyOptional({ example: 'Call after 9am' })
|
|
@IsOptional()
|
|
@IsString()
|
|
@MaxLength(CONTACT_NOTES_MAX_LENGTH)
|
|
notes?: string;
|
|
}
|
|
|
|
export class UpdateCustomerContactDto {
|
|
@ApiPropertyOptional({ example: 'Jean Luc' })
|
|
@IsOptional()
|
|
@IsString()
|
|
@IsNotEmpty()
|
|
@MaxLength(CONTACT_NAME_MAX_LENGTH)
|
|
name?: string;
|
|
|
|
@ApiPropertyOptional({ example: 'Purchasing Manager', nullable: true })
|
|
@IsOptional()
|
|
@IsString()
|
|
@MaxLength(CONTACT_JOB_TITLE_MAX_LENGTH)
|
|
jobTitle?: string | null;
|
|
|
|
@ApiPropertyOptional({ example: '+6281234567890', nullable: true })
|
|
@IsOptional()
|
|
@IsString()
|
|
phone?: string | null;
|
|
|
|
@ApiPropertyOptional({ example: '+6281234567891', nullable: true })
|
|
@IsOptional()
|
|
@IsString()
|
|
mobilePhone?: string | null;
|
|
|
|
@ApiPropertyOptional({ example: 'Call after 9am', nullable: true })
|
|
@IsOptional()
|
|
@IsString()
|
|
@MaxLength(CONTACT_NOTES_MAX_LENGTH)
|
|
notes?: string | null;
|
|
}
|
|
|
|
export class CustomerContactDto {
|
|
@ApiProperty({ format: 'uuid' })
|
|
id!: string;
|
|
|
|
@ApiProperty({ format: 'uuid' })
|
|
customerId!: string;
|
|
|
|
@ApiProperty()
|
|
name!: string;
|
|
|
|
@ApiPropertyOptional({ nullable: true })
|
|
jobTitle!: string | null;
|
|
|
|
@ApiPropertyOptional({ nullable: true })
|
|
phone!: string | null;
|
|
|
|
@ApiPropertyOptional({ nullable: true })
|
|
mobilePhone!: string | null;
|
|
|
|
@ApiPropertyOptional({ nullable: true })
|
|
notes!: string | null;
|
|
}
|
|
|
|
export class CreateCustomerDto {
|
|
@ApiProperty({ example: 'CUST_01', maxLength: CUSTOMER_CODE_MAX_LENGTH })
|
|
@IsString()
|
|
@IsNotEmpty()
|
|
@MaxLength(CUSTOMER_CODE_MAX_LENGTH)
|
|
@Matches(CUSTOMER_CODE_PATTERN, {
|
|
message: 'code must contain only letters, numbers, and underscores',
|
|
})
|
|
code!: string;
|
|
|
|
@ApiProperty({ example: 'Acme Corp', maxLength: CUSTOMER_NAME_MAX_LENGTH })
|
|
@IsString()
|
|
@IsNotEmpty()
|
|
@MaxLength(CUSTOMER_NAME_MAX_LENGTH)
|
|
@Matches(CUSTOMER_NAME_PATTERN, {
|
|
message: 'name must contain only letters and spaces',
|
|
})
|
|
name!: string;
|
|
|
|
@ApiProperty({ example: '+6281234567890' })
|
|
@IsString()
|
|
@IsNotEmpty()
|
|
phone!: string;
|
|
|
|
@ApiProperty({ example: 'Jl Sudirman No 1' })
|
|
@IsString()
|
|
@IsNotEmpty()
|
|
@MaxLength(CUSTOMER_ADDRESS_MAX_LENGTH)
|
|
address!: string;
|
|
|
|
@ApiPropertyOptional({ example: -6.2 })
|
|
@IsOptional()
|
|
@Type(() => Number)
|
|
@IsNumber()
|
|
@Min(-90)
|
|
@Max(90)
|
|
latitude?: number;
|
|
|
|
@ApiPropertyOptional({ example: 106.8 })
|
|
@IsOptional()
|
|
@Type(() => Number)
|
|
@IsNumber()
|
|
@Min(-180)
|
|
@Max(180)
|
|
longitude?: number;
|
|
|
|
@ApiPropertyOptional({ example: 'NFC-001' })
|
|
@IsOptional()
|
|
@IsString()
|
|
@IsNotEmpty()
|
|
@MaxLength(CUSTOMER_NFC_ID_MAX_LENGTH)
|
|
nfcId?: string;
|
|
|
|
@ApiPropertyOptional({ enum: CORE_STATUSES })
|
|
@IsOptional()
|
|
@IsIn([...CORE_STATUSES])
|
|
status?: string;
|
|
|
|
@ApiPropertyOptional({ type: [CreateCustomerContactDto] })
|
|
@IsOptional()
|
|
@IsArray()
|
|
@ValidateNested({ each: true })
|
|
@Type(() => CreateCustomerContactDto)
|
|
contacts?: CreateCustomerContactDto[];
|
|
}
|
|
|
|
export class UpdateCustomerDto {
|
|
@ApiPropertyOptional({ example: 'CUST_01' })
|
|
@IsOptional()
|
|
@IsString()
|
|
@IsNotEmpty()
|
|
@MaxLength(CUSTOMER_CODE_MAX_LENGTH)
|
|
@Matches(CUSTOMER_CODE_PATTERN, {
|
|
message: 'code must contain only letters, numbers, and underscores',
|
|
})
|
|
code?: string;
|
|
|
|
@ApiPropertyOptional({ example: 'Acme Corp' })
|
|
@IsOptional()
|
|
@IsString()
|
|
@IsNotEmpty()
|
|
@MaxLength(CUSTOMER_NAME_MAX_LENGTH)
|
|
@Matches(CUSTOMER_NAME_PATTERN, {
|
|
message: 'name must contain only letters and spaces',
|
|
})
|
|
name?: string;
|
|
|
|
@ApiPropertyOptional({ example: '+6281234567890' })
|
|
@IsOptional()
|
|
@IsString()
|
|
@IsNotEmpty()
|
|
phone?: string;
|
|
|
|
@ApiPropertyOptional({ example: 'Jl Sudirman No 1' })
|
|
@IsOptional()
|
|
@IsString()
|
|
@IsNotEmpty()
|
|
@MaxLength(CUSTOMER_ADDRESS_MAX_LENGTH)
|
|
address?: string;
|
|
|
|
@ApiPropertyOptional({ example: -6.2, nullable: true })
|
|
@IsOptional()
|
|
@Type(() => Number)
|
|
@IsNumber()
|
|
@Min(-90)
|
|
@Max(90)
|
|
latitude?: number | null;
|
|
|
|
@ApiPropertyOptional({ example: 106.8, nullable: true })
|
|
@IsOptional()
|
|
@Type(() => Number)
|
|
@IsNumber()
|
|
@Min(-180)
|
|
@Max(180)
|
|
longitude?: number | null;
|
|
|
|
@ApiPropertyOptional({ example: 'NFC-001', nullable: true })
|
|
@IsOptional()
|
|
@IsString()
|
|
@MaxLength(CUSTOMER_NFC_ID_MAX_LENGTH)
|
|
nfcId?: string | null;
|
|
|
|
@ApiPropertyOptional({ type: [CreateCustomerContactDto] })
|
|
@IsOptional()
|
|
@IsArray()
|
|
@ValidateNested({ each: true })
|
|
@Type(() => CreateCustomerContactDto)
|
|
contacts?: CreateCustomerContactDto[];
|
|
}
|
|
|
|
export class UpdateCustomerStatusDto {
|
|
@ApiProperty({ enum: CORE_STATUSES })
|
|
@IsIn([...CORE_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: CORE_STATUSES })
|
|
@IsIn([...CORE_STATUSES])
|
|
status!: string;
|
|
}
|
|
|
|
export class ListCustomersQueryDto extends PaginationQueryDto {
|
|
@ApiPropertyOptional()
|
|
@IsOptional()
|
|
@IsString()
|
|
code?: string;
|
|
|
|
@ApiPropertyOptional()
|
|
@IsOptional()
|
|
@IsString()
|
|
name?: string;
|
|
|
|
@ApiPropertyOptional()
|
|
@IsOptional()
|
|
@IsString()
|
|
phone?: string;
|
|
|
|
@ApiPropertyOptional()
|
|
@IsOptional()
|
|
@IsString()
|
|
address?: string;
|
|
|
|
@ApiPropertyOptional()
|
|
@IsOptional()
|
|
@IsString()
|
|
nfcId?: string;
|
|
|
|
@ApiPropertyOptional({ enum: CORE_STATUSES })
|
|
@IsOptional()
|
|
@IsIn([...CORE_STATUSES])
|
|
status?: string;
|
|
|
|
@ApiPropertyOptional({
|
|
description: 'Case-insensitive match on code, name, or address',
|
|
})
|
|
@IsOptional()
|
|
@IsString()
|
|
search?: string;
|
|
}
|
|
|
|
export class CustomerDto {
|
|
@ApiProperty({ format: 'uuid' })
|
|
id!: string;
|
|
|
|
@ApiProperty()
|
|
code!: string;
|
|
|
|
@ApiProperty()
|
|
name!: string;
|
|
|
|
@ApiProperty({ example: '+6281234567890' })
|
|
phone!: string;
|
|
|
|
@ApiProperty()
|
|
address!: string;
|
|
|
|
@ApiPropertyOptional({ nullable: true })
|
|
latitude!: number | null;
|
|
|
|
@ApiPropertyOptional({ nullable: true })
|
|
longitude!: number | null;
|
|
|
|
@ApiPropertyOptional({ nullable: true })
|
|
nfcId!: string | null;
|
|
|
|
@ApiPropertyOptional({ type: [CustomerContactDto] })
|
|
contacts?: CustomerContactDto[];
|
|
|
|
@ApiProperty({ enum: CORE_STATUSES })
|
|
status!: string;
|
|
|
|
@ApiProperty({ description: 'Unix ms' })
|
|
createdAt!: number;
|
|
|
|
@ApiProperty({ description: 'Unix ms' })
|
|
updatedAt!: number;
|
|
|
|
@ApiProperty({ type: UserRelationDto })
|
|
createdBy!: UserRelationDto;
|
|
|
|
@ApiProperty({ type: UserRelationDto })
|
|
updatedBy!: UserRelationDto;
|
|
}
|