Add attendance and visit management features with database schema updates
- Introduced new `attendances` and `visits` tables to manage employee attendance and customer visits, including relevant fields for check-in and check-out details. - Updated `company_settings` to include a `check_in_radius_meters` column for attendance validation. - Implemented foreign key constraints to ensure data integrity between `attendances`, `visits`, `employees`, `branches`, and other related entities. - Created new services and controllers for handling attendance and visit operations, including check-in, check-out, and bulk actions. - Enhanced DTOs for attendance and visit data transfer, including validation for input data. - Added unit and integration tests to validate the new functionalities and ensure proper handling of attendance and visit records. - Created migration scripts to apply the necessary database schema changes for the new features.
This commit is contained in:
@@ -0,0 +1,230 @@
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import {
|
||||
ArrayNotEmpty,
|
||||
IsArray,
|
||||
IsIn,
|
||||
IsLatitude,
|
||||
IsLongitude,
|
||||
IsNumber,
|
||||
IsOptional,
|
||||
IsString,
|
||||
IsUrl,
|
||||
IsUUID,
|
||||
ValidateIf,
|
||||
} from 'class-validator';
|
||||
import { PaginationQueryDto } from '../../../../common/http/response';
|
||||
import { CORE_STATUSES } from '../../../../common/value-objects/status/status';
|
||||
import { CHECK_IN_METHODS } from '../../shared/check-in-verification';
|
||||
|
||||
export class VisitCheckInDto {
|
||||
@ApiProperty({ format: 'uuid' })
|
||||
@IsUUID('4')
|
||||
customerId!: string;
|
||||
|
||||
@ApiPropertyOptional({ format: 'uuid' })
|
||||
@IsOptional()
|
||||
@IsUUID('4')
|
||||
planId?: string;
|
||||
|
||||
@ApiProperty({ enum: CHECK_IN_METHODS })
|
||||
@IsIn([...CHECK_IN_METHODS])
|
||||
method!: string;
|
||||
|
||||
@ApiPropertyOptional()
|
||||
@ValidateIf((dto: VisitCheckInDto) => dto.method === 'nfc')
|
||||
@IsString()
|
||||
nfcId?: string;
|
||||
|
||||
@ApiPropertyOptional()
|
||||
@ValidateIf((dto: VisitCheckInDto) => dto.method === 'qr')
|
||||
@IsString()
|
||||
qrCode?: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsLatitude()
|
||||
latitude!: number;
|
||||
|
||||
@ApiProperty()
|
||||
@IsLongitude()
|
||||
longitude!: number;
|
||||
|
||||
@ApiPropertyOptional()
|
||||
@IsOptional()
|
||||
@IsUrl({ require_protocol: true, protocols: ['https'] })
|
||||
photoUrl?: string;
|
||||
}
|
||||
|
||||
export class VisitCheckOutDto {
|
||||
@ApiProperty({ enum: CHECK_IN_METHODS })
|
||||
@IsIn([...CHECK_IN_METHODS])
|
||||
method!: string;
|
||||
|
||||
@ApiPropertyOptional()
|
||||
@ValidateIf((dto: VisitCheckOutDto) => dto.method === 'nfc')
|
||||
@IsString()
|
||||
nfcId?: string;
|
||||
|
||||
@ApiPropertyOptional()
|
||||
@ValidateIf((dto: VisitCheckOutDto) => dto.method === 'qr')
|
||||
@IsString()
|
||||
qrCode?: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsLatitude()
|
||||
latitude!: number;
|
||||
|
||||
@ApiProperty()
|
||||
@IsLongitude()
|
||||
longitude!: number;
|
||||
|
||||
@ApiPropertyOptional()
|
||||
@IsOptional()
|
||||
@IsUrl({ require_protocol: true, protocols: ['https'] })
|
||||
photoUrl?: string;
|
||||
}
|
||||
|
||||
export class UpdateVisitStatusDto {
|
||||
@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 ListVisitsQueryDto extends PaginationQueryDto {
|
||||
@ApiPropertyOptional({ format: 'uuid' })
|
||||
@IsOptional()
|
||||
@IsUUID('4')
|
||||
employeeId?: string;
|
||||
|
||||
@ApiPropertyOptional({ format: 'uuid' })
|
||||
@IsOptional()
|
||||
@IsUUID('4')
|
||||
customerId?: string;
|
||||
|
||||
@ApiPropertyOptional({ description: 'Unix ms start of calendar day' })
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
date?: number;
|
||||
|
||||
@ApiPropertyOptional({ enum: CORE_STATUSES })
|
||||
@IsOptional()
|
||||
@IsIn([...CORE_STATUSES])
|
||||
status?: string;
|
||||
|
||||
@ApiPropertyOptional()
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
search?: string;
|
||||
}
|
||||
|
||||
class RelationDto {
|
||||
@ApiProperty({ format: 'uuid' })
|
||||
id!: string;
|
||||
|
||||
@ApiProperty()
|
||||
code!: string;
|
||||
|
||||
@ApiProperty()
|
||||
name!: string;
|
||||
}
|
||||
|
||||
class UserRelationDto {
|
||||
@ApiProperty({ format: 'uuid' })
|
||||
id!: string;
|
||||
|
||||
@ApiProperty()
|
||||
username!: string;
|
||||
}
|
||||
|
||||
export class VisitDto {
|
||||
@ApiProperty({ format: 'uuid' })
|
||||
id!: string;
|
||||
|
||||
@ApiProperty({ type: RelationDto })
|
||||
employee!: RelationDto;
|
||||
|
||||
@ApiProperty({ type: RelationDto })
|
||||
customer!: RelationDto;
|
||||
|
||||
@ApiProperty({ format: 'uuid', nullable: true })
|
||||
attendanceId!: string | null;
|
||||
|
||||
@ApiProperty({ format: 'uuid', nullable: true })
|
||||
planId!: string | null;
|
||||
|
||||
@ApiProperty({ format: 'uuid', nullable: true })
|
||||
planDestinationId!: string | null;
|
||||
|
||||
@ApiProperty()
|
||||
date!: number;
|
||||
|
||||
@ApiProperty()
|
||||
checkInAt!: number;
|
||||
|
||||
@ApiProperty({ enum: CHECK_IN_METHODS })
|
||||
checkInMethod!: string;
|
||||
|
||||
@ApiProperty()
|
||||
checkInLatitude!: number;
|
||||
|
||||
@ApiProperty()
|
||||
checkInLongitude!: number;
|
||||
|
||||
@ApiProperty({ nullable: true })
|
||||
checkInPhotoUrl!: string | null;
|
||||
|
||||
@ApiProperty({ nullable: true })
|
||||
checkInDistanceMeters!: number | null;
|
||||
|
||||
@ApiProperty({ nullable: true })
|
||||
checkOutAt!: number | null;
|
||||
|
||||
@ApiProperty({ enum: CHECK_IN_METHODS, nullable: true })
|
||||
checkOutMethod!: string | null;
|
||||
|
||||
@ApiProperty({ nullable: true })
|
||||
checkOutLatitude!: number | null;
|
||||
|
||||
@ApiProperty({ nullable: true })
|
||||
checkOutLongitude!: number | null;
|
||||
|
||||
@ApiProperty({ nullable: true })
|
||||
checkOutPhotoUrl!: string | null;
|
||||
|
||||
@ApiProperty({ nullable: true })
|
||||
checkOutDistanceMeters!: number | null;
|
||||
|
||||
@ApiProperty()
|
||||
status!: string;
|
||||
|
||||
@ApiProperty()
|
||||
createdAt!: number;
|
||||
|
||||
@ApiProperty()
|
||||
updatedAt!: number;
|
||||
|
||||
@ApiProperty({ type: UserRelationDto })
|
||||
createdBy!: UserRelationDto;
|
||||
|
||||
@ApiProperty({ type: UserRelationDto })
|
||||
updatedBy!: UserRelationDto;
|
||||
}
|
||||
Reference in New Issue
Block a user