feat: Enhance KTP Validation Logic and Update Patrol Unit Selection UI

- Expanded KTP validation to include additional required fields: 'jenis_kelamin' and 'kewarganegaraan'.
- Improved error handling for missing or invalid gender and nationality during KTP validation.
- Updated Patrol Unit Selection Screen to reflect changes in UI elements, including renaming 'Position' to 'Rank' and enhancing dark mode support.
- Refactored image uploader and circular loader components to utilize theme-aware colors for better visual consistency.
This commit is contained in:
vergiLgood1 2025-05-26 16:22:52 +07:00
parent 5bd92a0399
commit 105d992faa
8 changed files with 318 additions and 191 deletions

View File

@ -1411,7 +1411,7 @@ class AzureOCRService {
// Check if KTP has all required fields
bool isKtpValid(Map<String, String> extractedInfo) {
// Required fields for KTP validation
final requiredFields = ['nik', 'nama'];
final requiredFields = ['nik', 'nama', 'jenis_kelamin', 'kewarganegaraan'];
// Check that all required fields are present and not empty
for (var field in requiredFields) {
@ -1439,12 +1439,12 @@ class AzureOCRService {
// Birth place and date should be valid
// final birthPlace = extractedInfo['tempat_lahir'] ?? '';
// final birthDate = extractedInfo['tanggal_lahir'] ?? '';
// if (birthPlace.isEmpty || birthDate.isEmpty) {
// if (birthDate.isEmpty) {
// print('KTP validation failed: missing birth place or date');
// return false;
// }
// // Birth date should be in the format dd-mm-yyyy
// // // Birth date should be in the format dd-mm-yyyy
// final dateRegex = RegExp(r'^\d{1,2}-\d{1,2}-\d{2,4}$');
// if (!dateRegex.hasMatch(birthDate)) {
// print('KTP validation failed: invalid birth date format');
@ -1452,19 +1452,19 @@ class AzureOCRService {
// }
// // Gender should be either "L" or "P"
// final gender = extractedInfo['jenis_kelamin'] ?? '';
// if (gender.isEmpty || gender != 'LAKI-LAKI' || gender != 'PEREMPUAN') {
// print('KTP validation failed: Missing or invalid gender');
// return false;
// }
final gender = extractedInfo['jenis_kelamin'] ?? '';
if (gender.isEmpty || gender != 'LAKI-LAKI' || gender != 'PEREMPUAN') {
print('KTP validation failed: Missing or invalid gender');
return false;
}
// // Nationality should be "WNI"
// final nationality = extractedInfo['kewarganegaraan'] ?? 'WNI';
final nationality = extractedInfo['kewarganegaraan'] ?? 'WNI';
// if (nationality.isEmpty || nationality != 'WNI') {
// print('KTP validation failed: Missing or invalid');
// return false;
// }
if (nationality.isEmpty || nationality != 'WNI') {
print('KTP validation failed: Missing or invalid');
return false;
}
// Validated successfully
isValidKtp = true;

View File

@ -84,6 +84,7 @@ class IdCardVerificationStep extends StatelessWidget {
onClear: controller.clearIdCardImage,
onValidate: controller.validateIdCardImage,
placeholderIcon: Icons.add_a_photo,
),
),
@ -187,7 +188,6 @@ class IdCardVerificationStep extends StatelessWidget {
"Your photo and all text should be clearly visible",
"Avoid using flash to prevent glare",
],
);
}
@ -285,7 +285,7 @@ class IdCardVerificationStep extends StatelessWidget {
_buildInfoRow('Unit', model.policeUnit, context),
if ((model.position ?? '').isNotEmpty)
_buildInfoRow('Position', model.position ?? '', context),
_buildInfoRow('Rank', model.position ?? '', context),
if (model.issueDate.isNotEmpty)
_buildInfoRow('Issue Date', model.issueDate, context),

View File

@ -4,6 +4,7 @@ import 'package:sigap/src/features/auth/presentasion/controllers/signup/step/off
import 'package:sigap/src/shared/widgets/text/custom_text_field.dart';
import 'package:sigap/src/utils/constants/colors.dart';
import 'package:sigap/src/utils/constants/sizes.dart';
import 'package:sigap/src/utils/helpers/helper_functions.dart';
import 'package:sigap/src/utils/validators/validation.dart';
class PatrolUnitSelectionScreen extends StatelessWidget {
@ -13,6 +14,7 @@ class PatrolUnitSelectionScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
final isDark = THelperFunctions.isDarkMode(context);
return Scaffold(
appBar: AppBar(
title: const Text('Configure Patrol Unit'),
@ -30,12 +32,18 @@ class PatrolUnitSelectionScreen extends StatelessWidget {
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.grey[100],
color:
isDark
? TColors.accent.withOpacity(0.1)
: TColors.lightContainer.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: Row(
children: [
const Icon(Icons.business, color: TColors.primary),
Icon(
Icons.business,
color: isDark ? TColors.accent : TColors.primary,
),
const SizedBox(width: 12),
Expanded(
child: Column(
@ -43,9 +51,13 @@ class PatrolUnitSelectionScreen extends StatelessWidget {
children: [
Text(
'Selected Unit',
style: TextStyle(
color: Colors.grey[600],
fontSize: 12,
style: Theme.of(
context,
).textTheme.labelSmall?.copyWith(
color:
isDark
? TColors.accent.withOpacity(0.5)
: TColors.primary.withOpacity(0.5),
),
),
Obx(
@ -70,94 +82,109 @@ class PatrolUnitSelectionScreen extends StatelessWidget {
),
const SizedBox(height: TSizes.spaceBtwInputFields / 2),
GetX<OfficerInfoController>(
builder:
(controller) => Row(
children: [
Expanded(
child: InkWell(
onTap: () {
controller.setPatrolUnitType(PatrolUnitType.car);
},
child: Container(
padding: const EdgeInsets.symmetric(vertical: 12),
decoration: BoxDecoration(
builder: (controller) {
final isCarSelected =
controller.selectedPatrolType.value == PatrolUnitType.car;
final isMotorcycleSelected =
controller.selectedPatrolType.value ==
PatrolUnitType.motorcycle;
final isDark = THelperFunctions.isDarkMode(context);
return Row(
children: [
Expanded(
child: InkWell(
onTap: () {
controller.setPatrolUnitType(PatrolUnitType.car);
},
child: Container(
padding: const EdgeInsets.symmetric(vertical: 12),
decoration: BoxDecoration(
color:
isCarSelected
? isDark
? TColors.accent.withOpacity(0.1)
: TColors.primary.withOpacity(0.1)
: Colors.transparent,
borderRadius: BorderRadius.circular(8),
border: Border.all(
color:
controller.selectedPatrolType.value ==
PatrolUnitType.car
? TColors.primary.withOpacity(0.1)
: Colors.transparent,
borderRadius: BorderRadius.circular(8),
border: Border.all(
isCarSelected
? isDark
? TColors.accent
: TColors.primary
: TColors.accent.withOpacity(0.3),
),
),
child: Column(
children: [
Icon(
Icons.directions_car,
color:
controller.selectedPatrolType.value ==
PatrolUnitType.car
? TColors.primary
: Colors.grey,
isCarSelected
? isDark
? TColors.accent
: TColors.primary
: TColors.accent.withOpacity(0.3),
size: 32,
),
),
child: Column(
children: [
Icon(
Icons.directions_car,
color:
controller.selectedPatrolType.value ==
PatrolUnitType.car
? TColors.primary
: Colors.grey,
size: 32,
),
const SizedBox(height: TSizes.sm),
const Text('Car'),
],
),
const SizedBox(height: TSizes.sm),
const Text('Car'),
],
),
),
),
const SizedBox(width: TSizes.spaceBtwInputFields),
Expanded(
child: InkWell(
onTap: () {
controller.setPatrolUnitType(
PatrolUnitType.motorcycle,
);
},
child: Container(
padding: const EdgeInsets.symmetric(vertical: 12),
decoration: BoxDecoration(
),
const SizedBox(width: TSizes.spaceBtwInputFields),
Expanded(
child: InkWell(
onTap: () {
controller.setPatrolUnitType(
PatrolUnitType.motorcycle,
);
},
child: Container(
padding: const EdgeInsets.symmetric(vertical: 12),
decoration: BoxDecoration(
color:
isMotorcycleSelected
? isDark
? TColors.accent.withOpacity(0.1)
: TColors.primary.withOpacity(0.1)
: Colors.transparent,
borderRadius: BorderRadius.circular(8),
border: Border.all(
color:
controller.selectedPatrolType.value ==
PatrolUnitType.motorcycle
? TColors.primary.withOpacity(0.1)
: Colors.transparent,
borderRadius: BorderRadius.circular(8),
border: Border.all(
isMotorcycleSelected
? isDark
? TColors.accent
: TColors.primary
: TColors.accent.withOpacity(0.3),
),
),
child: Column(
children: [
Icon(
Icons.motorcycle,
color:
controller.selectedPatrolType.value ==
PatrolUnitType.motorcycle
? TColors.primary
: Colors.grey,
isMotorcycleSelected
? isDark
? TColors.accent
: TColors.primary
: TColors.accent.withOpacity(0.3),
size: 32,
),
),
child: Column(
children: [
Icon(
Icons.motorcycle,
color:
controller.selectedPatrolType.value ==
PatrolUnitType.motorcycle
? TColors.primary
: Colors.grey,
size: 32,
),
const SizedBox(height: TSizes.sm),
const Text('Motorcycle'),
],
),
const SizedBox(height: TSizes.sm),
const Text('Motorcycle'),
],
),
),
),
],
),
),
],
);
},
),
const SizedBox(height: TSizes.spaceBtwSections),
@ -177,18 +204,26 @@ class PatrolUnitSelectionScreen extends StatelessWidget {
PatrolSelectionMode.individual,
'Individual',
Icons.person,
context,
),
const SizedBox(width: TSizes.spaceBtwInputFields),
_buildModeTab(
controller,
PatrolSelectionMode.group,
'Group',
Icons.group,
context,
),
const SizedBox(width: TSizes.spaceBtwInputFields),
_buildModeTab(
controller,
PatrolSelectionMode.createNew,
'Create New',
Icons.add_circle,
context,
),
],
),
@ -264,44 +299,39 @@ class PatrolUnitSelectionScreen extends StatelessWidget {
PatrolSelectionMode mode,
String label,
IconData icon,
BuildContext context,
) {
final bool isSelected = controller.patrolSelectionMode.value == mode;
final isDark = THelperFunctions.isDarkMode(context);
// define the color based on selection and theme
final boxDecorationColor =
isSelected
? (isDark
? TColors.accent.withOpacity(0.1)
: TColors.primary.withOpacity(0.1))
: TColors.transparent;
final color =
isSelected
? (isDark ? TColors.accent : TColors.primary)
: TColors.accent.withOpacity(0.3);
return Expanded(
child: InkWell(
onTap: () => controller.setPatrolSelectionMode(mode),
child: Container(
padding: const EdgeInsets.symmetric(vertical: 12),
decoration: BoxDecoration(
color:
controller.patrolSelectionMode.value == mode
? TColors.primary.withOpacity(0.1)
: Colors.transparent,
color: boxDecorationColor,
borderRadius: BorderRadius.circular(8),
border: Border.all(
color:
controller.patrolSelectionMode.value == mode
? TColors.primary
: Colors.grey,
),
border: Border.all(color: color),
),
child: Column(
children: [
Icon(
icon,
color:
controller.patrolSelectionMode.value == mode
? TColors.primary
: Colors.grey,
),
Icon(icon, color: color),
const SizedBox(height: 4),
Text(
label,
style: TextStyle(
color:
controller.patrolSelectionMode.value == mode
? TColors.primary
: Colors.grey,
),
),
Text(label, style: TextStyle(color: color)),
],
),
),
@ -348,38 +378,42 @@ class PatrolUnitSelectionScreen extends StatelessWidget {
itemCount: filteredUnits.length,
itemBuilder: (context, index) {
final patrolUnit = filteredUnits[index];
final isSelected =
final isUnitSelected =
patrolUnit.id == controller.patrolUnitIdController.text;
final isCarType = patrolUnit.type.toLowerCase() == 'car';
return Card(
elevation: isSelected ? 2 : 0,
color: isSelected ? TColors.primary.withOpacity(0.1) : null,
elevation: isUnitSelected ? 2 : 0,
color:
isUnitSelected
? TColors.primary.withOpacity(0.1)
: null,
margin: const EdgeInsets.symmetric(vertical: 4),
child: ListTile(
title: Text(
patrolUnit.name,
style: TextStyle(
fontWeight:
isSelected ? FontWeight.bold : FontWeight.normal,
isUnitSelected
? FontWeight.bold
: FontWeight.normal,
),
),
subtitle: Text(
'Members: ${patrolUnit.members?.length ?? 0}',
),
leading: Icon(
patrolUnit.type.toLowerCase() == 'car'
? Icons.directions_car
: Icons.motorcycle,
color: isSelected ? TColors.primary : null,
isCarType ? Icons.directions_car : Icons.motorcycle,
color: isUnitSelected ? TColors.primary : null,
),
trailing:
isSelected
isUnitSelected
? const Icon(
Icons.check_circle,
color: TColors.primary,
)
: null,
selected: isSelected,
selected: isUnitSelected,
onTap: () => controller.joinPatrolUnit(patrolUnit),
),
);

View File

@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:sigap/src/utils/constants/colors.dart';
import 'package:sigap/src/utils/constants/sizes.dart';
import 'package:sigap/src/utils/helpers/helper_functions.dart';
import 'package:sigap/src/utils/loaders/circular_loader.dart';
class ImageUploader extends StatelessWidget {
@ -68,7 +69,7 @@ class ImageUploader extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (image == null)
_buildEmptyUploader(backgroundColor, borderColor)
_buildEmptyUploader(backgroundColor, borderColor, context)
else
_buildImagePreview(borderColor, context),
@ -102,7 +103,13 @@ class ImageUploader extends StatelessWidget {
);
}
Widget _buildEmptyUploader(Color backgroundColor, Color borderColor) {
Widget _buildEmptyUploader(
Color backgroundColor,
Color borderColor,
BuildContext context,
) {
final isDark = THelperFunctions.isDarkMode(context);
return GestureDetector(
onTap: onTapToSelect,
child: Container(
@ -122,7 +129,11 @@ class ImageUploader extends StatelessWidget {
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const TCircularLoader(),
TCircularLoader(
backgroundColor: TColors.transparent,
foregroundColor:
isDark ? TColors.accent : TColors.primary,
),
const SizedBox(height: TSizes.sm),
Text(
'Uploading...',

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:sigap/src/utils/constants/colors.dart';
import 'package:sigap/src/utils/constants/sizes.dart';
import 'package:sigap/src/utils/helpers/helper_functions.dart';
/// A circular loader widget with customizable foreground and background colors.
class TCircularLoader extends StatelessWidget {
@ -20,6 +21,7 @@ class TCircularLoader extends StatelessWidget {
@override
Widget build(BuildContext context) {
final isDark = THelperFunctions.isDarkMode(context);
return Container(
padding: const EdgeInsets.all(TSizes.lg),
decoration: BoxDecoration(

View File

@ -11,17 +11,18 @@ datasource db {
}
model profiles {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
user_id String @unique @db.Uuid
avatar String? @db.VarChar(355)
username String? @unique @db.VarChar(255)
first_name String? @db.VarChar(255)
last_name String? @db.VarChar(255)
bio String? @db.VarChar
address Json? @db.Json
birth_date DateTime?
nik String? @default("") @db.VarChar(100)
users users @relation(fields: [user_id], references: [id])
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
user_id String @unique @db.Uuid
avatar String? @db.VarChar(355)
username String? @unique @db.VarChar(255)
first_name String? @db.VarChar(255)
last_name String? @db.VarChar(255)
bio String? @db.VarChar
address Json? @db.Json
birth_date DateTime?
nik String? @db.VarChar(100)
birth_place String?
users users @relation(fields: [user_id], references: [id])
@@index([user_id])
@@index([username])
@ -321,17 +322,19 @@ model units {
}
model patrol_units {
unit_id String @db.VarChar(20)
location_id String @db.Uuid
name String @db.VarChar(100)
type String @db.VarChar(50)
status String @db.VarChar(50)
radius Float
created_at DateTime @default(now()) @db.Timestamptz(6)
id String @id @unique @db.VarChar(100)
members officers[]
location locations @relation(fields: [location_id], references: [id], onDelete: Cascade, onUpdate: NoAction)
unit units @relation(fields: [unit_id], references: [code_unit], onDelete: Cascade, onUpdate: NoAction)
unit_id String @db.VarChar(20)
location_id String @db.Uuid
name String @db.VarChar(100)
type String @db.VarChar(50)
category patrol_unit_category? @default(group)
member_count Int? @default(0)
status String @db.VarChar(50)
radius Float
created_at DateTime @default(now()) @db.Timestamptz(6)
id String @id @unique @db.VarChar(100)
members officers[]
location locations @relation(fields: [location_id], references: [id], onDelete: Cascade, onUpdate: NoAction)
unit units @relation(fields: [unit_id], references: [code_unit], onDelete: Cascade, onUpdate: NoAction)
@@index([unit_id], map: "idx_patrol_units_unit_id")
@@index([location_id], map: "idx_patrol_units_location_id")
@ -341,9 +344,9 @@ model patrol_units {
}
model officers {
unit_id String @db.VarChar(20)
unit_id String? @db.VarChar(20)
role_id String @db.Uuid
nrp String @unique @db.VarChar(100)
nrp String? @unique @db.VarChar(100)
name String @db.VarChar(100)
rank String? @db.VarChar(100)
position String? @db.VarChar(100)
@ -354,7 +357,7 @@ model officers {
qr_code String?
created_at DateTime? @default(now()) @db.Timestamptz(6)
updated_at DateTime? @default(now()) @db.Timestamptz(6)
patrol_unit_id String @db.VarChar(100)
patrol_unit_id String? @db.VarChar(100)
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
banned_reason String? @db.VarChar(255)
banned_until DateTime?
@ -363,9 +366,9 @@ model officers {
spoofing_attempts Int @default(0)
place_of_birth String?
date_of_birth DateTime? @db.Timestamptz(6)
patrol_units patrol_units @relation(fields: [patrol_unit_id], references: [id])
patrol_units patrol_units? @relation(fields: [patrol_unit_id], references: [id], onDelete: Restrict)
roles roles @relation(fields: [role_id], references: [id], onDelete: Cascade, onUpdate: NoAction)
units units @relation(fields: [unit_id], references: [code_unit], onDelete: Cascade, onUpdate: NoAction)
units units? @relation(fields: [unit_id], references: [code_unit], onDelete: Cascade, onUpdate: NoAction)
panic_button_logs panic_button_logs[]
@@index([unit_id], map: "idx_officers_unit_id")
@ -472,6 +475,11 @@ model location_logs {
@@index([user_id], map: "idx_location_logs_user_id")
}
enum patrol_unit_category {
individual
group
}
enum session_status {
active
completed

View File

@ -33,19 +33,19 @@ class DatabaseSeeder {
// Daftar semua seeders di sini
this.seeders = [
new RoleSeeder(prisma),
new ResourceSeeder(prisma),
new PermissionSeeder(prisma),
new CrimeCategoriesSeeder(prisma),
new GeoJSONSeeder(prisma),
new UnitSeeder(prisma),
// new RoleSeeder(prisma),
// new ResourceSeeder(prisma),
// new PermissionSeeder(prisma),
// new CrimeCategoriesSeeder(prisma),
// new GeoJSONSeeder(prisma),
// new UnitSeeder(prisma),
new PatrolUnitsSeeder(prisma),
new OfficersSeeder(prisma),
new DemographicsSeeder(prisma),
new CrimesSeeder(prisma),
// new OfficersSeeder(prisma),
// new DemographicsSeeder(prisma),
// new CrimesSeeder(prisma),
// new CrimeIncidentsByUnitSeeder(prisma),
new CrimeIncidentsByTypeSeeder(prisma),
new IncidentLogSeeder(prisma),
// new CrimeIncidentsByTypeSeeder(prisma),
// new IncidentLogSeeder(prisma),
];
}

View File

@ -11,9 +11,85 @@ export class PatrolUnitsSeeder {
private supabase = createClient()
) { }
async run(): Promise<void> {
// Add tactical callsigns as a class property
private tacticalCallsigns = [
'Alpha', 'Bravo', 'Charlie', 'Delta', 'Echo',
'Foxtrot', 'Ghost', 'Hunter', 'Ice', 'Jaguar',
'Kilo', 'Lima', 'Mike', 'Nova', 'Omega',
'Phoenix', 'Quebec', 'Romeo', 'Sierra', 'Tango',
'Ultra', 'Victor', 'Whiskey', 'X-Ray', 'Yankee',
'Zulu', 'Raptor', 'Viper', 'Cobra', 'Eagle',
];
// Mapping type to code
private typeCodeMap: Record<string, string> = {
car: "C",
motorcycle: "M",
foot: "F",
mixed: "X",
drone: "D",
};
// Helper method to get random callsign
private getRandomCallsign(): string {
return this.tacticalCallsigns[Math.floor(Math.random() * this.tacticalCallsigns.length)];
}
async run(isUpdate: boolean = true): Promise<void> {
console.log('🚓 Seeding patrol units...');
if (isUpdate) {
console.log('Updating patrol unit categories based on member count...');
// Get all patrol units with their member counts
const patrolUnitsWithMembers = await this.prisma.patrol_units.findMany({
select: {
id: true,
unit_id: true,
type: true,
_count: {
select: {
members: true
}
}
}
});
// Update each patrol unit's member_count and category
for (const pu of patrolUnitsWithMembers) {
const memberCount = pu._count.members;
const category = memberCount > 1 ? 'group' : 'individual';
const callsign = this.getRandomCallsign();
const patrolName = `${callsign}-${pu.id.split('-')[1]}`;
try {
await this.prisma.patrol_units.update({
where: { id: pu.id },
data: {
member_count: memberCount,
category: category,
name: patrolName.toUpperCase()
}
});
// Update in Supabase as well
// await this.supabase
// .from('patrol_units')
// .update({
// member_count: memberCount,
// category: category
// })
// .eq('id', pu.id);
} catch (error) {
console.error(`Error updating patrol unit ${pu.id}:`, error);
}
}
console.log('✅ Updated patrol unit categories and member counts');
return;
}
// First, let's clear existing patrol units
try {
await this.prisma.patrol_units.deleteMany({});
@ -21,7 +97,7 @@ export class PatrolUnitsSeeder {
await this.supabase.from('patrol_units').delete().neq('id', 'dummy');
console.log('✅ Removed existing patrol units');
} catch (error) {
console.error('❌ Error removing existing patrol units:', error);
console.error('❌k Error removing existing patrol units:', error);
return; // Exit if we can't clean up properly
}
@ -84,15 +160,6 @@ export class PatrolUnitsSeeder {
}
};
// Mapping type to code
const typeCodeMap: Record<string, string> = {
car: "C",
motorcycle: "M",
foot: "F",
mixed: "X",
drone: "D",
};
// Get locations for each district to assign to patrol units
const locationsByDistrict = await this.getLocationsByDistrict();
@ -111,9 +178,8 @@ export class PatrolUnitsSeeder {
for (let i = 1; i <= patrolCount; i++) {
// Select patrol type based on weighted distribution
const patrolType = this.getWeightedRandomItem(unitTypeWeights) as string;
const patrolName = `${unit.name.replace('Polsek', 'Patroli').replace('Polres', 'Patroli')} ${patrolType.charAt(0).toUpperCase() + patrolType.slice(1)
} ${i}`;
const callsign = this.getRandomCallsign();
const patrolName = `${callsign}-${unit.code_unit}-${this.typeCodeMap[patrolType]}${i}`;
const radius = getPatrolRadius(patrolType);
const status = this.getWeightedRandomItem(weightedStatus) as string;
@ -132,7 +198,7 @@ export class PatrolUnitsSeeder {
continue;
}
const typeCode = typeCodeMap[patrolType] || "P";
const typeCode = this.typeCodeMap[patrolType] || "P";
const codeUnitLast2 = unit.code_unit.slice(-2);
try {
@ -149,6 +215,10 @@ export class PatrolUnitsSeeder {
CRegex.PATROL_UNIT_ID_REGEX
);
// Generate random member count and set category
const memberCount = faker.number.int({ min: 1, max: 4 });
const category = memberCount > 1 ? 'group' : 'individual';
patrolUnits.push({
id: newId,
unit_id: unit.code_unit,
@ -157,6 +227,8 @@ export class PatrolUnitsSeeder {
type: patrolType,
status: status,
radius: radius,
member_count: memberCount,
category: category
});
} catch (error) {
console.error(`Error generating ID for patrol unit: ${error}`);
@ -218,7 +290,7 @@ export class PatrolUnitsSeeder {
where: {
user_id: user.id
}
});
})
if (!event) {
try {