feat: validasi update obat for admin

This commit is contained in:
yosaphatprs 2025-11-26 15:28:04 +07:00
parent 1cab0a2348
commit 3b209ca566
7 changed files with 191 additions and 77 deletions

View File

@ -61,7 +61,7 @@ export class ObatController {
@Body() dto: UpdateObatDto, @Body() dto: UpdateObatDto,
@CurrentUser() user: ActiveUserPayload, @CurrentUser() user: ActiveUserPayload,
) { ) {
return await this.obatService.updateObatById(id, dto, user); return await this.obatService.updateObat(id, dto, user);
} }
@Get('/:id/log') @Get('/:id/log')

View File

@ -157,6 +157,7 @@ describe('ObatService', () => {
describe('updateObatById', () => { describe('updateObatById', () => {
const updatePayload: UpdateObatDto = { const updatePayload: UpdateObatDto = {
id_visit: 'VISIT-1',
obat: 'Ibuprofen', obat: 'Ibuprofen',
jumlah_obat: 1, jumlah_obat: 1,
aturan_pakai: '2x1', aturan_pakai: '2x1',
@ -165,12 +166,12 @@ describe('ObatService', () => {
it('updates obat and stores log', async () => { it('updates obat and stores log', async () => {
prisma.pemberian_obat.update.mockResolvedValueOnce({ prisma.pemberian_obat.update.mockResolvedValueOnce({
id: 99, id: 99,
id_visit: 'VISIT-1',
...updatePayload, ...updatePayload,
id_visit: 'VISIT-1',
}); });
logService.storeLog.mockResolvedValueOnce({ txId: 'updated' }); logService.storeLog.mockResolvedValueOnce({ txId: 'updated' });
const result = await service.updateObatById(99, updatePayload, mockUser); const result = await service.updateObat(99, updatePayload, mockUser);
expect(prisma.pemberian_obat.update).toHaveBeenCalledWith({ expect(prisma.pemberian_obat.update).toHaveBeenCalledWith({
where: { id: 99 }, where: { id: 99 },

View File

@ -18,8 +18,6 @@ export class ObatService {
} }
determineStatus(rawFabricLog: any, index: number, arrLength: number): any { determineStatus(rawFabricLog: any, index: number, arrLength: number): any {
console.log('Inside determineStatus with index:', index);
console.log('Raw Fabric Log:', rawFabricLog);
const flatLog = { const flatLog = {
...rawFabricLog.value, ...rawFabricLog.value,
txId: rawFabricLog.txId, txId: rawFabricLog.txId,
@ -102,6 +100,7 @@ export class ObatService {
const latestPayload = rawLogs[0].value.payload; const latestPayload = rawLogs[0].value.payload;
const isTampered = currentDataHash !== latestPayload; const isTampered = currentDataHash !== latestPayload;
console.log(latestPayload, currentDataHash, isTampered);
const chronologicalLogs = [...rawLogs]; const chronologicalLogs = [...rawLogs];
@ -116,72 +115,162 @@ export class ObatService {
}; };
} }
async createObat(dto: CreateObatDto, user: ActiveUserPayload) { async isIdVisitExists(id_visit: string) {
const visitExists = await this.prisma.rekam_medis.findUnique({ const existing = await this.prisma.rekam_medis.findUnique({
where: { id_visit: dto.id_visit }, where: { id_visit: id_visit },
}); });
return !!existing;
}
if (!visitExists) { async createObat(dto: CreateObatDto, user: ActiveUserPayload) {
if (!(await this.isIdVisitExists(dto.id_visit))) {
throw new BadRequestException(`ID Visit ${dto.id_visit} tidak ditemukan`);
}
try {
return await this.prisma.validation_queue.create({
data: {
table_name: 'pemberian_obat',
action: 'CREATE',
dataPayload: {
...dto,
},
status: 'PENDING',
user_id_request: Number(user.sub),
},
});
} catch (error) {
console.error('Error creating Obat:', error);
throw error;
}
}
async createObatToDBAndBlockchain(dto: CreateObatDto, userId: number) {
if (!(await this.isIdVisitExists(dto.id_visit))) {
throw new BadRequestException(`Visit with id ${dto.id_visit} not found`); throw new BadRequestException(`Visit with id ${dto.id_visit} not found`);
} }
const res = await this.prisma.pemberian_obat.create({ try {
data: { const createObat = await this.prisma.$transaction(async (tx) => {
id_visit: dto.id_visit, const res = await tx.pemberian_obat.create({
obat: dto.obat, data: {
jumlah_obat: dto.jumlah_obat, ...dto,
aturan_pakai: dto.aturan_pakai, },
}, });
}); const logPayload = JSON.stringify({
obat: dto.obat,
console.log('Created Obat:', dto); jumlah_obat: dto.jumlah_obat,
aturan_pakai: dto.aturan_pakai,
const logPayload = JSON.stringify(dto); });
const payloadHash = sha256(logPayload); const payloadHash = sha256(logPayload);
const data = { const data = {
id: `OBAT_${res.id}`, id: `OBAT_${res.id}`,
event: 'obat_created', event: 'obat_created',
user_id: user.sub, user_id: userId,
payload: payloadHash, payload: payloadHash,
}; };
const logResult = await this.logService.storeLog(data); const logResult = await this.logService.storeLog(data);
return { return {
...res, ...res,
...logResult, ...logResult,
}; };
});
return createObat;
} catch (error) {
console.error('Error creating Obat:', error);
throw error;
}
} }
async updateObatById( async updateObatToDBAndBlockchain(
id: number, id: number,
dto: UpdateObatDto, dto: UpdateObatDto,
user: ActiveUserPayload, userId: number,
) { ) {
const res = await this.prisma.pemberian_obat.update({ const obatId = Number(id);
where: {
id: id,
},
data: {
obat: dto.obat,
jumlah_obat: dto.jumlah_obat,
aturan_pakai: dto.aturan_pakai,
},
});
console.log('Updated Obat:', dto); if (isNaN(obatId)) {
throw new BadRequestException('ID obat tidak valid');
}
const logPayload = JSON.stringify(dto); if (!(await this.getObatById(obatId))) {
const payloadHash = sha256(logPayload); throw new BadRequestException(`Obat with id ${obatId} not found`);
const data = { }
id: `OBAT_${id}`,
event: 'obat_updated', try {
user_id: user.sub, const updateObat = await this.prisma.$transaction(async (tx) => {
payload: payloadHash, const updatedObat = await tx.pemberian_obat.update({
}; where: { id: obatId },
const logResult = await this.logService.storeLog(data); data: {
return { ...dto,
...res, },
...logResult, });
}; const logPayload = JSON.stringify({
obat: dto.obat,
jumlah_obat: dto.jumlah_obat,
aturan_pakai: dto.aturan_pakai,
});
const payloadHash = sha256(logPayload);
const data = {
id: `OBAT_${updatedObat.id}`,
event: 'obat_updated',
user_id: userId.toString(),
payload: payloadHash,
};
const logResult = await this.logService.storeLog(data);
return {
...updatedObat,
...logResult,
};
});
return updateObat;
} catch (error) {
console.error('Error updating Obat:', error);
throw error;
}
}
async updateObat(id: number, dto: UpdateObatDto, user: ActiveUserPayload) {
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(
`Pemberian obat dengan ID ${obatId} tidak ditemukan`,
);
}
const hasUpdates =
dto.obat !== existingObat.obat ||
dto.jumlah_obat !== existingObat.jumlah_obat ||
dto.aturan_pakai !== existingObat.aturan_pakai;
if (!hasUpdates) {
throw new BadRequestException('Tidak ada perubahan data obat');
}
try {
return await this.prisma.validation_queue.create({
data: {
table_name: 'pemberian_obat',
action: 'UPDATE',
dataPayload: {
...dto,
},
record_id: obatId.toString(),
user_id_request: Number(user.sub),
status: 'PENDING',
},
});
} catch (error) {
console.error('Error updating Obat:', error);
throw error;
}
} }
async countObat() { async countObat() {

View File

@ -5,9 +5,16 @@ import { PrismaModule } from '../prisma/prisma.module';
import { LogModule } from '../log/log.module'; import { LogModule } from '../log/log.module';
import { RekamMedisModule } from '../rekammedis/rekammedis.module'; import { RekamMedisModule } from '../rekammedis/rekammedis.module';
import { TindakanDokterModule } from '../tindakandokter/tindakandokter.module'; import { TindakanDokterModule } from '../tindakandokter/tindakandokter.module';
import { ObatModule } from '../obat/obat.module';
@Module({ @Module({
imports: [PrismaModule, LogModule, RekamMedisModule, TindakanDokterModule], imports: [
PrismaModule,
LogModule,
RekamMedisModule,
TindakanDokterModule,
ObatModule,
],
controllers: [ValidationController], controllers: [ValidationController],
providers: [ValidationService], providers: [ValidationService],
exports: [ValidationService], exports: [ValidationService],

View File

@ -4,6 +4,7 @@ import { ActiveUserPayload } from '../auth/decorator/current-user.decorator';
import { RekammedisService } from '../rekammedis/rekammedis.service'; import { RekammedisService } from '../rekammedis/rekammedis.service';
import { CreateRekamMedisDto } from '../rekammedis/dto/create-rekammedis.dto'; import { CreateRekamMedisDto } from '../rekammedis/dto/create-rekammedis.dto';
import { TindakanDokterService } from '../tindakandokter/tindakandokter.service'; import { TindakanDokterService } from '../tindakandokter/tindakandokter.service';
import { ObatService } from '../obat/obat.service';
@Injectable() @Injectable()
export class ValidationService { export class ValidationService {
@ -11,6 +12,7 @@ export class ValidationService {
private prisma: PrismaService, private prisma: PrismaService,
private rekamMedisService: RekammedisService, private rekamMedisService: RekammedisService,
private tindakanDokterService: TindakanDokterService, private tindakanDokterService: TindakanDokterService,
private obatService: ObatService,
) {} ) {}
private handlers: Record< private handlers: Record<
@ -71,7 +73,27 @@ export class ValidationService {
); );
}, },
}, },
pemberian_obat: {}, 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),
);
},
},
}; };
async getAllValidationsQueue() { async getAllValidationsQueue() {

View File

@ -221,13 +221,6 @@ onMounted(async () => {
</svg> </svg>
<span>Berhasil memperbarui data!</span> <span>Berhasil memperbarui data!</span>
</div> </div>
<!-- <div class="text-sm">
<p>ID: {{ data?.id }}</p>
<p>ID Visit: {{ data?.id_visit }}</p>
<p>Obat: {{ data?.obat }}</p>
<p>Jumlah Obat: {{ data?.jumlah_obat }}</p>
<p>Aturan Pakai: {{ data?.aturan_pakai }}</p>
</div> -->
<div> <div>
<form @submit.prevent="handleSubmit"> <form @submit.prevent="handleSubmit">
<FieldInput <FieldInput
@ -266,14 +259,16 @@ onMounted(async () => {
v-model="data.aturan_pakai" v-model="data.aturan_pakai"
:is-disabled="false" :is-disabled="false"
/> />
<ButtonDark <div class="flex justify-end">
type="submit" <ButtonDark
:text="isLoading ? 'Menyimpan...' : 'Simpan Perubahan'" type="submit"
class="mt-4" :text="isLoading ? 'Menyimpan...' : 'Simpan Perubahan'"
:isLoading="isLoading" class="mt-4"
> :isLoading="isLoading"
Simpan Perubahan >
</ButtonDark> Simpan Perubahan
</ButtonDark>
</div>
</form> </form>
</div> </div>
<hr /> <hr />

View File

@ -65,7 +65,7 @@ const normalizedData = (rawData: any[]): ValidationLog[] => {
return "Rekam Medis"; return "Rekam Medis";
case "pemberian_tindakan": case "pemberian_tindakan":
return "Tindakan Dokter"; return "Tindakan Dokter";
case "obat": case "pemberian_obat":
return "Obat"; return "Obat";
default: default:
return tableName; return tableName;