2025-11-26 03:27:39 +00:00
|
|
|
import { BadRequestException, Injectable } from '@nestjs/common';
|
2025-11-25 09:26:00 +00:00
|
|
|
import { PrismaService } from '../prisma/prisma.service';
|
|
|
|
|
import { ActiveUserPayload } from '../auth/decorator/current-user.decorator';
|
|
|
|
|
import { RekammedisService } from '../rekammedis/rekammedis.service';
|
|
|
|
|
import { CreateRekamMedisDto } from '../rekammedis/dto/create-rekammedis.dto';
|
2025-11-26 04:29:27 +00:00
|
|
|
import { TindakanDokterService } from '../tindakandokter/tindakandokter.service';
|
2025-11-26 08:28:04 +00:00
|
|
|
import { ObatService } from '../obat/obat.service';
|
2025-11-25 09:26:00 +00:00
|
|
|
|
|
|
|
|
@Injectable()
|
|
|
|
|
export class ValidationService {
|
|
|
|
|
constructor(
|
|
|
|
|
private prisma: PrismaService,
|
|
|
|
|
private rekamMedisService: RekammedisService,
|
2025-11-26 04:29:27 +00:00
|
|
|
private tindakanDokterService: TindakanDokterService,
|
2025-11-26 08:28:04 +00:00
|
|
|
private obatService: ObatService,
|
2025-11-25 09:26:00 +00:00
|
|
|
) {}
|
|
|
|
|
|
|
|
|
|
private handlers: Record<
|
|
|
|
|
string,
|
|
|
|
|
{
|
|
|
|
|
approveCreate?: (queue: any) => Promise<any>;
|
|
|
|
|
approveUpdate?: (queue: any) => Promise<any>;
|
|
|
|
|
approveDelete?: (queue: any) => Promise<any>;
|
|
|
|
|
}
|
|
|
|
|
> = {
|
|
|
|
|
rekam_medis: {
|
|
|
|
|
approveCreate: async (queue: any) => {
|
|
|
|
|
const payload = queue.dataPayload as Partial<CreateRekamMedisDto>;
|
2025-11-26 03:27:39 +00:00
|
|
|
const result =
|
|
|
|
|
await this.rekamMedisService.createRekamMedisToDBAndBlockchain(
|
|
|
|
|
payload as CreateRekamMedisDto,
|
|
|
|
|
Number(queue.user_id_request),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
...result,
|
|
|
|
|
id: result.id_visit,
|
|
|
|
|
};
|
2025-11-25 09:26:00 +00:00
|
|
|
},
|
|
|
|
|
approveUpdate: async (queue: any) => {
|
|
|
|
|
const payload = queue.dataPayload as Partial<CreateRekamMedisDto>;
|
|
|
|
|
return this.rekamMedisService.updateRekamMedisToDBAndBlockchain(
|
|
|
|
|
queue.record_id,
|
|
|
|
|
payload as CreateRekamMedisDto,
|
|
|
|
|
Number(queue.user_id_request),
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
approveDelete: async (queue: any) => {
|
2025-11-28 03:34:50 +00:00
|
|
|
return this.rekamMedisService.deleteRekamMedisFromDBAndBlockchain(
|
|
|
|
|
queue.record_id,
|
|
|
|
|
Number(queue.user_id_request),
|
|
|
|
|
);
|
2025-11-25 09:26:00 +00:00
|
|
|
},
|
|
|
|
|
},
|
2025-11-26 03:27:39 +00:00
|
|
|
pemberian_tindakan: {
|
|
|
|
|
approveCreate: async (queue: any) => {
|
|
|
|
|
const payload = queue.dataPayload;
|
2025-11-26 04:29:27 +00:00
|
|
|
const result =
|
|
|
|
|
await this.tindakanDokterService.createTindakanDokterToDBAndBlockchain(
|
|
|
|
|
payload,
|
|
|
|
|
Number(queue.user_id_request),
|
|
|
|
|
);
|
|
|
|
|
return {
|
|
|
|
|
...result,
|
|
|
|
|
id: result.id,
|
|
|
|
|
};
|
2025-11-26 03:27:39 +00:00
|
|
|
},
|
2025-11-26 06:46:50 +00:00
|
|
|
approveUpdate: async (queue: any) => {
|
|
|
|
|
const payload = queue.dataPayload;
|
|
|
|
|
return await this.tindakanDokterService.updateTindakanDokterToDBAndBlockchain(
|
|
|
|
|
queue.record_id,
|
|
|
|
|
payload,
|
|
|
|
|
Number(queue.user_id_request),
|
|
|
|
|
);
|
|
|
|
|
},
|
2025-11-26 09:33:19 +00:00
|
|
|
approveDelete: async (queue: any) => {
|
|
|
|
|
return this.tindakanDokterService.deleteTindakanDokterFromDBAndBlockchain(
|
|
|
|
|
Number(queue.record_id),
|
|
|
|
|
queue.user_id_request,
|
|
|
|
|
);
|
|
|
|
|
},
|
2025-11-26 03:27:39 +00:00
|
|
|
},
|
2025-11-26 08:28:04 +00:00
|
|
|
pemberian_obat: {
|
|
|
|
|
approveCreate: async (queue: any) => {
|
|
|
|
|
const payload = queue.dataPayload;
|
|
|
|
|
const result = await this.obatService.createObatToDBAndBlockchain(
|
|
|
|
|
payload,
|
|
|
|
|
Number(queue.user_id_request),
|
|
|
|
|
);
|
|
|
|
|
return {
|
|
|
|
|
...result,
|
|
|
|
|
id: result.id,
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
approveUpdate: async (queue: any) => {
|
|
|
|
|
const payload = queue.dataPayload;
|
|
|
|
|
return await this.obatService.updateObatToDBAndBlockchain(
|
|
|
|
|
queue.record_id,
|
|
|
|
|
payload,
|
|
|
|
|
Number(queue.user_id_request),
|
|
|
|
|
);
|
|
|
|
|
},
|
2025-11-28 03:34:50 +00:00
|
|
|
approveDelete: async (queue: any) => {
|
|
|
|
|
return this.obatService.deleteObatFromDBAndBlockchain(
|
|
|
|
|
Number(queue.record_id),
|
|
|
|
|
queue.user_id_request,
|
|
|
|
|
);
|
|
|
|
|
},
|
2025-11-26 08:28:04 +00:00
|
|
|
},
|
2025-11-25 09:26:00 +00:00
|
|
|
};
|
|
|
|
|
|
2025-12-01 04:17:47 +00:00
|
|
|
async getAllValidationsQueue(params: any) {
|
|
|
|
|
const {
|
|
|
|
|
take,
|
|
|
|
|
skip,
|
|
|
|
|
page,
|
|
|
|
|
orderBy,
|
|
|
|
|
order,
|
|
|
|
|
searchIdRecord,
|
|
|
|
|
kelompok_data,
|
|
|
|
|
aksi,
|
|
|
|
|
status,
|
|
|
|
|
} = params;
|
|
|
|
|
const skipValue = skip
|
|
|
|
|
? parseInt(skip.toString())
|
|
|
|
|
: page
|
|
|
|
|
? (parseInt(page.toString()) - 1) * take
|
|
|
|
|
: 0;
|
|
|
|
|
console.log('Params', params);
|
2025-11-25 09:26:00 +00:00
|
|
|
const result = await this.prisma.validation_queue.findMany({
|
2025-12-01 04:17:47 +00:00
|
|
|
take,
|
|
|
|
|
skip: skipValue,
|
|
|
|
|
orderBy: orderBy ? { [orderBy]: order || 'asc' } : { created_at: 'desc' },
|
|
|
|
|
where: {
|
|
|
|
|
record_id: searchIdRecord
|
|
|
|
|
? {
|
|
|
|
|
contains: searchIdRecord,
|
|
|
|
|
}
|
|
|
|
|
: undefined,
|
|
|
|
|
table_name:
|
|
|
|
|
kelompok_data && kelompok_data !== 'all'
|
|
|
|
|
? kelompok_data.toLowerCase()
|
|
|
|
|
: undefined,
|
|
|
|
|
action: aksi && aksi !== 'all' ? aksi.toUpperCase() : undefined,
|
|
|
|
|
status: status && status !== 'all' ? status.toUpperCase() : undefined,
|
|
|
|
|
},
|
2025-11-25 09:26:00 +00:00
|
|
|
});
|
|
|
|
|
const totalCount = await this.prisma.validation_queue.count({
|
2025-12-01 04:17:47 +00:00
|
|
|
where: {
|
|
|
|
|
record_id: searchIdRecord
|
|
|
|
|
? {
|
|
|
|
|
contains: searchIdRecord,
|
|
|
|
|
}
|
|
|
|
|
: undefined,
|
|
|
|
|
table_name:
|
|
|
|
|
kelompok_data && kelompok_data !== 'all'
|
|
|
|
|
? kelompok_data.toLowerCase()
|
|
|
|
|
: undefined,
|
|
|
|
|
action: aksi && aksi !== 'all' ? aksi.toUpperCase() : undefined,
|
|
|
|
|
status: status && status !== 'all' ? status.toUpperCase() : undefined,
|
|
|
|
|
},
|
2025-11-25 09:26:00 +00:00
|
|
|
});
|
|
|
|
|
return { data: result, totalCount };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async getAllValidationQueueDashboard() {
|
|
|
|
|
const result = await this.prisma.validation_queue.findMany({
|
|
|
|
|
where: { status: 'PENDING' },
|
|
|
|
|
take: 5,
|
|
|
|
|
orderBy: { created_at: 'desc' },
|
|
|
|
|
});
|
|
|
|
|
const totalCount = await this.prisma.validation_queue.count({
|
|
|
|
|
where: { status: 'PENDING' },
|
|
|
|
|
});
|
|
|
|
|
return { data: result, totalCount };
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-26 03:27:39 +00:00
|
|
|
// For service internal use
|
2025-11-25 09:26:00 +00:00
|
|
|
async getValidationQueueById(id: number) {
|
|
|
|
|
return await this.prisma.validation_queue.findUnique({
|
|
|
|
|
where: { id },
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-26 03:27:39 +00:00
|
|
|
// For front-end detail view
|
2025-11-25 09:26:00 +00:00
|
|
|
async getValidationQueue(id: number) {
|
|
|
|
|
const result = await this.getValidationQueueById(id);
|
|
|
|
|
if (!result) return null;
|
|
|
|
|
if (result.action !== 'UPDATE') return result;
|
|
|
|
|
const previousLog = await this.fetchPreviousLog(
|
|
|
|
|
result.table_name,
|
|
|
|
|
result.record_id,
|
|
|
|
|
);
|
|
|
|
|
return { previousLog, ...result };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async fetchPreviousLog(tableName: string, recordId: string) {
|
|
|
|
|
if (!recordId) return null;
|
|
|
|
|
switch (tableName) {
|
|
|
|
|
case 'pemberian_tindakan':
|
|
|
|
|
return this.prisma.pemberian_tindakan.findUnique({
|
|
|
|
|
where: { id: Number(recordId) },
|
|
|
|
|
});
|
|
|
|
|
case 'rekam_medis':
|
|
|
|
|
return this.prisma.rekam_medis.findUnique({
|
|
|
|
|
where: { id_visit: recordId },
|
|
|
|
|
});
|
|
|
|
|
case 'pemberian_obat':
|
|
|
|
|
return this.prisma.pemberian_obat.findUnique({
|
|
|
|
|
where: { id: Number(recordId) },
|
|
|
|
|
});
|
|
|
|
|
default:
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async approveWithHandler(queue: any) {
|
|
|
|
|
const handler = this.handlers[queue.table_name];
|
|
|
|
|
if (!handler) throw new Error('Unsupported table');
|
|
|
|
|
switch (queue.action) {
|
|
|
|
|
case 'CREATE':
|
|
|
|
|
if (!handler.approveCreate)
|
|
|
|
|
throw new Error('Create not implemented for table');
|
|
|
|
|
return handler.approveCreate(queue);
|
|
|
|
|
case 'UPDATE':
|
|
|
|
|
if (!handler.approveUpdate)
|
|
|
|
|
throw new Error('Update not implemented for table');
|
|
|
|
|
return handler.approveUpdate(queue);
|
|
|
|
|
case 'DELETE':
|
|
|
|
|
if (!handler.approveDelete)
|
|
|
|
|
throw new Error('Delete not implemented for table');
|
|
|
|
|
return handler.approveDelete(queue);
|
|
|
|
|
default:
|
|
|
|
|
throw new Error('Unknown action');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async approveValidation(id: number, user: ActiveUserPayload) {
|
|
|
|
|
const validationQueue = await this.getValidationQueueById(id);
|
|
|
|
|
if (!validationQueue) {
|
2025-11-26 03:27:39 +00:00
|
|
|
throw new BadRequestException('Validation queue not found');
|
2025-11-25 09:26:00 +00:00
|
|
|
}
|
|
|
|
|
if (!validationQueue.dataPayload) {
|
|
|
|
|
throw new Error('Data payload is missing');
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
const approvalResult = await this.approveWithHandler(validationQueue);
|
2025-11-26 03:27:39 +00:00
|
|
|
console.log('Approval result:', approvalResult);
|
2025-11-25 09:26:00 +00:00
|
|
|
const updated = await this.prisma.validation_queue.update({
|
|
|
|
|
where: { id: validationQueue.id },
|
|
|
|
|
data: {
|
2025-11-28 03:34:50 +00:00
|
|
|
record_id:
|
|
|
|
|
validationQueue.table_name === 'rekam_medis'
|
|
|
|
|
? approvalResult.id_visit
|
|
|
|
|
: approvalResult.id.toString(),
|
2025-11-25 09:26:00 +00:00
|
|
|
status: 'APPROVED',
|
|
|
|
|
user_id_process: Number(user.sub),
|
|
|
|
|
processed_at: new Date(),
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
return { ...updated, approvalResult };
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Error approving validation:', (error as Error).message);
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-28 09:14:38 +00:00
|
|
|
determineIdType(tableName: string, recordId: string) {
|
|
|
|
|
if (tableName === 'rekam_medis') {
|
|
|
|
|
return recordId;
|
|
|
|
|
} else if (
|
|
|
|
|
tableName === 'pemberian_tindakan' ||
|
|
|
|
|
tableName === 'pemberian_obat'
|
|
|
|
|
) {
|
|
|
|
|
return Number(recordId); // numeric ID
|
|
|
|
|
} else {
|
|
|
|
|
throw new Error('Unsupported table for ID determination');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-25 09:26:00 +00:00
|
|
|
async rejectValidation(id: number, user: ActiveUserPayload) {
|
|
|
|
|
const validationQueue = await this.getValidationQueueById(id);
|
|
|
|
|
if (!validationQueue) {
|
|
|
|
|
throw new Error('Validation queue not found');
|
|
|
|
|
}
|
2025-11-28 09:14:38 +00:00
|
|
|
|
|
|
|
|
let recordId: number | string = '';
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
validationQueue.status === 'PENDING' &&
|
|
|
|
|
validationQueue.action === 'DELETE'
|
|
|
|
|
) {
|
|
|
|
|
recordId = this.determineIdType(
|
|
|
|
|
validationQueue.table_name,
|
|
|
|
|
validationQueue.record_id,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const rejectedResponse = await this.prisma.$transaction(async (tx) => {
|
|
|
|
|
let updatedDeleteStatus = null;
|
|
|
|
|
if (validationQueue.action === 'DELETE') {
|
|
|
|
|
switch (validationQueue.table_name) {
|
|
|
|
|
case 'rekam_medis':
|
|
|
|
|
updatedDeleteStatus = await tx.rekam_medis.update({
|
|
|
|
|
where: { id_visit: recordId as string },
|
|
|
|
|
data: { deleted_status: null },
|
|
|
|
|
});
|
|
|
|
|
break;
|
|
|
|
|
case 'pemberian_tindakan':
|
|
|
|
|
updatedDeleteStatus = await tx.pemberian_tindakan.update({
|
|
|
|
|
where: { id: recordId as number },
|
|
|
|
|
data: { deleted_status: null },
|
|
|
|
|
});
|
|
|
|
|
break;
|
|
|
|
|
case 'pemberian_obat':
|
|
|
|
|
updatedDeleteStatus = await tx.pemberian_obat.update({
|
|
|
|
|
where: { id: recordId as number },
|
|
|
|
|
data: { deleted_status: null },
|
|
|
|
|
});
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
throw new Error('Unsupported table for delete rejection');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const updatedQueue = await tx.validation_queue.update({
|
|
|
|
|
where: { id: validationQueue.id },
|
|
|
|
|
data: {
|
|
|
|
|
status: 'REJECTED',
|
|
|
|
|
user_id_process: Number(user.sub),
|
|
|
|
|
processed_at: new Date(),
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
return {
|
|
|
|
|
...updatedQueue,
|
|
|
|
|
updatedDeleteStatus,
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
return rejectedResponse;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Error rejecting validation:', (error as Error).message);
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
2025-11-25 09:26:00 +00:00
|
|
|
}
|
|
|
|
|
}
|