feat(lamaran): reset lamaran dan rollover batch, serta menambahkan snapshot posisi lamaran mhs ke tabel user
This commit is contained in:
+17
@@ -0,0 +1,17 @@
|
|||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE `user` DROP FOREIGN KEY `user_id_lamaran_magang_foreign`;
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE `user` DROP FOREIGN KEY `user_id_mahasiswa_foreign`;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE `user` ADD COLUMN `intern_interest_group` VARCHAR(255) NULL,
|
||||||
|
ADD COLUMN `intern_position` VARCHAR(255) NULL,
|
||||||
|
MODIFY `id_mahasiswa` INTEGER NULL,
|
||||||
|
MODIFY `id_lamaran_magang` INTEGER NULL;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE `user` ADD CONSTRAINT `user_id_mahasiswa_foreign` FOREIGN KEY (`id_mahasiswa`) REFERENCES `mahasiswa`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE `user` ADD CONSTRAINT `user_id_lamaran_magang_foreign` FOREIGN KEY (`id_lamaran_magang`) REFERENCES `lamaran_magang`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
+15
-13
@@ -179,20 +179,22 @@ model Batch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model User {
|
model User {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
id_mahasiswa Int @unique
|
id_mahasiswa Int? @unique
|
||||||
id_lamaran_magang Int @unique
|
id_lamaran_magang Int? @unique
|
||||||
full_name String @db.VarChar(255)
|
full_name String @db.VarChar(255)
|
||||||
email String @unique @db.VarChar(255)
|
email String @unique @db.VarChar(255)
|
||||||
password String @db.Text
|
password String @db.Text
|
||||||
role Role @default(intern)
|
role Role @default(intern)
|
||||||
professional_bio String? @db.Text
|
professional_bio String? @db.Text
|
||||||
is_active Boolean @default(true)
|
intern_position String? @db.VarChar(255)
|
||||||
created_at DateTime @default(now())
|
intern_interest_group String? @db.VarChar(255)
|
||||||
updated_at DateTime @updatedAt
|
is_active Boolean @default(true)
|
||||||
|
created_at DateTime @default(now())
|
||||||
|
updated_at DateTime @updatedAt
|
||||||
|
|
||||||
mahasiswa Mahasiswa @relation(fields: [id_mahasiswa], references: [id], map: "user_id_mahasiswa_foreign")
|
mahasiswa Mahasiswa? @relation(fields: [id_mahasiswa], references: [id], onDelete: SetNull, map: "user_id_mahasiswa_foreign")
|
||||||
lamaran_magang LamaranMagang @relation(fields: [id_lamaran_magang], references: [id], map: "user_id_lamaran_magang_foreign")
|
lamaran_magang LamaranMagang? @relation(fields: [id_lamaran_magang], references: [id], onDelete: SetNull, map: "user_id_lamaran_magang_foreign")
|
||||||
project_members ProjectMember[]
|
project_members ProjectMember[]
|
||||||
task_submissions TaskSubmission[]
|
task_submissions TaskSubmission[]
|
||||||
certificates Certificate[]
|
certificates Certificate[]
|
||||||
|
|||||||
@@ -172,6 +172,32 @@ class LamaranMagangController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async deleteAllLamaranMagang(req, res) {
|
||||||
|
try {
|
||||||
|
const result = await lamaranMagangService.deleteAllLamaranMagang();
|
||||||
|
|
||||||
|
if (result.err) {
|
||||||
|
return response(res, 'fail', result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response(
|
||||||
|
res,
|
||||||
|
'success',
|
||||||
|
result,
|
||||||
|
'Data lamaran magang dan mahasiswa berhasil dibersihkan, batch baru berhasil dibuat',
|
||||||
|
SUCCESS.OK
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
return response(
|
||||||
|
res,
|
||||||
|
'fail',
|
||||||
|
error(new InternalServerError(err.message)),
|
||||||
|
'Terjadi kesalahan yang tidak terduga',
|
||||||
|
ERROR.INTERNAL_ERROR
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async exportLamaranToExcel(req, res) {
|
async exportLamaranToExcel(req, res) {
|
||||||
try {
|
try {
|
||||||
const result = await lamaranMagangService.exportToExcel();
|
const result = await lamaranMagangService.exportToExcel();
|
||||||
|
|||||||
@@ -163,6 +163,134 @@ class LamaranMagangRepository {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async findAllMahasiswaForCleanup() {
|
||||||
|
return prisma.mahasiswa.findMany({
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
cv_path: true,
|
||||||
|
portofolio_path: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteAllLamaranMagang() {
|
||||||
|
return prisma.$transaction(async (tx) => {
|
||||||
|
const internUsers = await tx.user.findMany({
|
||||||
|
where: {
|
||||||
|
role: "intern",
|
||||||
|
id_lamaran_magang: {
|
||||||
|
not: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
intern_position: true,
|
||||||
|
intern_interest_group: true,
|
||||||
|
lamaran_magang: {
|
||||||
|
select: {
|
||||||
|
lowongan_magang: {
|
||||||
|
select: {
|
||||||
|
posisi: true,
|
||||||
|
kelompok_peminatan: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const user of internUsers) {
|
||||||
|
const lowongan = user.lamaran_magang?.lowongan_magang;
|
||||||
|
|
||||||
|
await tx.user.update({
|
||||||
|
where: {
|
||||||
|
id: user.id,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
intern_position: user.intern_position || lowongan?.posisi || null,
|
||||||
|
intern_interest_group:
|
||||||
|
user.intern_interest_group || lowongan?.kelompok_peminatan || null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const detachedUsers = await tx.user.updateMany({
|
||||||
|
where: {
|
||||||
|
OR: [
|
||||||
|
{
|
||||||
|
id_mahasiswa: {
|
||||||
|
not: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id_lamaran_magang: {
|
||||||
|
not: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
id_mahasiswa: null,
|
||||||
|
id_lamaran_magang: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const deletedLamaranMagang = await tx.lamaranMagang.deleteMany({});
|
||||||
|
const deletedMahasiswa = await tx.mahasiswa.deleteMany({});
|
||||||
|
|
||||||
|
const latestBatch = await tx.batch.findFirst({
|
||||||
|
orderBy: {
|
||||||
|
batch_number: "desc",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const previousActiveBatch = await tx.batch.findFirst({
|
||||||
|
where: {
|
||||||
|
is_active: true,
|
||||||
|
},
|
||||||
|
orderBy: {
|
||||||
|
batch_number: "desc",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (previousActiveBatch) {
|
||||||
|
await tx.batch.update({
|
||||||
|
where: {
|
||||||
|
id: previousActiveBatch.id,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
is_active: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const newBatch = await tx.batch.create({
|
||||||
|
data: {
|
||||||
|
batch_number: latestBatch ? latestBatch.batch_number + 1 : 1,
|
||||||
|
is_active: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
detached_users: detachedUsers.count,
|
||||||
|
deleted_lamaran_magang: deletedLamaranMagang.count,
|
||||||
|
deleted_mahasiswa: deletedMahasiswa.count,
|
||||||
|
previous_active_batch: previousActiveBatch
|
||||||
|
? {
|
||||||
|
id: previousActiveBatch.id,
|
||||||
|
batch_number: previousActiveBatch.batch_number,
|
||||||
|
is_active: false,
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
new_active_batch: {
|
||||||
|
id: newBatch.id,
|
||||||
|
batch_number: newBatch.batch_number,
|
||||||
|
is_active: newBatch.is_active,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async getStatistics({ query }) {
|
async getStatistics({ query }) {
|
||||||
let where = {};
|
let where = {};
|
||||||
|
|
||||||
@@ -238,7 +366,7 @@ class LamaranMagangRepository {
|
|||||||
: Prisma.sql``;
|
: Prisma.sql``;
|
||||||
|
|
||||||
const data = await prisma.$queryRaw`
|
const data = await prisma.$queryRaw`
|
||||||
SELECT
|
SELECT
|
||||||
m.negara,
|
m.negara,
|
||||||
COUNT(*) as total,
|
COUNT(*) as total,
|
||||||
SUM(lm.status = 'diterima') as diterima,
|
SUM(lm.status = 'diterima') as diterima,
|
||||||
@@ -266,7 +394,7 @@ class LamaranMagangRepository {
|
|||||||
: Prisma.sql``;
|
: Prisma.sql``;
|
||||||
|
|
||||||
const data = await prisma.$queryRaw`
|
const data = await prisma.$queryRaw`
|
||||||
SELECT
|
SELECT
|
||||||
m.universitas,
|
m.universitas,
|
||||||
COUNT(*) as total,
|
COUNT(*) as total,
|
||||||
SUM(lm.status = 'diterima') as diterima,
|
SUM(lm.status = 'diterima') as diterima,
|
||||||
|
|||||||
@@ -202,6 +202,10 @@ class LamaranMagangService {
|
|||||||
|
|
||||||
let generatedPassword = null;
|
let generatedPassword = null;
|
||||||
if (status === "diterima") {
|
if (status === "diterima") {
|
||||||
|
// Menyimpan posisi dan kelompok peminatan intern
|
||||||
|
const internPosition = checkResult.data.lowongan_magang?.posisi || null;
|
||||||
|
const internInterestGroup = checkResult.data.lowongan_magang?.kelompok_peminatan || null;
|
||||||
|
|
||||||
const existingUser = await prisma.user.findFirst({
|
const existingUser = await prisma.user.findFirst({
|
||||||
where: {
|
where: {
|
||||||
OR: [
|
OR: [
|
||||||
@@ -227,9 +231,21 @@ class LamaranMagangService {
|
|||||||
email: checkResult.data.mahasiswa.email,
|
email: checkResult.data.mahasiswa.email,
|
||||||
password: hashedPassword,
|
password: hashedPassword,
|
||||||
role: "intern",
|
role: "intern",
|
||||||
|
intern_position: internPosition,
|
||||||
|
intern_interest_group: internInterestGroup,
|
||||||
is_active: true,
|
is_active: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
await prisma.user.update({
|
||||||
|
where: {
|
||||||
|
id: existingUser.id,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
intern_position: existingUser.intern_position || internPosition,
|
||||||
|
intern_interest_group:existingUser.intern_interest_group || internInterestGroup,
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,6 +283,43 @@ class LamaranMagangService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async deleteAllLamaranMagang() {
|
||||||
|
try {
|
||||||
|
const mahasiswaList = await lamaranMagangRepository.findAllMahasiswaForCleanup();
|
||||||
|
const result = await lamaranMagangRepository.deleteAllLamaranMagang();
|
||||||
|
|
||||||
|
let deletedFiles = 0;
|
||||||
|
const failedFiles = [];
|
||||||
|
|
||||||
|
for (const mahasiswa of mahasiswaList) {
|
||||||
|
const files = [mahasiswa.cv_path, mahasiswa.portofolio_path];
|
||||||
|
|
||||||
|
for (const filePath of files) {
|
||||||
|
if (!filePath) continue;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const fileName = path.basename(filePath);
|
||||||
|
await deleteFileIfExists(fileName);
|
||||||
|
deletedFiles += 1;
|
||||||
|
} catch (fileErr) {
|
||||||
|
failedFiles.push({
|
||||||
|
file_path: filePath,
|
||||||
|
error: fileErr.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return data({
|
||||||
|
...result,
|
||||||
|
deleted_files: deletedFiles,
|
||||||
|
failed_files: failedFiles,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
return error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async exportToExcel() {
|
async exportToExcel() {
|
||||||
try {
|
try {
|
||||||
const lamaranList = await lamaranMagangRepository.findAll({});
|
const lamaranList = await lamaranMagangRepository.findAll({});
|
||||||
|
|||||||
@@ -441,35 +441,87 @@ router.delete('/delete/:id', verifyJWT, isAdmin, lamaranMagangController.deleteL
|
|||||||
* @swagger
|
* @swagger
|
||||||
* /lamaran-magang-api/delete:
|
* /lamaran-magang-api/delete:
|
||||||
* delete:
|
* delete:
|
||||||
* summary: Delete all internship applications, students, and their files
|
* summary: Reset internship applications and create new active batch
|
||||||
|
* description: Delete all internship applications and mahasiswa data, detach existing intern accounts from deleted application records, preserve intern accounts and their position snapshots, deactivate the previous active batch, and create one new active batch. Only admin can access this endpoint.
|
||||||
* tags: [Lamaran Magang]
|
* tags: [Lamaran Magang]
|
||||||
* security:
|
* security:
|
||||||
* - bearerAuth: []
|
* - bearerAuth: []
|
||||||
* responses:
|
* responses:
|
||||||
* 200:
|
* 200:
|
||||||
* description: All student data, applications, and files were successfully deleted.
|
* description: Application data reset successfully and new batch created.
|
||||||
* content:
|
* content:
|
||||||
* application/json:
|
* application/json:
|
||||||
* schema:
|
* schema:
|
||||||
* type: object
|
* type: object
|
||||||
* properties:
|
* properties:
|
||||||
|
* status:
|
||||||
|
* type: boolean
|
||||||
|
* example: true
|
||||||
|
* data:
|
||||||
|
* type: object
|
||||||
|
* properties:
|
||||||
|
* detached_users:
|
||||||
|
* type: integer
|
||||||
|
* example: 10
|
||||||
|
* deleted_lamaran_magang:
|
||||||
|
* type: integer
|
||||||
|
* example: 25
|
||||||
|
* deleted_mahasiswa:
|
||||||
|
* type: integer
|
||||||
|
* example: 25
|
||||||
|
* deleted_files:
|
||||||
|
* type: integer
|
||||||
|
* example: 50
|
||||||
|
* failed_files:
|
||||||
|
* type: array
|
||||||
|
* items:
|
||||||
|
* type: object
|
||||||
|
* properties:
|
||||||
|
* file_path:
|
||||||
|
* type: string
|
||||||
|
* example: /uploads/cv/example.pdf
|
||||||
|
* error:
|
||||||
|
* type: string
|
||||||
|
* example: File not found
|
||||||
|
* previous_active_batch:
|
||||||
|
* type: object
|
||||||
|
* nullable: true
|
||||||
|
* properties:
|
||||||
|
* id:
|
||||||
|
* type: integer
|
||||||
|
* example: 3
|
||||||
|
* batch_number:
|
||||||
|
* type: integer
|
||||||
|
* example: 3
|
||||||
|
* is_active:
|
||||||
|
* type: boolean
|
||||||
|
* example: false
|
||||||
|
* new_active_batch:
|
||||||
|
* type: object
|
||||||
|
* properties:
|
||||||
|
* id:
|
||||||
|
* type: integer
|
||||||
|
* example: 4
|
||||||
|
* batch_number:
|
||||||
|
* type: integer
|
||||||
|
* example: 4
|
||||||
|
* is_active:
|
||||||
|
* type: boolean
|
||||||
|
* example: true
|
||||||
* message:
|
* message:
|
||||||
* type: string
|
* type: string
|
||||||
* example: All student data, applications, and files were successfully deleted.
|
* example: Data lamaran magang dan mahasiswa berhasil dibersihkan, batch baru berhasil dibuat
|
||||||
|
* code:
|
||||||
|
* type: integer
|
||||||
|
* example: 200
|
||||||
|
* 401:
|
||||||
|
* description: Unauthorized
|
||||||
|
* 403:
|
||||||
|
* description: Forbidden. Only admin can access this endpoint.
|
||||||
* 500:
|
* 500:
|
||||||
* description: Failed to delete all data
|
* description: Failed to reset internship application data.
|
||||||
* content:
|
|
||||||
* application/json:
|
|
||||||
* schema:
|
|
||||||
* type: object
|
|
||||||
* properties:
|
|
||||||
* message:
|
|
||||||
* type: string
|
|
||||||
* example: Failed to delete all data.
|
|
||||||
* error:
|
|
||||||
* type: string
|
|
||||||
*/
|
*/
|
||||||
// router.delete("/delete", verifyJWT, lamaranMagangController.deleteAllLamaranMagang); // Method doesn't exist
|
router.delete("/delete", verifyJWT, isAdmin, lamaranMagangController.deleteAllLamaranMagang);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @swagger
|
* @swagger
|
||||||
@@ -683,4 +735,4 @@ router.get('/statistics/university', verifyJWT, isAdmin, lamaranMagangController
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|||||||
Reference in New Issue
Block a user