Compare commits

..

2 Commits

Author SHA1 Message Date
marszayn 86647ef04e update: scanner submit struk 2025-08-12 10:33:17 +07:00
marszayn b5056edfa4 update: cover image submit foto kendaraan 2025-08-12 10:08:51 +07:00
3 changed files with 380 additions and 300 deletions

View File

@ -35,7 +35,7 @@
<div class="relative w-full h-60 bg-center bg-gray-100 rounded-xl flex items-center justify-center mx-auto mb-3 overflow-hidden preview-container" id="preview-container">
<div id="default-state">
<div class="upload-icon-container">
<img src="~/driver/images/trukk.jpg" alt="contoh gambar">
<img class="object-cover" src="~/driver/images/trukk.jpg" alt="contoh gambar">
<p class="absolute inset-0 flex items-center justify-center text-white">Contoh Foto</p>
@* <i class="w-8 h-8 text-orange-600" data-lucide="upload-cloud" id="preview-icon"></i> *@
</div>

View File

@ -7,7 +7,7 @@
<link rel="stylesheet" href="@Url.Content("~/driver/css/scanner.css")" asp-append-version="true" />
}
<div class="w-full lg:max-w-sm mx-auto bg-white min-h-screen">
<div class="max-w-sm mx-auto bg-white min-h-screen">
<div class="bg-orange-500 text-white px-3 py-4 rounded-b-2xl relative pb-12">
<div class="flex items-center justify-between">
<a href="@Url.Action("Index", "Home")" class="p-1 hover:bg-white/10 rounded-full transition-colors">
@ -26,16 +26,26 @@
</div>
<h2 class="text-xl font-bold text-orange-500">Scan Struk Otomatis</h2>
<p class="text-sm text-gray-500 text-center">Arahkan kamera ke struk atau upload foto struk untuk membaca data secara otomatis.</p>
</div>
<div id="ocr-processing" class="hidden bg-yellow-50 border border-yellow-200 rounded-lg p-3">
<div id="ocr-processing" class="hidden bg-yellow-50 border border-yellow-200 rounded-lg p-3 mb-4">
<div class="flex items-center">
<div class="loading-spinner-small mr-2"></div>
<span class="text-yellow-800 text-sm">Memproses teks dari struk...</span>
</div>
</div>
<div id="scan-success" class="hidden bg-green-50 border border-green-200 rounded-lg p-3 mb-4">
<div class="flex items-center">
<i class="w-5 h-5 text-green-600 mr-2" data-lucide="check-circle"></i>
<span class="text-green-800 font-medium">Data struk berhasil diisi otomatis!</span>
</div>
<p class="text-green-700 text-sm mt-1">Scanning dihentikan. Periksa form di bawah dan lengkapi data jika diperlukan.</p>
<p class="text-green-700 text-xs mt-1">💡 Untuk scan ulang, "Upload Foto Struk"</p>
</div>
<div class="scanner-container mb-4" style="height: 300px;">
<div id="scanner-container" class="w-full h-full relative bg-gray-900 rounded-lg overflow-hidden">
<div id="loading-scanner" class="absolute inset-0 bg-gray-900 flex items-center justify-center z-10">
@ -48,10 +58,10 @@
</div>
<div class="space-y-3 mb-4">
<button id="start-scanner" type="button" class="w-full bg-orange-500 hover:bg-orange-600 text-white font-medium py-3 px-4 rounded-lg transition-colors btn-scanner">
@* <button id="start-scanner" type="button" class="w-full bg-orange-500 hover:bg-orange-600 text-white font-medium py-3 px-4 rounded-lg transition-colors btn-scanner">
<i class="w-5 h-5 inline mr-2" data-lucide="camera"></i>
Mulai Scan Struk
</button>
</button> *@
<button id="stop-scanner" type="button" class="w-full bg-gray-500 hover:bg-gray-600 text-white font-medium py-3 px-4 rounded-lg transition-colors btn-scanner hidden">
<i class="w-5 h-5 inline mr-2" data-lucide="camera-off"></i>
@ -59,11 +69,11 @@
</button>
<div class="flex items-center">
@* <div class="flex items-center">
<div class="flex-1 border-t border-gray-200"></div>
<span class="px-3 text-sm text-gray-500 bg-white">atau</span>
<div class="flex-1 border-t border-gray-200"></div>
</div>
</div> *@
<label for="file-upload" class="w-full bg-blue-500 hover:bg-blue-600 text-white font-medium py-3 px-4 rounded-lg transition-colors cursor-pointer flex items-center justify-center gap-2 upload-label">
<i class="w-5 h-5" data-lucide="upload"></i>
Upload Foto Struk
@ -110,17 +120,6 @@
</div>
<div id="scan-success" class="hidden bg-green-50 border border-green-200 rounded-lg p-3">
<div class="flex items-center">
<i class="w-5 h-5 text-green-600 mr-2" data-lucide="check-circle"></i>
<span class="text-green-800 font-medium">Data struk berhasil diisi otomatis!</span>
</div>
<p class="text-green-700 text-sm mt-1">Scanning dihentikan. Periksa form di bawah dan lengkapi data jika diperlukan.</p>
<p class="text-green-700 text-xs mt-1">💡 Untuk scan ulang, klik "Mulai Scan Struk" atau "Upload Foto Struk"</p>
</div>
</div>
</div>
@ -253,7 +252,7 @@
</div>
<register-block dynamic-section="scripts" key="jsSubmitStruk">
<script src="https://cdn.jsdelivr.net/npm/tesseract.js@5.1.1/dist/tesseract.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/tesseract.js@4.1.1/dist/tesseract.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
const nomorStrukInput = document.getElementById('NomorStruk');
@ -667,150 +666,239 @@
}
if (!truckNumber) {
for (const line of lines) {
if (line.toLowerCase().includes('no truk')) {
const match = line.match(/no\s*truk\s*:?\s*(.+)/i);
if (match && match[1]) {
truckNumber = match[1].trim();
console.log(`Found truck number: ${truckNumber} (ambil semua karakter setelah label No Truk)`);
break;
}
}
}
if (!truckNumber) {
for (const line of lines) {
const match = line.match(/\bB\s*\d{3,5}\s*[A-Z]{2,4}\b/i);
if (match && match[0]) {
truckNumber = match[0].trim();
console.log(`Fallback truck number: ${truckNumber}`);
console.log('No truck number found in context lines, trying all lines...');
for (const line of lines) {
console.log(`Scanning line for truck pattern: "${line}"`);
if (line.match(/[A-Z]\s*\d+\s*[A-Z]{2,3}/i)) {
console.log('Potential truck pattern found:', line);
for (const pattern of truckPatterns) {
const match = line.match(pattern);
if (match) {
let foundTruck = '';
if (match.length === 4 && match[1] && match[2] && match[3]) {
foundTruck = `${match[1]} ${match[2]} ${match[3]}`;
} else if (match[1]) {
foundTruck = match[1].trim();
}
if (foundTruck) {
foundTruck = foundTruck.replace(/([A-Z])(\d+)(\s+[A-Z]{2,3})/g, '$1 $2$3');
foundTruck = foundTruck.replace(/([A-Z]{1,2})(\d{3,4})([A-Z]{2,3})/g, '$1 $2 $3');
foundTruck = foundTruck.replace(/\s+/g, ' ').trim();
if (foundTruck.match(/^[A-Z]{1,2}\s+\d{3,4}\s+[A-Z]{2,3}$/)) {
truckNumber = foundTruck;
console.log(`Found truck number: "${truckNumber}" using general pattern`);
break;
}
}
}
}
if (truckNumber) break;
}
}
}
if (!truckNumber) {
console.log('Still no truck number, trying loose patterns...');
for (const line of lines) {
const looseMatch = line.match(/([A-Z]{1,2})\s*(\d{3,4})\s*([A-Z]{2,3})/i);
if (looseMatch && looseMatch[1] && looseMatch[2] && looseMatch[3]) {
const candidate = `${looseMatch[1].toUpperCase()} ${looseMatch[2]} ${looseMatch[3].toUpperCase()}`;
console.log(`Found potential truck number with loose pattern: "${candidate}"`);
if (looseMatch[1].length <= 2 &&
looseMatch[2].length >= 3 && looseMatch[2].length <= 4 &&
looseMatch[3].length >= 2 && looseMatch[3].length <= 3) {
truckNumber = candidate;
console.log(`Accepted truck number: "${truckNumber}"`);
break;
}
}
}
}
console.log('Truck detection results:', { truckNumber });
console.log('Truck detection result:', truckNumber);
const assignmentPatterns = [
/(?:penugasan|assignment)[\s.:]*([A-Z\s]+?)(?:\n|$)/gi,
/(JAKARTA\s+\w+)/gi, // Specific pattern for Jakarta areas
/(BANDUNG|SURABAYA|MEDAN|SEMARANG|PALEMBANG|MAKASSAR)[\s\w]*/gi,
];
for (const line of lines) {
console.log(`Processing line for assignment: "${line}"`);
if (line.toLowerCase().includes('penugasan')) {
console.log('Found penugasan line:', line);
const match = line.match(/penugasan\s*:?\s*(.+)/i);
if (match && match[1]) {
assignment = match[1].trim();
console.log(`Found assignment: ${assignment} (ambil semua karakter setelah label Penugasan)`);
const assignmentMatch = line.match(/penugasan\s*:\s*(.+)/i);
if (assignmentMatch && assignmentMatch[1]) {
assignment = assignmentMatch[1].trim();
console.log(`Found assignment: ${assignment}`);
break;
}
}
if (line.toUpperCase().includes('JAKARTA') && line.toUpperCase().includes('BARAT')) {
assignment = 'JAKARTA BARAT';
console.log(`Found assignment: ${assignment} from Jakarta Barat line`);
break;
}
for (const pattern of assignmentPatterns) {
const match = line.match(pattern);
if (match && match[1]) {
assignment = match[1].trim();
console.log(`Found assignment: ${assignment} using pattern: ${pattern}`);
break;
}
}
if (assignment) break;
}
console.log('Assignment detection result:', assignment);
// Enhanced time patterns
const timePatterns = [
/(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})/gi, // 2025-08-02 12:35:34 (highest priority)
/(\d{1,2}\s+\w{3}\s+\d{4},\s*\d{1,2}:\d{2}:\d{2})/gi, // 04 Aug 2025, 08:13:51
/(\d{1,2}\s+\w{3}\s+\d{4}\s+\d{1,2}:\d{2}:\d{2})/gi, // 04 Aug 2025 08:13:51
/(\d{1,2}\/\d{1,2}\/\d{4}\s+\d{1,2}:\d{2}:\d{2})/gi, // 04/08/2025 08:13:51
/(\d{1,2}-\d{1,2}-\d{4}\s+\d{1,2}:\d{2}:\d{2})/gi, // 04-08-2025 08:13:51
/(\d{4}-\d{1,2}-\d{1,2},\s*\d{1,2}:\d{2}:\d{2})/gi, // 2025-08-04, 08:13:51
/(\d{4}\/\d{1,2}\/\d{1,2}\s+\d{1,2}:\d{2}:\d{2})/gi, // 2025/08/04 08:13:51
];
const convertDateFormat = (dateString) => {
const monthMap = {
'jan': '01', 'january': '01', 'januari': '01',
'feb': '02', 'february': '02', 'februari': '02',
'mar': '03', 'march': '03', 'maret': '03',
'apr': '04', 'april': '04', 'april': '04',
'may': '05', 'may': '05', 'mei': '05',
'jun': '06', 'june': '06', 'juni': '06',
'jul': '07', 'july': '07', 'juli': '07',
'aug': '08', 'august': '08', 'agustus': '08',
'sep': '09', 'september': '09', 'september': '09',
'oct': '10', 'october': '10', 'oktober': '10',
'nov': '11', 'november': '11', 'november': '11',
'dec': '12', 'december': '12', 'desember': '12'
};
const convertToStandardFormat = (dateTimeString) => {
if (!dateTimeString) return '';
const match = dateString.match(/(\d{1,2})\s+(\w{3,})\s+(\d{4}),?\s*(\d{1,2}:\d{2}:\d{2})/i);
if (match) {
const day = match[1].padStart(2, '0');
const monthName = match[2].toLowerCase();
const year = match[3];
const time = match[4];
console.log('Converting date string:', dateTimeString);
const month = monthMap[monthName];
if (month) {
return `${year}-${month}-${day}, ${time}`;
}
const monthNamePattern = /(\d{1,2})\s+(\w{3})\s+(\d{4})[,\s]+(\d{1,2}:\d{2}:\d{2})/i;
const monthNameMatch = dateTimeString.match(monthNamePattern);
if (monthNameMatch) {
const day = monthNameMatch[1].padStart(2, '0');
const monthName = monthNameMatch[2].toLowerCase();
const year = monthNameMatch[3];
const time = monthNameMatch[4];
const monthMap = {
'jan': '01', 'feb': '02', 'mar': '03', 'apr': '04',
'may': '05', 'jun': '06', 'jul': '07', 'aug': '08',
'sep': '09', 'oct': '10', 'nov': '11', 'dec': '12'
};
const month = monthMap[monthName] || '01';
const converted = `${year}-${month}-${day}, ${time}`;
console.log('Converted month name format:', converted);
return converted;
}
return dateString;
const ddmmyyyyPattern = /(\d{1,2})\/(\d{1,2})\/(\d{4})\s+(\d{1,2}:\d{2}:\d{2})/;
const ddmmyyyyMatch = dateTimeString.match(ddmmyyyyPattern);
if (ddmmyyyyMatch) {
const day = ddmmyyyyMatch[1].padStart(2, '0');
const month = ddmmyyyyMatch[2].padStart(2, '0');
const year = ddmmyyyyMatch[3];
const time = ddmmyyyyMatch[4];
const converted = `${year}-${month}-${day}, ${time}`;
console.log('Converted DD/MM/YYYY format:', converted);
return converted;
}
const ddmmyyyyDashPattern = /(\d{1,2})-(\d{1,2})-(\d{4})\s+(\d{1,2}:\d{2}:\d{2})/;
const ddmmyyyyDashMatch = dateTimeString.match(ddmmyyyyDashPattern);
if (ddmmyyyyDashMatch) {
const day = ddmmyyyyDashMatch[1].padStart(2, '0');
const month = ddmmyyyyDashMatch[2].padStart(2, '0');
const year = ddmmyyyyDashMatch[3];
const time = ddmmyyyyDashMatch[4];
const converted = `${year}-${month}-${day}, ${time}`;
console.log('Converted DD-MM-YYYY format:', converted);
return converted;
}
const yyyymmddSlashPattern = /(\d{4})\/(\d{1,2})\/(\d{1,2})\s+(\d{1,2}:\d{2}:\d{2})/;
const yyyymmddSlashMatch = dateTimeString.match(yyyymmddSlashPattern);
if (yyyymmddSlashMatch) {
const year = yyyymmddSlashMatch[1];
const month = yyyymmddSlashMatch[2].padStart(2, '0');
const day = yyyymmddSlashMatch[3].padStart(2, '0');
const time = yyyymmddSlashMatch[4];
const converted = `${year}-${month}-${day}, ${time}`;
console.log('Converted YYYY/MM/DD format:', converted);
return converted;
}
const standardPattern = /(\d{4}-\d{1,2}-\d{1,2}),?\s*(\d{1,2}:\d{2}:\d{2})/;
const standardMatch = dateTimeString.match(standardPattern);
if (standardMatch) {
const datePart = standardMatch[1];
const timePart = standardMatch[2];
const [year, month, day] = datePart.split('-');
const paddedMonth = month.padStart(2, '0');
const paddedDay = day.padStart(2, '0');
const converted = `${year}-${paddedMonth}-${paddedDay}, ${timePart}`;
console.log('Standardized YYYY-MM-DD format:', converted);
return converted;
}
console.log('No date pattern matched, returning original:', dateTimeString);
return dateTimeString;
};
for (const line of lines) {
console.log(`Processing line for time: "${line}"`);
// Entry time - look for "Masuk :" pattern
if (line.toLowerCase().includes('masuk') && line.includes(':')) {
console.log('Found masuk line:', line);
const masukMatch = line.match(/masuk\s*:\s*(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})/i);
if (masukMatch && masukMatch[1]) {
entryTime = masukMatch[1].trim();
console.log(`Found entry time via masuk pattern: ${entryTime} (format: YYYY-MM-DD HH:MM:SS)`);
} else {
for (const pattern of timePatterns) {
const match = line.match(pattern);
if (match && match[1]) {
const rawTime = match[1].trim();
entryTime = convertDateFormat(rawTime);
console.log(`Found entry time: ${rawTime} -> converted to: ${entryTime}`);
break;
}
for (const pattern of timePatterns) {
const match = line.match(pattern);
if (match && match[1]) {
entryTime = convertToStandardFormat(match[1].trim());
console.log(`Found entry time: ${entryTime}`);
break;
}
}
if (!entryTime) {
const masukMatch = line.match(/masuk\s*:\s*(.+)/i);
if (masukMatch && masukMatch[1]) {
const rawTime = masukMatch[1].trim();
entryTime = convertDateFormat(rawTime);
console.log(`Found entry time via masuk pattern: ${rawTime} -> converted to: ${entryTime}`);
}
// Also try to extract after "Masuk : "
const masukMatch = line.match(/masuk\s*:\s*(.+)/i);
if (masukMatch && masukMatch[1] && !entryTime) {
entryTime = convertToStandardFormat(masukMatch[1].trim());
console.log(`Found entry time via masuk pattern: ${entryTime}`);
}
}
// Exit time - look for "Keluar :" pattern
if (line.toLowerCase().includes('keluar') && line.includes(':')) {
console.log('Found keluar line:', line);
const keluarMatch = line.match(/keluar\s*:\s*(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})/i);
if (keluarMatch && keluarMatch[1]) {
exitTime = keluarMatch[1].trim();
console.log(`Found exit time via keluar pattern: ${exitTime} (format: YYYY-MM-DD HH:MM:SS)`);
} else {
for (const pattern of timePatterns) {
const match = line.match(pattern);
if (match && match[1]) {
const rawTime = match[1].trim();
exitTime = convertDateFormat(rawTime);
console.log(`Found exit time: ${rawTime} -> converted to: ${exitTime}`);
break;
}
for (const pattern of timePatterns) {
const match = line.match(pattern);
if (match && match[1]) {
exitTime = convertToStandardFormat(match[1].trim());
console.log(`Found exit time: ${exitTime}`);
break;
}
}
if (!exitTime) {
const keluarMatch = line.match(/keluar\s*:\s*(.+)/i);
if (keluarMatch && keluarMatch[1]) {
const rawTime = keluarMatch[1].trim();
exitTime = convertDateFormat(rawTime);
console.log(`Found exit time via keluar pattern: ${rawTime} -> converted to: ${exitTime}`);
}
// Also try to extract after "Keluar : "
const keluarMatch = line.match(/keluar\s*:\s*(.+)/i);
if (keluarMatch && keluarMatch[1] && !exitTime) {
exitTime = convertToStandardFormat(keluarMatch[1].trim());
console.log(`Found exit time via keluar pattern: ${exitTime}`);
}
}
}
console.log('Time detection results:', { entryTime, exitTime });
// Enhanced weight patterns - more specific for Indonesian receipts
const weightPatterns = [
/berat\s+masuk\s*:\s*(\d+)\s*kg/gi, // Berat Masuk : 23280 kg
/berat\s+keluar\s*:\s*(\d+)\s*kg/gi, // Berat Keluar : 13540 kg
@ -833,38 +921,41 @@
if (specificMatch) {
const numbers = specificMatch[0].match(/(\d+)/);
if (numbers && numbers[1]) {
weightIn = numbers[1]; // Hanya angka, tanpa "kg"
console.log(`Found weight in via specific pattern: ${weightIn} (removed kg)`);
weightIn = numbers[1];
console.log(`Found weight in via specific pattern: ${weightIn} kg`);
}
} else {
// Try general patterns
for (const pattern of weightPatterns) {
const match = line.match(pattern);
if (match && match[1] && parseInt(match[1]) > 1000) {
weightIn = match[1]; // Hanya angka
console.log(`Found weight in via general pattern: ${weightIn} (removed kg)`);
weightIn = match[1];
console.log(`Found weight in via general pattern: ${weightIn} kg`);
break;
}
}
}
}
// Berat Keluar - look for exact pattern "Berat Keluar : 13540 kg"
if (lowerLine.includes('berat keluar')) {
console.log('Found berat keluar line:', line);
// Try specific pattern first
const specificMatch = line.match(/berat\s+keluar\s*:\s*(\d+)\s*kg/gi);
if (specificMatch) {
const numbers = specificMatch[0].match(/(\d+)/);
if (numbers && numbers[1]) {
weightOut = numbers[1]; // Hanya angka, tanpa "kg"
console.log(`Found weight out via specific pattern: ${weightOut} (removed kg)`);
weightOut = numbers[1];
console.log(`Found weight out via specific pattern: ${weightOut} kg`);
}
} else {
// Try general patterns
for (const pattern of weightPatterns) {
const match = line.match(pattern);
if (match && match[1] && parseInt(match[1]) > 1000) {
weightOut = match[1]; // Hanya angka
console.log(`Found weight out via general pattern: ${weightOut} (removed kg)`);
weightOut = match[1];
console.log(`Found weight out via general pattern: ${weightOut} kg`);
break;
}
}
@ -880,16 +971,16 @@
if (specificMatch) {
const numbers = specificMatch[0].match(/(\d+)/);
if (numbers && numbers[1]) {
weightNett = numbers[1]; // Hanya angka, tanpa "kg"
console.log(`Found weight nett via specific pattern: ${weightNett} (removed kg)`);
weightNett = numbers[1];
console.log(`Found weight nett via specific pattern: ${weightNett} kg`);
}
} else {
// Try general patterns
for (const pattern of weightPatterns) {
const match = line.match(pattern);
if (match && match[1] && parseInt(match[1]) > 100) {
weightNett = match[1]; // Hanya angka
console.log(`Found weight nett via general pattern: ${weightNett} (removed kg)`);
weightNett = match[1];
console.log(`Found weight nett via general pattern: ${weightNett} kg`);
break;
}
}
@ -897,24 +988,6 @@
}
}
// Validasi Berat Nett = Berat Masuk - Berat Keluar
if (weightIn && weightOut && weightNett) {
const calculatedNett = parseInt(weightIn) - parseInt(weightOut);
const detectedNett = parseInt(weightNett);
console.log(`Weight validation: ${weightIn} - ${weightOut} = ${calculatedNett}, detected: ${detectedNett}`);
if (calculatedNett !== detectedNett) {
console.warn(`WARNING: Berat Nett tidak sesuai! Perhitungan: ${calculatedNett}, Terdeteksi: ${detectedNett}`);
// Gunakan hasil perhitungan yang benar
weightNett = calculatedNett.toString();
console.log(`Using calculated weight nett: ${weightNett}`);
// Alert akan ditampilkan di frontend saat apply data
this.weightValidationError = `Berat Nett tidak sesuai! Perhitungan: ${calculatedNett} kg, Terdeteksi: ${detectedNett} kg. Menggunakan hasil perhitungan.`;
}
}
console.log('Weight detection results:', { weightIn, weightOut, weightNett });
console.log('Final Detected Data before assignment:', {
@ -967,15 +1040,9 @@
}
applyDetectedDataDirectly() {
console.log(`=== APPLYING DATA DIRECTLY TO FORM ===`);
console.log('=== APPLYING DATA DIRECTLY TO FORM ===');
console.log('Data to apply:', this.detectedData);
// Tampilkan alert jika ada error validasi berat nett
if (this.weightValidationError) {
alert(this.weightValidationError);
this.weightValidationError = null; // Reset error
}
// Get input elements by ID directly
const nomorStrukInput = document.getElementById('NomorStruk');
const nomorPolisiInput = document.getElementById('NomorPolisi');

View File

@ -7,7 +7,7 @@
<link rel="stylesheet" href="@Url.Content("~/driver/css/scanner.css")" asp-append-version="true" />
}
<div class="max-w-sm mx-auto bg-white min-h-screen">
<div class="w-full lg:max-w-sm mx-auto bg-white min-h-screen">
<div class="bg-orange-500 text-white px-3 py-4 rounded-b-2xl relative pb-12">
<div class="flex items-center justify-between">
<a href="@Url.Action("Index", "Home")" class="p-1 hover:bg-white/10 rounded-full transition-colors">
@ -180,7 +180,7 @@
</div>
<!-- Waktu Masuk dan Keluar -->
<div class="grid grid-cols-2 gap-3">
<div class="grid grid-cols-1 gap-3">
<div>
<label for="WaktuMasuk" class="block text-sm font-medium text-gray-700 mb-1">Masuk</label>
<input
@ -188,7 +188,7 @@
id="WaktuMasuk"
name="WaktuMasuk"
class="mt-1 block w-full rounded-lg border border-orange-300 shadow-sm focus:border-orange-500 focus:ring-2 focus:ring-orange-200 transition-all duration-150 px-4 py-2"
placeholder="04 Aug 2025, 08:13:51"
placeholder="2025-08-04, 08:13:51"
/>
</div>
<div>
@ -198,7 +198,7 @@
id="WaktuKeluar"
name="WaktuKeluar"
class="mt-1 block w-full rounded-lg border border-orange-300 shadow-sm focus:border-orange-500 focus:ring-2 focus:ring-orange-200 transition-all duration-150 px-4 py-2"
placeholder="04 Aug 2025, 14:35:10"
placeholder="2025-08-04, 14:35:10"
/>
</div>
</div>
@ -253,7 +253,7 @@
</div>
<register-block dynamic-section="scripts" key="jsSubmitStruk">
<script src="https://cdn.jsdelivr.net/npm/tesseract.js@4.1.1/dist/tesseract.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/tesseract.js@5.1.1/dist/tesseract.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
const nomorStrukInput = document.getElementById('NomorStruk');
@ -577,36 +577,35 @@
let weightNett = '';
const receiptPatterns = [
/(\d{2})\s+(\d{7,})/gi,
/(\d{2})_(\d{7,})/gi,
/(?:no.*struk|nomor.*struk|receipt)[\s.:]*(\d{7,})/gi,
/(?:^|\s)(\d{7,10})(?:\s|$)/g,
/(\d{2})_(\d{6,})/gi,
/(\d{2})\s+(\d{6,})/gi,
/(?:no.*struk|nomor.*struk|receipt)[\s.:]*(\d{6,})/gi,
/(?:^|\s)(\d{6,10})(?:\s|$)/g,
];
for (const line of lines) {
console.log(`Processing line for receipt: "${line}"`);
const monthNumberMatch = line.match(/(\d{2})\s+(\d{7,})/);
if (monthNumberMatch && monthNumberMatch[2]) {
receiptNumber = monthNumberMatch[2]; // Take the number after space/underscore
console.log(`Found receipt number: ${receiptNumber} using month-number pattern`);
break;
}
const monthUnderscoreMatch = line.match(/(\d{2})_(\d{7,})/);
const monthUnderscoreMatch = line.match(/(\d{2})_(\d{6,})/);
if (monthUnderscoreMatch && monthUnderscoreMatch[2]) {
receiptNumber = monthUnderscoreMatch[2]; // Take the number after underscore
console.log(`Found receipt number: ${receiptNumber} using month-underscore pattern`);
receiptNumber = monthUnderscoreMatch[2];
console.log(`Found receipt number: ${receiptNumber} using month-underscore pattern (removed prefix: ${monthUnderscoreMatch[1]}_)`);
break;
}
// Other patterns
for (const pattern of receiptPatterns.slice(2)) { // Skip first 2 patterns already handled above
const monthSpaceMatch = line.match(/(\d{2})\s+(\d{6,})/);
if (monthSpaceMatch && monthSpaceMatch[2]) {
receiptNumber = monthSpaceMatch[2]; // Take ONLY the number after space
console.log(`Found receipt number: ${receiptNumber} using month-space pattern (removed prefix: ${monthSpaceMatch[1]} )`);
break;
}
for (const pattern of receiptPatterns.slice(2)) {
const matches = [...line.matchAll(pattern)];
for (const match of matches) {
if (match[1] && match[1].length >= 7) {
if (match[1] && match[1].length >= 6) {
receiptNumber = match[1];
console.log(`Found receipt number: ${receiptNumber} using pattern: ${pattern}`);
console.log(`Found receipt number: ${receiptNumber} using context pattern: ${pattern}`);
break;
}
}
@ -668,157 +667,150 @@
}
if (!truckNumber) {
console.log('No truck number found in context lines, trying all lines...');
for (const line of lines) {
console.log(`Scanning line for truck pattern: "${line}"`);
if (line.match(/[A-Z]\s*\d+\s*[A-Z]{2,3}/i)) {
console.log('Potential truck pattern found:', line);
for (const pattern of truckPatterns) {
const match = line.match(pattern);
if (match) {
let foundTruck = '';
if (match.length === 4 && match[1] && match[2] && match[3]) {
foundTruck = `${match[1]} ${match[2]} ${match[3]}`;
} else if (match[1]) {
foundTruck = match[1].trim();
}
if (foundTruck) {
foundTruck = foundTruck.replace(/([A-Z])(\d+)(\s+[A-Z]{2,3})/g, '$1 $2$3');
foundTruck = foundTruck.replace(/([A-Z]{1,2})(\d{3,4})([A-Z]{2,3})/g, '$1 $2 $3');
foundTruck = foundTruck.replace(/\s+/g, ' ').trim();
if (foundTruck.match(/^[A-Z]{1,2}\s+\d{3,4}\s+[A-Z]{2,3}$/)) {
truckNumber = foundTruck;
console.log(`Found truck number: "${truckNumber}" using general pattern`);
for (const line of lines) {
if (line.toLowerCase().includes('no truk')) {
const match = line.match(/no\s*truk\s*:?\s*(.+)/i);
if (match && match[1]) {
truckNumber = match[1].trim();
console.log(`Found truck number: ${truckNumber} (ambil semua karakter setelah label No Truk)`);
break;
}
}
}
if (!truckNumber) {
for (const line of lines) {
const match = line.match(/\bB\s*\d{3,5}\s*[A-Z]{2,4}\b/i);
if (match && match[0]) {
truckNumber = match[0].trim();
console.log(`Fallback truck number: ${truckNumber}`);
break;
}
}
}
}
if (truckNumber) break;
}
}
}
if (!truckNumber) {
console.log('Still no truck number, trying loose patterns...');
for (const line of lines) {
const looseMatch = line.match(/([A-Z]{1,2})\s*(\d{3,4})\s*([A-Z]{2,3})/i);
if (looseMatch && looseMatch[1] && looseMatch[2] && looseMatch[3]) {
const candidate = `${looseMatch[1].toUpperCase()} ${looseMatch[2]} ${looseMatch[3].toUpperCase()}`;
console.log(`Found potential truck number with loose pattern: "${candidate}"`);
if (looseMatch[1].length <= 2 &&
looseMatch[2].length >= 3 && looseMatch[2].length <= 4 &&
looseMatch[3].length >= 2 && looseMatch[3].length <= 3) {
truckNumber = candidate;
console.log(`Accepted truck number: "${truckNumber}"`);
break;
}
}
}
}
console.log('Truck detection results:', { truckNumber });
console.log('Truck detection result:', truckNumber);
const assignmentPatterns = [
/(?:penugasan|assignment)[\s.:]*([A-Z\s]+?)(?:\n|$)/gi,
/(JAKARTA\s+\w+)/gi, // Specific pattern for Jakarta areas
/(BANDUNG|SURABAYA|MEDAN|SEMARANG|PALEMBANG|MAKASSAR)[\s\w]*/gi,
];
for (const line of lines) {
console.log(`Processing line for assignment: "${line}"`);
if (line.toLowerCase().includes('penugasan')) {
console.log('Found penugasan line:', line);
const assignmentMatch = line.match(/penugasan\s*:\s*(.+)/i);
if (assignmentMatch && assignmentMatch[1]) {
assignment = assignmentMatch[1].trim();
console.log(`Found assignment: ${assignment}`);
break;
}
}
if (line.toUpperCase().includes('JAKARTA') && line.toUpperCase().includes('BARAT')) {
assignment = 'JAKARTA BARAT';
console.log(`Found assignment: ${assignment} from Jakarta Barat line`);
break;
}
for (const pattern of assignmentPatterns) {
const match = line.match(pattern);
const match = line.match(/penugasan\s*:?\s*(.+)/i);
if (match && match[1]) {
assignment = match[1].trim();
console.log(`Found assignment: ${assignment} using pattern: ${pattern}`);
console.log(`Found assignment: ${assignment} (ambil semua karakter setelah label Penugasan)`);
break;
}
}
if (assignment) break;
}
console.log('Assignment detection result:', assignment);
// Enhanced time patterns
const timePatterns = [
/(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})/gi, // 2025-08-02 12:35:34 (highest priority)
/(\d{1,2}\s+\w{3}\s+\d{4},\s*\d{1,2}:\d{2}:\d{2})/gi, // 04 Aug 2025, 08:13:51
/(\d{1,2}\s+\w{3}\s+\d{4}\s+\d{1,2}:\d{2}:\d{2})/gi, // 04 Aug 2025 08:13:51
/(\d{1,2}\/\d{1,2}\/\d{4}\s+\d{1,2}:\d{2}:\d{2})/gi, // 04/08/2025 08:13:51
/(\d{1,2}-\d{1,2}-\d{4}\s+\d{1,2}:\d{2}:\d{2})/gi, // 04-08-2025 08:13:51
];
for (const line of lines) {
console.log(`Processing line for time: "${line}"`);
const convertDateFormat = (dateString) => {
const monthMap = {
'jan': '01', 'january': '01', 'januari': '01',
'feb': '02', 'february': '02', 'februari': '02',
'mar': '03', 'march': '03', 'maret': '03',
'apr': '04', 'april': '04', 'april': '04',
'may': '05', 'may': '05', 'mei': '05',
'jun': '06', 'june': '06', 'juni': '06',
'jul': '07', 'july': '07', 'juli': '07',
'aug': '08', 'august': '08', 'agustus': '08',
'sep': '09', 'september': '09', 'september': '09',
'oct': '10', 'october': '10', 'oktober': '10',
'nov': '11', 'november': '11', 'november': '11',
'dec': '12', 'december': '12', 'desember': '12'
};
// Entry time - look for "Masuk :" pattern
if (line.toLowerCase().includes('masuk') && line.includes(':')) {
console.log('Found masuk line:', line);
for (const pattern of timePatterns) {
const match = line.match(pattern);
if (match && match[1]) {
entryTime = match[1].trim();
console.log(`Found entry time: ${entryTime}`);
break;
}
}
// Also try to extract after "Masuk : "
const masukMatch = line.match(/masuk\s*:\s*(.+)/i);
if (masukMatch && masukMatch[1] && !entryTime) {
entryTime = masukMatch[1].trim();
console.log(`Found entry time via masuk pattern: ${entryTime}`);
const match = dateString.match(/(\d{1,2})\s+(\w{3,})\s+(\d{4}),?\s*(\d{1,2}:\d{2}:\d{2})/i);
if (match) {
const day = match[1].padStart(2, '0');
const monthName = match[2].toLowerCase();
const year = match[3];
const time = match[4];
const month = monthMap[monthName];
if (month) {
return `${year}-${month}-${day}, ${time}`;
}
}
// Exit time - look for "Keluar :" pattern
if (line.toLowerCase().includes('keluar') && line.includes(':')) {
console.log('Found keluar line:', line);
for (const pattern of timePatterns) {
const match = line.match(pattern);
if (match && match[1]) {
exitTime = match[1].trim();
console.log(`Found exit time: ${exitTime}`);
break;
return dateString;
};
for (const line of lines) {
console.log(`Processing line for time: "${line}"`);
if (line.toLowerCase().includes('masuk') && line.includes(':')) {
console.log('Found masuk line:', line);
const masukMatch = line.match(/masuk\s*:\s*(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})/i);
if (masukMatch && masukMatch[1]) {
entryTime = masukMatch[1].trim();
console.log(`Found entry time via masuk pattern: ${entryTime} (format: YYYY-MM-DD HH:MM:SS)`);
} else {
for (const pattern of timePatterns) {
const match = line.match(pattern);
if (match && match[1]) {
const rawTime = match[1].trim();
entryTime = convertDateFormat(rawTime);
console.log(`Found entry time: ${rawTime} -> converted to: ${entryTime}`);
break;
}
}
}
// Also try to extract after "Keluar : "
const keluarMatch = line.match(/keluar\s*:\s*(.+)/i);
if (keluarMatch && keluarMatch[1] && !exitTime) {
if (!entryTime) {
const masukMatch = line.match(/masuk\s*:\s*(.+)/i);
if (masukMatch && masukMatch[1]) {
const rawTime = masukMatch[1].trim();
entryTime = convertDateFormat(rawTime);
console.log(`Found entry time via masuk pattern: ${rawTime} -> converted to: ${entryTime}`);
}
}
}
if (line.toLowerCase().includes('keluar') && line.includes(':')) {
console.log('Found keluar line:', line);
const keluarMatch = line.match(/keluar\s*:\s*(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})/i);
if (keluarMatch && keluarMatch[1]) {
exitTime = keluarMatch[1].trim();
console.log(`Found exit time via keluar pattern: ${exitTime}`);
console.log(`Found exit time via keluar pattern: ${exitTime} (format: YYYY-MM-DD HH:MM:SS)`);
} else {
for (const pattern of timePatterns) {
const match = line.match(pattern);
if (match && match[1]) {
const rawTime = match[1].trim();
exitTime = convertDateFormat(rawTime);
console.log(`Found exit time: ${rawTime} -> converted to: ${exitTime}`);
break;
}
}
}
if (!exitTime) {
const keluarMatch = line.match(/keluar\s*:\s*(.+)/i);
if (keluarMatch && keluarMatch[1]) {
const rawTime = keluarMatch[1].trim();
exitTime = convertDateFormat(rawTime);
console.log(`Found exit time via keluar pattern: ${rawTime} -> converted to: ${exitTime}`);
}
}
}
}
console.log('Time detection results:', { entryTime, exitTime });
// Enhanced weight patterns - more specific for Indonesian receipts
const weightPatterns = [
/berat\s+masuk\s*:\s*(\d+)\s*kg/gi, // Berat Masuk : 23280 kg
/berat\s+keluar\s*:\s*(\d+)\s*kg/gi, // Berat Keluar : 13540 kg
@ -841,41 +833,38 @@
if (specificMatch) {
const numbers = specificMatch[0].match(/(\d+)/);
if (numbers && numbers[1]) {
weightIn = numbers[1];
console.log(`Found weight in via specific pattern: ${weightIn} kg`);
weightIn = numbers[1]; // Hanya angka, tanpa "kg"
console.log(`Found weight in via specific pattern: ${weightIn} (removed kg)`);
}
} else {
// Try general patterns
for (const pattern of weightPatterns) {
const match = line.match(pattern);
if (match && match[1] && parseInt(match[1]) > 1000) {
weightIn = match[1];
console.log(`Found weight in via general pattern: ${weightIn} kg`);
weightIn = match[1]; // Hanya angka
console.log(`Found weight in via general pattern: ${weightIn} (removed kg)`);
break;
}
}
}
}
// Berat Keluar - look for exact pattern "Berat Keluar : 13540 kg"
if (lowerLine.includes('berat keluar')) {
console.log('Found berat keluar line:', line);
// Try specific pattern first
const specificMatch = line.match(/berat\s+keluar\s*:\s*(\d+)\s*kg/gi);
if (specificMatch) {
const numbers = specificMatch[0].match(/(\d+)/);
if (numbers && numbers[1]) {
weightOut = numbers[1];
console.log(`Found weight out via specific pattern: ${weightOut} kg`);
weightOut = numbers[1]; // Hanya angka, tanpa "kg"
console.log(`Found weight out via specific pattern: ${weightOut} (removed kg)`);
}
} else {
// Try general patterns
for (const pattern of weightPatterns) {
const match = line.match(pattern);
if (match && match[1] && parseInt(match[1]) > 1000) {
weightOut = match[1];
console.log(`Found weight out via general pattern: ${weightOut} kg`);
weightOut = match[1]; // Hanya angka
console.log(`Found weight out via general pattern: ${weightOut} (removed kg)`);
break;
}
}
@ -891,16 +880,16 @@
if (specificMatch) {
const numbers = specificMatch[0].match(/(\d+)/);
if (numbers && numbers[1]) {
weightNett = numbers[1];
console.log(`Found weight nett via specific pattern: ${weightNett} kg`);
weightNett = numbers[1]; // Hanya angka, tanpa "kg"
console.log(`Found weight nett via specific pattern: ${weightNett} (removed kg)`);
}
} else {
// Try general patterns
for (const pattern of weightPatterns) {
const match = line.match(pattern);
if (match && match[1] && parseInt(match[1]) > 100) {
weightNett = match[1];
console.log(`Found weight nett via general pattern: ${weightNett} kg`);
weightNett = match[1]; // Hanya angka
console.log(`Found weight nett via general pattern: ${weightNett} (removed kg)`);
break;
}
}
@ -908,6 +897,24 @@
}
}
// Validasi Berat Nett = Berat Masuk - Berat Keluar
if (weightIn && weightOut && weightNett) {
const calculatedNett = parseInt(weightIn) - parseInt(weightOut);
const detectedNett = parseInt(weightNett);
console.log(`Weight validation: ${weightIn} - ${weightOut} = ${calculatedNett}, detected: ${detectedNett}`);
if (calculatedNett !== detectedNett) {
console.warn(`WARNING: Berat Nett tidak sesuai! Perhitungan: ${calculatedNett}, Terdeteksi: ${detectedNett}`);
// Gunakan hasil perhitungan yang benar
weightNett = calculatedNett.toString();
console.log(`Using calculated weight nett: ${weightNett}`);
// Alert akan ditampilkan di frontend saat apply data
this.weightValidationError = `Berat Nett tidak sesuai! Perhitungan: ${calculatedNett} kg, Terdeteksi: ${detectedNett} kg. Menggunakan hasil perhitungan.`;
}
}
console.log('Weight detection results:', { weightIn, weightOut, weightNett });
console.log('Final Detected Data before assignment:', {
@ -960,9 +967,15 @@
}
applyDetectedDataDirectly() {
console.log('=== APPLYING DATA DIRECTLY TO FORM ===');
console.log(`=== APPLYING DATA DIRECTLY TO FORM ===`);
console.log('Data to apply:', this.detectedData);
// Tampilkan alert jika ada error validasi berat nett
if (this.weightValidationError) {
alert(this.weightValidationError);
this.weightValidationError = null; // Reset error
}
// Get input elements by ID directly
const nomorStrukInput = document.getElementById('NomorStruk');
const nomorPolisiInput = document.getElementById('NomorPolisi');