MIF_E31221222/sigap-website/prisma/seeds/officers.ts

300 lines
12 KiB
TypeScript

import { officers, PrismaClient } from '@prisma/client';
import { createClient } from '../../app/_utils/supabase/client';
import { faker } from '@faker-js/faker';
import * as crypto from 'crypto';
import { CRegex } from '../../app/_utils/const/regex';
import { generateIdWithDbCounter } from '../../app/_utils/common';
const RANKS = [
'IPDA', 'IPTU', 'AKP', 'KOMPOL', 'AKBP', 'KOMBES', // Officers
'AIPDA', 'AIPTU', 'BRIGADIR', 'BRIGADIR KEPALA', // Non-commissioned officers
'BRIPTU', 'BRIPKA', 'BRIPDA', // Lower ranks
];
const POSITIONS = [
'Kapolsek', 'Wakapolsek', 'Kanit Reskrim', 'Kanit Intel', 'Kanit Sabhara',
'Kanit Binmas', 'Kanit Provost', 'Anggota', 'Penyidik', 'Pengemban Fungsi',
'Kepala Unit Patroli', 'Komandan Sektor', 'Staf Operasional'
];
/**
* Generates a QR code value based on officer's NRP and unit ID
* The generated string can be used as input for QR code generation
*
* @param nrp Officer NRP (ID number)
* @param unitId Unit identifier
* @returns Encoded string to be used as QR code content
*/
function generateOfficerQRCode(nrp: string, unitId: string): string {
// Create a unique string by combining NRP and unit ID
const baseString = `SIGAP-OFFICER:${nrp}:${unitId}:${Date.now()}`;
// Create a hash of this string for security
const hash = crypto.createHash('sha256')
.update(baseString)
.digest('hex')
.substring(0, 12);
// Create a URL-friendly encoded string that includes officer info and validation hash
const qrData = Buffer.from(`${nrp}:${unitId}:${hash}`).toString('base64');
return qrData;
}
export class OfficersSeeder {
constructor(
private prisma: PrismaClient,
private supabase = createClient()
) { }
async run(): Promise<void> {
console.log('👮 Seeding officers...');
// First, let's clear existing officers
try {
await this.prisma.officers.deleteMany({});
console.log('✅ Removed existing officers');
} catch (error) {
console.error('❌ Error removing existing officers:', error);
}
// Get all police units
const policeUnits = await this.prisma.units.findMany({
select: {
code_unit: true,
name: true,
type: true,
patrol_units: {
select: {
id: true,
unit_id: true,
name: true,
},
}
},
});
// Get all patrol units
const patrolUnits = await this.prisma.patrol_units.findMany({
select: {
id: true,
unit_id: true,
name: true,
unit: {
select: {
code_unit: true,
name: true,
type: true,
},
}
},
});
if (!policeUnits.length) {
console.error('❌ No police units found. Please seed units first.');
return;
}
if (!patrolUnits.length) {
console.error('❌ No patrol units found. Please seed patrol units first.');
return;
}
// Create a mapping of unit_id to a list of patrol_unit_ids
const unitToPatrolUnits: Record<string, string[]> = {};
for (const patrol of patrolUnits) {
if (!unitToPatrolUnits[patrol.unit_id]) {
unitToPatrolUnits[patrol.unit_id] = [];
}
unitToPatrolUnits[patrol.unit_id].push(patrol.id);
}
// Check if each police unit has at least one patrol unit
for (const unit of policeUnits) {
if (!unitToPatrolUnits[unit.code_unit] || unitToPatrolUnits[unit.code_unit].length === 0) {
console.warn(`⚠️ Unit ${unit.name} (${unit.code_unit}) has no patrol units. Creating one...`);
// // Create a default patrol unit for this police unit
// try {
// // Mapping type to code
// const typeCodeMap: Record<string, string> = {
// car: "C",
// motorcycle: "M",
// foot: "F",
// mixed: "X",
// drone: "D",
// };
// const typeCode = typeCodeMap[patrolType] || "P";
// const codeUnitLast2 = unit.code_unit.slice(-2);
// const newId = await generateIdWithDbCounter(
// "patrol_units",
// {
// prefix: "PU",
// segments: {
// codes: [typeCode + codeUnitLast2],
// sequentialDigits: 2,
// },
// format: "{prefix}-{codes}{sequence}",
// },
// CRegex.PATROL_UNIT_ID_REGEX
// );
// const newPatrolUnit = await this.prisma.patrol_units.create({
// data: {
// unit_id: unit.code_unit,
// name: `Default Patrol Unit - ${unit.name}`,
// created_at: new Date(),
// }
// });
// if (!unitToPatrolUnits[unit.code_unit]) {
// unitToPatrolUnits[unit.code_unit] = [];
// }
// unitToPatrolUnits[unit.code_unit].push(newPatrolUnit.id);
// console.log(`✅ Created default patrol unit for ${unit.name}`);
// } catch (err) {
// console.error(`❌ Failed to create default patrol unit for ${unit.name}:`, err);
// // Skip this unit if we can't create a patrol unit
// continue;
// }
}
}
// Get officer role ID
const officerRole = await this.prisma.roles.findFirst({
where: { name: 'officer' },
select: { id: true },
});
if (!officerRole) {
console.error('❌ Officer role not found. Please seed roles first.');
return;
}
const roleId = officerRole.id;
const officers: Partial<officers>[] = [];
// Generate officers for each police unit
for (const unit of policeUnits) {
const patrolUnitIds = unitToPatrolUnits[unit.code_unit];
// Skip unit if there are no patrol units available
if (!patrolUnitIds || patrolUnitIds.length === 0) {
console.warn(`⚠️ Skipping unit ${unit.name} because it has no patrol units`);
continue;
}
// Number of officers varies by unit type
const officerCount = unit.type === 'polres' ?
faker.number.int({ min: 20, max: 30 }) :
faker.number.int({ min: 10, max: 20 });
// Keep track of assigned positions to avoid duplicates
const assignedPositions = new Set();
for (let i = 1; i <= officerCount; i++) {
// Generate a unique NRP (ID number)
const nrpYear = faker.number.int({ min: 80, max: 99 }).toString();
const nrpSeq = faker.number.int({ min: 10000, max: 99999 }).toString();
const nrp = `${nrpYear}${nrpSeq}`;
// Choose rank based on position
let position, rank;
// For important positions, assign specific ranks
if (i <= 5 && !assignedPositions.has('Kapolsek')) {
position = 'Kapolsek';
rank = faker.helpers.arrayElement(['IPTU', 'AKP', 'KOMPOL']);
assignedPositions.add(position);
} else if (i <= 5 && !assignedPositions.has('Wakapolsek')) {
position = 'Wakapolsek';
rank = faker.helpers.arrayElement(['IPDA', 'IPTU', 'AKP']);
assignedPositions.add(position);
} else if (i <= 5 && !assignedPositions.has('Kanit Reskrim')) {
position = 'Kanit Reskrim';
rank = faker.helpers.arrayElement(['IPDA', 'IPTU']);
assignedPositions.add(position);
} else {
// For other officers, assign random positions and ranks
position = faker.helpers.arrayElement(POSITIONS.filter(p =>
p !== 'Kapolsek' && p !== 'Wakapolsek' && p !== 'Kanit Reskrim' || !assignedPositions.has(p)
));
rank = faker.helpers.arrayElement(RANKS);
}
// Generate QR code
const qrCode = generateOfficerQRCode(nrp, unit.code_unit);
// Assign a default patrol unit (will be potentially reassigned later)
// IMPORTANT: Set a default patrol_unit_id to ensure all officers have one
const defaultPatrolUnitId = faker.helpers.arrayElement(patrolUnitIds);
// Create a new officer with a required patrol_unit_id
const officer = {
unit_id: unit.code_unit,
role_id: roleId,
nrp: nrp,
name: faker.person.fullName(),
rank: rank,
position: position,
phone: faker.helpers.fromRegExp(/08[0-9]{8,12}/), // Keep original format
email: faker.internet.email().toLowerCase(),
valid_until: faker.date.future(),
created_at: faker.date.past(),
updated_at: new Date(),
avatar: faker.image.personPortrait(), // Keep original format
qr_code: qrCode,
patrol_unit_id: defaultPatrolUnitId, // Default assignment
};
officers.push(officer);
}
}
// Insert officers in smaller batches
if (officers.length > 0) {
const batchSize = 100;
for (let i = 0; i < officers.length; i += batchSize) {
const batch = officers.slice(i, i + batchSize);
try {
// Ensure all officers in the batch have patrol_unit_id
const validBatch = batch.filter(officer => officer.patrol_unit_id);
if (validBatch.length !== batch.length) {
console.warn(`⚠️ Filtered out ${batch.length - validBatch.length} officers without patrol_unit_id`);
}
if (validBatch.length === 0) {
console.warn(`⚠️ Skipping empty batch ${i / batchSize + 1}`);
continue;
}
const { error } = await this.supabase
.from('officers')
.insert(validBatch)
.select();
if (error) {
console.error(`Error inserting officers batch ${i / batchSize + 1}:`, error);
} else {
console.log(`✅ Inserted batch ${i / batchSize + 1} (${validBatch.length} officers)`);
}
// Small delay between batches
await new Promise(resolve => setTimeout(resolve, 300));
} catch (err) {
console.error(`Exception when inserting officers batch ${i / batchSize + 1}:`, err);
}
}
console.log(`👮 Created ${officers.length} officers for ${policeUnits.length} police units`);
} else {
console.warn('⚠️ No officer data to insert');
}
}
}