- Introduced new columns `address`, `latitude`, and `longitude` in the `sales_invoices` table to store location details. - Updated the `SalesInvoicesService` to handle the new fields, including validation for address format and geographical coordinates. - Enhanced the `SalesInvoiceDto` and related data transfer objects to include the new fields for API requests and responses. - Added unit and e2e tests to ensure proper handling of the new fields and validate their integration within the sales invoice workflow. - Created a new migration script to apply the database schema changes for the sales invoices.
265 lines
5.6 KiB
TypeScript
265 lines
5.6 KiB
TypeScript
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
|
import { Transform, Type } from 'class-transformer';
|
|
import {
|
|
ArrayNotEmpty,
|
|
IsArray,
|
|
IsIn,
|
|
IsNotEmpty,
|
|
IsNumber,
|
|
IsOptional,
|
|
IsString,
|
|
IsUUID,
|
|
Matches,
|
|
Max,
|
|
MaxLength,
|
|
Min,
|
|
ValidateNested,
|
|
} from 'class-validator';
|
|
import {
|
|
CodeRelationDto,
|
|
DefaultRelationDto,
|
|
PaginationQueryDto,
|
|
parseQueryIdList,
|
|
UserRelationDto,
|
|
} from '../../../../common/http/response';
|
|
import {
|
|
DOCUMENT_ADDRESS_MAX_LENGTH,
|
|
DOCUMENT_CODE_MAX_LENGTH,
|
|
DOCUMENT_CODE_PATTERN,
|
|
DOCUMENT_NOTES_MAX_LENGTH,
|
|
PACKING_SLIP_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 CreatePackingSlipDto {
|
|
@ApiPropertyOptional({ example: 'PS-20260824-0001' })
|
|
@IsOptional()
|
|
@IsString()
|
|
@MaxLength(DOCUMENT_CODE_MAX_LENGTH)
|
|
@Matches(DOCUMENT_CODE_PATTERN)
|
|
code?: string;
|
|
|
|
@ApiPropertyOptional({ format: 'uuid' })
|
|
@IsOptional()
|
|
@IsUUID('4')
|
|
salesOrderId?: string;
|
|
|
|
@ApiPropertyOptional()
|
|
@IsOptional()
|
|
@IsString()
|
|
salesOrderNumber?: string;
|
|
|
|
@ApiPropertyOptional({ example: '2026-08-24T10:00:00+07:00' })
|
|
@IsOptional()
|
|
@IsString()
|
|
date?: string;
|
|
|
|
@ApiPropertyOptional({ format: 'uuid' })
|
|
@IsOptional()
|
|
@IsUUID('4')
|
|
customerId?: string;
|
|
|
|
@ApiPropertyOptional({ example: 'Jl Sudirman 1' })
|
|
@IsOptional()
|
|
@IsString()
|
|
@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;
|
|
|
|
@ApiPropertyOptional({ type: [SalesLineDto] })
|
|
@IsOptional()
|
|
@IsArray()
|
|
@ValidateNested({ each: true })
|
|
@Type(() => SalesLineDto)
|
|
products?: SalesLineDto[];
|
|
|
|
@ApiPropertyOptional({ enum: PACKING_SLIP_STATUSES })
|
|
@IsOptional()
|
|
@IsIn([...PACKING_SLIP_STATUSES])
|
|
status?: string;
|
|
}
|
|
|
|
export class UpdatePackingSlipDto {
|
|
@ApiPropertyOptional()
|
|
@IsOptional()
|
|
@IsString()
|
|
@MaxLength(DOCUMENT_CODE_MAX_LENGTH)
|
|
@Matches(DOCUMENT_CODE_PATTERN)
|
|
code?: string;
|
|
|
|
@ApiPropertyOptional({ format: 'uuid', nullable: true })
|
|
@IsOptional()
|
|
@IsUUID('4')
|
|
salesOrderId?: string | null;
|
|
|
|
@ApiPropertyOptional()
|
|
@IsOptional()
|
|
@IsString()
|
|
date?: 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[];
|
|
}
|
|
|
|
export class UpdatePackingSlipStatusDto {
|
|
@ApiProperty({ enum: PACKING_SLIP_STATUSES })
|
|
@IsIn([...PACKING_SLIP_STATUSES])
|
|
status!: string;
|
|
|
|
@ApiPropertyOptional({ type: [SalesLineDto] })
|
|
@IsOptional()
|
|
@IsArray()
|
|
@ValidateNested({ each: true })
|
|
@Type(() => SalesLineDto)
|
|
products?: SalesLineDto[];
|
|
}
|
|
|
|
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: PACKING_SLIP_STATUSES })
|
|
@IsIn([...PACKING_SLIP_STATUSES])
|
|
status!: string;
|
|
}
|
|
|
|
export class ListPackingSlipsQueryDto extends PaginationQueryDto {
|
|
@ApiPropertyOptional()
|
|
@IsOptional()
|
|
@IsString()
|
|
code?: string;
|
|
|
|
@ApiPropertyOptional({ enum: PACKING_SLIP_STATUSES })
|
|
@IsOptional()
|
|
@IsIn([...PACKING_SLIP_STATUSES])
|
|
status?: string;
|
|
|
|
@ApiPropertyOptional({ format: 'uuid' })
|
|
@IsOptional()
|
|
@IsUUID('4')
|
|
customerId?: string;
|
|
|
|
@ApiPropertyOptional({ type: [String], format: 'uuid' })
|
|
@IsOptional()
|
|
@Transform(({ value }) => parseQueryIdList(value))
|
|
@IsArray()
|
|
@IsUUID('4', { each: true })
|
|
customerIds?: string[];
|
|
|
|
@ApiPropertyOptional({ format: 'uuid' })
|
|
@IsOptional()
|
|
@IsUUID('4')
|
|
salesOrderId?: string;
|
|
|
|
@ApiPropertyOptional()
|
|
@IsOptional()
|
|
@IsString()
|
|
search?: string;
|
|
}
|
|
|
|
export class PackingSlipDto {
|
|
@ApiProperty({ format: 'uuid' })
|
|
id!: string;
|
|
@ApiProperty()
|
|
code!: string;
|
|
@ApiPropertyOptional({ type: CodeRelationDto, nullable: true })
|
|
salesOrder!: CodeRelationDto | null;
|
|
@ApiProperty()
|
|
date!: number;
|
|
@ApiProperty({ type: DefaultRelationDto, nullable: true })
|
|
customer!: DefaultRelationDto | null;
|
|
@ApiProperty()
|
|
address!: string;
|
|
@ApiPropertyOptional({ nullable: true })
|
|
latitude!: number | null;
|
|
@ApiPropertyOptional({ nullable: true })
|
|
longitude!: number | null;
|
|
@ApiPropertyOptional({ nullable: true })
|
|
notes!: string | null;
|
|
@ApiProperty({ enum: PACKING_SLIP_STATUSES })
|
|
status!: string;
|
|
@ApiProperty()
|
|
createdAt!: number;
|
|
@ApiProperty()
|
|
updatedAt!: number;
|
|
@ApiProperty({ type: UserRelationDto })
|
|
createdBy!: UserRelationDto;
|
|
@ApiProperty({ type: UserRelationDto })
|
|
updatedBy!: UserRelationDto;
|
|
}
|