update OCR KTP

parent 7c74f037
......@@ -87,8 +87,8 @@ class AuditLog(models.Model):
timestamp = models.DateTimeField(auto_now_add=True, db_index=True)
class Meta:
verbose_name = _('Audit Log (Rekam Jejak MPWN)')
verbose_name_plural = _('Audit Logs (Rekam Jejak MPWN)')
verbose_name = _('Audit Trail MPWN')
verbose_name_plural = _('Audit Trail MPWN')
ordering = ['-timestamp']
def __str__(self):
......
......@@ -48,13 +48,37 @@ def status_badge_class(status_code):
@register.filter(name='urlencode_text')
def urlencode_text(value):
"""
URL encode teks untuk tautan pesan WhatsApp wa.me/?text=...
URL encode teks untuk tautan WhatsApp wa.me/?text=...
"""
if not value:
return ''
return urllib.parse.quote(str(value))
_SISTEM_LABEL_ID = {
'Group results': 'Hasil Grup',
'Task results': 'Hasil Task',
'Groups': 'Grup',
'Tasks': 'Tugas',
'group results': 'Hasil Grup',
'task results': 'Hasil Task',
'group result': 'Hasil Grup',
'task result': 'Hasil Task',
'Celery Results': 'Hasil Celery',
'Django Celery Results': 'Hasil Celery',
}
@register.filter(name='bahasa')
def bahasa(value):
"""
Terjemahkan label admin berbahasa Inggris (umumnya dari modul pihak ketiga
seperti django_celery_results) ke Bahasa Indonesia pada lapisan penyajian.
Nilai yang tidak dikenal dikembalikan apa adanya.
"""
return _SISTEM_LABEL_ID.get(str(value), value)
@register.simple_tag
def get_legal_dashboard_stats(user=None):
"""
......
This diff is collapsed.
......@@ -224,6 +224,37 @@ class OCRAndEKYCPipelineTest(TestCase):
self.assertEqual(res.data['data']['extracted_data']['nik'], '3171012304850001')
self.assertEqual(res.data['data']['extracted_data']['nama'], 'AHMAD FAUZI')
def test_ocr_scan_real_ktp_image_file(self):
"""Uji scan OCR berkas citra digital KTP asli via API."""
import os
from django.core.files.uploadedfile import SimpleUploadedFile
photo_path = '/Users/trendy/Downloads/photo_2026-07-10 10.55.05.jpeg'
if not os.path.exists(photo_path):
self.skipTest("Berkas foto KTP pengujian tidak ditemukan di Downloads.")
with open(photo_path, 'rb') as f:
uploaded_img = SimpleUploadedFile(
name='ktp_test.jpg',
content=f.read(),
content_type='image/jpeg'
)
client = APIClient()
client.force_authenticate(user=self.staff)
res = client.post('/api/v1/ocr/scan/', {
'file': uploaded_img,
'doc_type': 'KTP'
}, format='multipart')
self.assertEqual(res.status_code, status.HTTP_200_OK)
self.assertEqual(res.data['status'], 'success')
extracted = res.data['data']['extracted_data']
self.assertEqual(extracted['nik'], '3277031812900014')
self.assertIn('TRENDY DWI ANUGRAH', extracted['nama'])
self.assertEqual(extracted['jenis_kelamin'], 'LAKI-LAKI')
self.assertEqual(extracted['status_perkawinan'], 'KAWIN')
self.assertEqual(extracted['pekerjaan'], 'KARYAWAN SWASTA')
def test_kbli_check_api_endpoint(self):
"""US-06: Endpoint API validasi KBLI 2020."""
client = APIClient()
......
......@@ -143,7 +143,8 @@ class OCRScanView(APIView):
pass
return Response({
'status': 'success',
'status': 'success' if result.get('status') == 'SUCCESS' else 'error',
'message': result.get('message', ''),
'data': result
}, status=status.HTTP_200_OK)
......
......@@ -848,6 +848,13 @@ fieldset.module h2 {
color: #ffffff !important;
}
#toggle-nav-sidebar:focus-visible,
.toggle-nav-sidebar:focus-visible {
box-shadow: inset 0 0 0 2px #3b82f6 !important;
outline: 1px solid transparent !important;
outline-offset: -2px !important;
}
.toggle-icon {
font-size: 16px;
font-weight: 700;
......@@ -911,7 +918,7 @@ fieldset.module h2 {
margin-bottom: 10px;
}
.sidebar-workdesk-link {
#nav-sidebar .sidebar-workdesk-link {
display: flex;
align-items: center;
justify-content: space-between;
......@@ -920,6 +927,7 @@ fieldset.module h2 {
border-radius: 6px;
padding: 7px 10px;
text-decoration: none;
color: #5fa3c7;
transition: all 0.15s ease;
}
......
......@@ -9,7 +9,7 @@
{% block extrastyle %}
{{ block.super }}
<link rel="stylesheet" href="{% static 'admin/css/custom_admin.css' %}?v=2.4">
<link rel="stylesheet" href="{% static 'admin/css/custom_admin.css' %}?v=2.5">
{% endblock %}
{% block extrahead %}
......
{% extends "admin/change_list.html" %}
{% load i18n admin_urls static admin_list %}
{% load i18n admin_urls static admin_list legal_tags %}
{% block extrastyle %}
{{ block.super }}
......@@ -29,3 +29,13 @@
}
</style>
{% endblock %}
{% if not is_popup %}
{% block breadcrumbs %}
<div class="breadcrumbs">
<a href="{% url 'admin:index' %}">{% translate 'Home' %}</a>
&rsaquo; <a href="{% url 'admin:app_list' app_label=cl.opts.app_label %}">{{ cl.opts.app_config.verbose_name|bahasa }}</a>
&rsaquo; {{ cl.opts.verbose_name_plural|capfirst|bahasa }}
</div>
{% endblock %}
{% endif %}
......@@ -356,7 +356,7 @@
{% elif app.app_label == 'ahu_integration' %}
🏛 &nbsp;Ditjen AHU & BPN
{% else %}
📁 &nbsp;<span class="app-system-tag">Sistem Teknis</span> {{ app.name }}
📁 &nbsp;<span class="app-system-tag">Sistem Teknis</span> {{ app.name|bahasa }}
{% endif %}
</h2>
<a href="{{ app.app_url }}" style="font-size:11px;color:var(--slate-500);text-decoration:none;font-weight:600;">Lihat Semua →</a>
......@@ -367,9 +367,9 @@
<tr>
<td class="model-link">
{% if model.admin_url %}
<a href="{{ model.admin_url }}">{{ model.name }}</a>
<a href="{{ model.admin_url }}">{{ model.name|bahasa }}</a>
{% else %}
{{ model.name }}
{{ model.name|bahasa }}
{% endif %}
</td>
<td class="action-links" style="text-align:right;">
......
......@@ -292,7 +292,8 @@
<!-- Toggle Password Visibility Button -->
<button type="button"
@click="isPasswordVisible = !isPasswordVisible"
class="absolute inset-y-0 right-0 flex items-center pr-3.5 text-slate-400 hover:text-judicial-navy transition-colors focus:outline-none"
:aria-label="isPasswordVisible ? 'Sembunyikan kata sandi' : 'Tampilkan kata sandi'"
class="absolute inset-y-0 right-0 flex items-center pr-3.5 text-slate-400 hover:text-judicial-navy transition-colors focus:outline-none focus:ring-2 focus:ring-judicial-gold rounded-judicial"
tabindex="-1"
title="Tampilkan/Sembunyikan Kata Sandi">
<!-- Eye Off Icon -->
......
{% load i18n static %}
{% load i18n static legal_tags %}
<button class="sticky toggle-nav-sidebar" id="toggle-nav-sidebar" aria-label="{% translate 'Toggle navigation' %}">
<span class="toggle-icon">‹</span>
......@@ -50,7 +50,7 @@
<th scope="row">
<a href="{{ model.admin_url }}" {% if model.admin_url in request.path|urlencode %}aria-current="page"{% endif %}>
<span class="model-bullet"></span>
<span class="model-name">{{ model.name }}</span>
<span class="model-name">{{ model.name|bahasa }}</span>
</a>
</th>
{% if model.add_url %}
......@@ -83,7 +83,7 @@
<th scope="row">
<a href="{{ model.admin_url }}" {% if model.admin_url in request.path|urlencode %}aria-current="page"{% endif %}>
<span class="model-bullet"></span>
<span class="model-name">{{ model.name }}</span>
<span class="model-name">{{ model.name|bahasa }}</span>
</a>
</th>
{% if model.add_url %}
......@@ -116,7 +116,7 @@
<th scope="row">
<a href="{{ model.admin_url }}" {% if model.admin_url in request.path|urlencode %}aria-current="page"{% endif %}>
<span class="model-bullet"></span>
<span class="model-name">{{ model.name }}</span>
<span class="model-name">{{ model.name|bahasa }}</span>
</a>
</th>
{% if model.add_url %}
......@@ -149,7 +149,7 @@
<th scope="row">
<a href="{{ model.admin_url }}" {% if model.admin_url in request.path|urlencode %}aria-current="page"{% endif %}>
<span class="model-bullet"></span>
<span class="model-name">{{ model.name }}</span>
<span class="model-name">{{ model.name|bahasa }}</span>
</a>
</th>
{% if model.add_url %}
......@@ -173,7 +173,7 @@
<div class="app-group app-group-system">
<div class="app-group-header">
<span class="group-icon">⚙️</span>
<span class="group-title">{{ app.name }}</span>
<span class="group-title">{{ app.name|bahasa }}</span>
</div>
<table class="nav-table">
<tbody>
......@@ -182,7 +182,7 @@
<th scope="row">
<a href="{{ model.admin_url }}" {% if model.admin_url in request.path|urlencode %}aria-current="page"{% endif %}>
<span class="model-bullet"></span>
<span class="model-name">{{ model.name }}</span>
<span class="model-name">{{ model.name|bahasa }}</span>
</a>
</th>
{% if model.add_url %}
......
{% load admin_list %}
{% load i18n legal_tags %}
<p class="paginator">
{% if pagination_required %}
{% for i in page_range %}
{% paginator_number cl i %}
{% endfor %}
{% endif %}
{{ cl.result_count }} {% if cl.result_count == 1 %}{{ cl.opts.verbose_name|bahasa }}{% else %}{{ cl.opts.verbose_name_plural|bahasa }}{% endif %}
{% if show_all_url %}<a href="{{ show_all_url }}" class="showall">{% translate 'Show all' %}</a>{% endif %}
{% if cl.formset and cl.result_count %}<input type="submit" name="_save" class="default" value="{% translate 'Save' %}">{% endif %}
</p>
\ No newline at end of file
{% load i18n static %}
{% if cl.search_fields %}
<div id="toolbar"><form id="changelist-search" method="get" role="search">
<div><!-- DIV needed for valid HTML -->
<label for="searchbar"><img src="{% static "admin/img/search.svg" %}" alt="" aria-hidden="true"><span style="position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0;">{% translate "Cari dalam daftar" %}</span></label>
<input type="text" size="40" name="{{ search_var }}" value="{{ cl.query }}" id="searchbar" aria-label="{% translate "Cari dalam daftar" %}"{% if cl.search_help_text %} aria-describedby="searchbar_helptext"{% endif %}>
<input type="submit" value="{% translate 'Search' %}">
{% if show_result_count %}
<span class="small quiet">{% blocktranslate count counter=cl.result_count %}{{ counter }} result{% plural %}{{ counter }} results{% endblocktranslate %} (<a href="?{% if cl.is_popup %}{{ is_popup_var }}=1{% if cl.add_facets %}&{% endif %}{% endif %}{% if cl.add_facets %}{{ is_facets_var }}{% endif %}">{% if cl.show_full_result_count %}{% blocktranslate with full_result_count=cl.full_result_count %}{{ full_result_count }} total{% endblocktranslate %}{% else %}{% translate "Show all" %}{% endif %}</a>)</span>
{% endif %}
{% for pair in cl.params.items %}
{% if pair.0 != search_var %}<input type="hidden" name="{{ pair.0 }}" value="{{ pair.1 }}">{% endif %}
{% endfor %}
</div>
{% if cl.search_help_text %}
<br class="clear">
<div class="help" id="searchbar_helptext">{{ cl.search_help_text }}</div>
{% endif %}
</form></div>
{% endif %}
\ No newline at end of file
......@@ -1014,6 +1014,11 @@
})
.then(d => {
ocrIsProcessing = false;
if (d.status === 'error' || (d.data && d.data.status === 'FAILED')) {
ocrError = d.message || (d.data && d.data.message) || 'Teks dokumen tidak terbaca.';
ocrResult = null;
return;
}
ocrResult = d.data;
})
.catch(err => {
......@@ -1057,7 +1062,7 @@
</div>
<div class="flex justify-between p-1 bg-white rounded border border-slate-200">
<span class="text-slate-500 font-sans">Alamat:</span>
<span class="text-slate-800 text-right truncate max-w-[220px]" x-text="ocrResult.extracted_data.alamat || '-'"></span>
<span class="text-slate-800 text-right truncate max-w-[240px]" :title="ocrResult.extracted_data.alamat_lengkap || ocrResult.extracted_data.alamat" x-text="ocrResult.extracted_data.alamat_lengkap || ocrResult.extracted_data.alamat || '-'"></span>
</div>
<div class="grid grid-cols-2 gap-1 pt-0.5">
<div class="p-1 bg-white rounded border border-slate-200">
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment