feat: validation delete tindakan

This commit is contained in:
yosaphatprs 2025-11-28 10:34:50 +07:00
parent 4d4565299d
commit cbcf4ad897
7 changed files with 232 additions and 39 deletions

View File

@ -112,6 +112,7 @@ export class AuditService {
).filter((record): record is AuditRecordPayload => record !== null);
if (records.length > 0) {
console.log(records);
await this.prisma.$transaction(
records.map((record) =>
this.prisma.audit.upsert({
@ -121,7 +122,7 @@ export class AuditService {
event: record.event,
payload: record.payload,
timestamp: record.timestamp,
user_id: record.user_id,
user_id: BigInt(record.user_id),
last_sync: record.last_sync,
result: record.result,
},
@ -133,7 +134,6 @@ export class AuditService {
if (nextBookmark === '' || nextBookmark === bookmark) {
const completeData = { status: 'COMPLETED' };
this.logger.log('Mengirim selesai via WebSocket:', completeData);
this.auditGateway.sendComplete(completeData);
break;
}
@ -213,6 +213,7 @@ export class AuditService {
const timestamp = this.parseTimestamp(value.timestamp) ?? now;
const userId = value.user_id;
const blockchainHash: string | undefined = value.payload;
let data: any = null;
if (!blockchainHash) {
return null;
@ -224,6 +225,7 @@ export class AuditService {
const obatId = this.extractNumericId(logId);
if (obatId !== null) {
const obat = await this.obatService.getObatById(obatId);
data = obat;
if (obat) {
dbHash = this.obatService.createHashingPayload({
obat: obat.obat,
@ -237,6 +239,7 @@ export class AuditService {
if (rekamMedisId) {
const rekamMedis =
await this.rekamMedisService.getRekamMedisById(rekamMedisId);
data = rekamMedis;
if (rekamMedis) {
dbHash = this.rekamMedisService.createHashingPayload({
dokter_id: 123,
@ -252,6 +255,7 @@ export class AuditService {
if (tindakanId !== null) {
const tindakanDokter =
await this.tindakanDokterService.getTindakanDokterById(tindakanId);
data = tindakanDokter;
if (tindakanDokter) {
dbHash = this.tindakanDokterService.createHashingPayload({
id_visit: tindakanDokter.id_visit,
@ -270,10 +274,13 @@ export class AuditService {
let isNotTampered = false;
const eventType = logEntry.value.event?.split('_').at(-1);
const isDeleteEvent = eventType === 'deleted';
const hasRow = Boolean(data);
if (eventType === 'deleted') {
isNotTampered = true;
console.log('Detected deleted event, marking as not tempered');
if (!hasRow) {
isNotTampered = isDeleteEvent;
} else if (isDeleteEvent || data.deleted_status === 'DELETED') {
isNotTampered = isDeleteEvent && data.deleted_status === 'DELETED';
} else {
const hashesMatch =
dbHash && (await this.compareData(blockchainHash, dbHash));
@ -287,7 +294,7 @@ export class AuditService {
progress_count: index ?? 0,
};
this.logger.log('Mengirim progres via WebSocket:', progressData);
// this.logger.log('Mengirim progres via WebSocket:', progressData);
this.auditGateway.sendProgress(progressData);
return {

View File

@ -1,6 +1,7 @@
import {
Body,
Controller,
Delete,
Get,
Param,
Post,
@ -69,4 +70,13 @@ export class ObatController {
async getObatLogs(@Param('id') id: string) {
return await this.obatService.getLogObatById(id);
}
@Delete(':id')
@UseGuards(AuthGuard)
async deleteObatById(
@Param('id') id: number,
@CurrentUser() user: ActiveUserPayload,
) {
return await this.obatService.deleteObat(id, user);
}
}

View File

@ -56,6 +56,11 @@ export class ObatService {
take: take,
where: {
obat: obat ? { contains: obat } : undefined,
OR: [
{ deleted_status: null },
{ deleted_status: 'DELETE_VALIDATION' },
{ deleted_status: { not: 'DELETED' } },
],
},
orderBy: orderBy
? { [Object.keys(orderBy)[0]]: order || 'asc' }
@ -65,6 +70,11 @@ export class ObatService {
const count = await this.prisma.pemberian_obat.count({
where: {
obat: obat ? { contains: obat } : undefined,
OR: [
{ deleted_status: null },
{ deleted_status: 'DELETE_VALIDATION' },
{ deleted_status: { not: 'DELETED' } },
],
},
});
@ -273,6 +283,89 @@ export class ObatService {
}
}
async deleteObat(id: number, user: ActiveUserPayload) {
const existingObat = await this.getObatById(id);
if (!existingObat) {
throw new BadRequestException(`Obat with id ${id} not found`);
}
try {
const response = await this.prisma.$transaction(async (tx) => {
const createdValidationQueue =
await this.prisma.validation_queue.create({
data: {
table_name: 'pemberian_obat',
action: 'DELETE',
dataPayload: {
...existingObat,
},
record_id: id.toString(),
user_id_request: Number(user.sub),
status: 'PENDING',
},
});
const updatedObat = await tx.pemberian_obat.update({
where: { id: id },
data: {
deleted_status: 'DELETE_VALIDATION',
},
});
return createdValidationQueue;
});
return response;
} catch (error) {
console.error('Error deleting Obat:', error);
throw error;
}
}
async deleteObatFromDBAndBlockchain(id: number, userId: number) {
const obatId = Number(id);
if (isNaN(obatId)) {
throw new BadRequestException('ID obat tidak valid');
}
const existingObat = await this.getObatById(obatId);
if (!existingObat) {
throw new BadRequestException(`Obat dengan ID ${obatId} tidak ditemukan`);
}
try {
const deleteObat = await this.prisma.$transaction(async (tx) => {
const deletedObat = await tx.pemberian_obat.update({
where: { id: obatId },
data: {
deleted_status: 'DELETED',
},
});
const logPayload = JSON.stringify({
obat: existingObat.obat,
jumlah_obat: existingObat.jumlah_obat,
aturan_pakai: existingObat.aturan_pakai,
});
const payloadHash = sha256(logPayload);
const data = {
id: `OBAT_${deletedObat.id}`,
event: 'obat_deleted',
user_id: userId.toString(),
payload: payloadHash,
};
const logResult = await this.logService.storeLog(data);
return {
...deletedObat,
...logResult,
};
});
return deleteObat;
} catch (error) {
console.error('Error deleting Obat:', error);
throw error;
}
}
async countObat() {
return this.prisma.pemberian_obat.count();
}

View File

@ -195,6 +195,11 @@ export class RekammedisService {
: undefined,
jenis_kelamin: jkCharacter ? { equals: jkCharacter } : undefined,
kode_diagnosa: kode_diagnosa ? { contains: kode_diagnosa } : undefined,
OR: [
{ deleted_status: null },
{ deleted_status: 'DELETE_VALIDATION' },
{ deleted_status: { not: 'DELETED' } },
],
...golDarahFilter,
...tindakLanjutFilter,
};
@ -469,7 +474,8 @@ export class RekammedisService {
throw new Error(`Rekam Medis with id_visit ${id_visit} not found`);
}
try {
const response = await this.prisma.validation_queue.create({
const response = await this.prisma.$transaction(async (tx) => {
const createdQueue = await tx.validation_queue.create({
data: {
table_name: 'rekam_medis',
action: 'DELETE',
@ -479,6 +485,18 @@ export class RekammedisService {
status: 'PENDING',
},
});
const updatedRekamMedis = await tx.rekam_medis.update({
where: { id_visit },
data: {
deleted_status: 'DELETE_VALIDATION',
},
});
return {
...createdQueue,
rekam_medis: updatedRekamMedis,
};
});
return response;
} catch (error) {
console.error('Error deleting validation queue:', error);
@ -486,11 +504,40 @@ export class RekammedisService {
}
}
async deleteRekamMedisFromDB(id_visit: string) {
async deleteRekamMedisFromDBAndBlockchain(id_visit: string, userId: number) {
const existing = await this.getRekamMedisById(id_visit);
if (!existing) {
throw new Error(`Rekam Medis with id_visit ${id_visit} not found`);
}
try {
const deletedRekamMedis = await this.prisma.rekam_medis.delete({
const deletedRekamMedis = await this.prisma.$transaction(async (tx) => {
const deleted = await tx.rekam_medis.update({
data: {
deleted_status: 'DELETED',
},
where: { id_visit },
});
const logPayload = {
dokter_id: 123,
visit_id: id_visit,
anamnese: deleted.anamnese,
jenis_kasus: deleted.jenis_kasus,
tindak_lanjut: deleted.tindak_lanjut,
};
const logPayloadString = JSON.stringify(logPayload);
const payloadHash = sha256(logPayloadString);
const logDto = {
id: `REKAM_${id_visit}`,
event: 'rekam_medis_deleted',
user_id: userId.toString(),
payload: payloadHash,
};
await this.log.storeLog(logDto);
return deleted;
});
return deletedRekamMedis;
} catch (error) {
console.error('Error deleting Rekam Medis:', error);

View File

@ -79,6 +79,11 @@ export class TindakanDokterService {
kategori_tindakanArray.length > 0
? { in: kategori_tindakanArray }
: undefined,
OR: [
{ deleted_status: null },
{ deleted_status: 'DELETE_VALIDATION' },
{ deleted_status: { not: 'DELETED' } },
],
},
orderBy: orderBy
? { [Object.keys(orderBy)[0]]: order || 'asc' }
@ -331,7 +336,9 @@ export class TindakanDokterService {
);
}
return this.prisma.validation_queue.create({
try {
const validationQueue = await this.prisma.$transaction(async (tx) => {
const queue = await tx.validation_queue.create({
data: {
table_name: 'pemberian_tindakan',
action: 'DELETE',
@ -341,6 +348,24 @@ export class TindakanDokterService {
status: 'PENDING',
},
});
const updatedTindakan = await tx.pemberian_tindakan.update({
where: { id: tindakanId },
data: {
deleted_status: 'DELETE_VALIDATION',
},
});
return {
...queue,
tindakan: updatedTindakan,
};
});
return validationQueue;
} catch (error) {
console.error('Error deleting Tindakan Dokter:', error);
throw error;
}
}
async deleteTindakanDokterFromDBAndBlockchain(id: number, userId: number) {
@ -359,8 +384,9 @@ export class TindakanDokterService {
try {
const deletedTindakan = await this.prisma.$transaction(async (tx) => {
const deleted = await tx.pemberian_tindakan.delete({
const deleted = await tx.pemberian_tindakan.update({
where: { id: tindakanId },
data: { deleted_status: 'DELETED' },
});
const logPayload = JSON.stringify(deleted);
const payloadHash = sha256(logPayload);

View File

@ -46,7 +46,10 @@ export class ValidationService {
);
},
approveDelete: async (queue: any) => {
return this.rekamMedisService.deleteRekamMedisFromDB(queue.record_id);
return this.rekamMedisService.deleteRekamMedisFromDBAndBlockchain(
queue.record_id,
Number(queue.user_id_request),
);
},
},
pemberian_tindakan: {
@ -97,6 +100,12 @@ export class ValidationService {
Number(queue.user_id_request),
);
},
approveDelete: async (queue: any) => {
return this.obatService.deleteObatFromDBAndBlockchain(
Number(queue.record_id),
queue.user_id_request,
);
},
},
};
@ -196,7 +205,10 @@ export class ValidationService {
const updated = await this.prisma.validation_queue.update({
where: { id: validationQueue.id },
data: {
record_id: approvalResult.id.toString(),
record_id:
validationQueue.table_name === 'rekam_medis'
? approvalResult.id_visit
: approvalResult.id.toString(),
status: 'APPROVED',
user_id_process: Number(user.sub),
processed_at: new Date(),

View File

@ -155,7 +155,6 @@ const handleUpdate = (item: ObatData) => {
};
const handleDelete = async (item: ObatData) => {
if (confirm(`Apakah Anda yakin ingin menghapus obat "${item.obat}"?`)) {
try {
await api.delete(`/obat/${item.id}`);
await fetchData();
@ -163,7 +162,6 @@ const handleDelete = async (item: ObatData) => {
console.error("Error deleting obat:", error);
alert("Gagal menghapus data obat");
}
}
};
watch([() => pagination.page.value], () => {