From b95e93d3c285f535eaac507e498008e3d2d117aa Mon Sep 17 00:00:00 2001 From: muamars Date: Tue, 17 Mar 2026 13:46:41 +0700 Subject: [PATCH] update: submit struk bg --- .../SubmitController.cs | 264 ++- .../Shared/Components/_PWAinstall.cshtml | 4 +- .../SpjDriverUpst/Submit/Struk.cshtml | 1725 +++++------------ wwwroot/driver/css/watch.css | 51 + wwwroot/driver/favicon.ico | Bin 5430 -> 15406 bytes 5 files changed, 737 insertions(+), 1307 deletions(-) diff --git a/Controllers/SpjDriverUpstController/SubmitController.cs b/Controllers/SpjDriverUpstController/SubmitController.cs index 0f73924..a8f1f0b 100644 --- a/Controllers/SpjDriverUpstController/SubmitController.cs +++ b/Controllers/SpjDriverUpstController/SubmitController.cs @@ -1,10 +1,26 @@ using Microsoft.AspNetCore.Mvc; +using System.Text; +using System.Text.Json; +using System.Text.RegularExpressions; namespace eSPJ.Controllers.SpjDriverUpstController { [Route("upst/submit")] public class SubmitController : Controller { + private readonly IHttpClientFactory _httpClientFactory; + private readonly IConfiguration _configuration; + private readonly IWebHostEnvironment _env; + + public SubmitController( + IHttpClientFactory httpClientFactory, + IConfiguration configuration, + IWebHostEnvironment env) + { + _httpClientFactory = httpClientFactory; + _configuration = configuration; + _env = env; + } [HttpGet("")] public IActionResult Index() @@ -12,70 +28,235 @@ namespace eSPJ.Controllers.SpjDriverUpstController return View("~/Views/Admin/Transport/SpjDriverUpst/Submit/Index.cshtml"); } - [HttpGet("struk")] public IActionResult Struk() { return View("~/Views/Admin/Transport/SpjDriverUpst/Submit/Struk.cshtml"); } + [HttpPost("ocr-struk")] + [IgnoreAntiforgeryToken] + public async Task OcrStruk(IFormFile? Foto) + { + if (Foto == null || Foto.Length == 0) + return BadRequest(new { success = false, message = "Foto tidak ditemukan." }); + + if (Foto.Length > 10 * 1024 * 1024) + return BadRequest(new { success = false, message = "Ukuran foto terlalu besar. Maksimal 10MB." }); + + var apiKey = _configuration["OpenRouter:OCRkey"]; + if (string.IsNullOrWhiteSpace(apiKey)) + return StatusCode(500, new { success = false, message = "OpenRouter API key belum diset." }); + + byte[] fileBytes; + await using (var ms = new MemoryStream()) + { + await Foto.CopyToAsync(ms); + fileBytes = ms.ToArray(); + } + + var mimeType = string.IsNullOrWhiteSpace(Foto.ContentType) ? "image/jpeg" : Foto.ContentType; + var base64 = Convert.ToBase64String(fileBytes); + var dataUrl = $"data:{mimeType};base64,{base64}"; + + var payload = new + { + model = "google/gemini-2.5-flash-image", + temperature = 0, + messages = new object[] + { + new + { + role = "user", + content = new object[] + { + new + { + type = "text", + text = @"Baca data dari foto struk timbang kendaraan ini. Ekstrak semua data yang tersedia ke dalam format JSON berikut: + { + ""nomorStruk"": """", + ""nomorPolisi"": """", + ""penugasan"": """", + ""waktuMasuk"": """", + ""waktuKeluar"": """", + ""beratMasuk"": , + ""beratKeluar"": , + ""beratNett"": + } + + Catatan penting: + - nomorStruk: ambil angka utama saja, abaikan prefix bulan seperti '03_' atau '03 ' + - waktu: konversi ke format YYYY-MM-DD, HH:MM:SS (contoh: 2025-08-04, 08:13:51) + - berat: hanya angka integer tanpa satuan 'kg' + - Jika data tidak ada atau tidak terbaca, isi dengan null + - Kembalikan HANYA JSON valid tanpa penjelasan atau markdown code block" + }, + new + { + type = "image_url", + image_url = new { url = dataUrl } + } + } + } + } + }; + + var json = JsonSerializer.Serialize(payload); + var request = new HttpRequestMessage(HttpMethod.Post, "https://openrouter.ai/api/v1/chat/completions"); + request.Headers.TryAddWithoutValidation("Authorization", $"Bearer {apiKey}"); + request.Headers.TryAddWithoutValidation("Accept", "application/json"); + request.Headers.TryAddWithoutValidation("HTTP-Referer", "https://pesapakawan.dinaslhdki.id"); + request.Headers.TryAddWithoutValidation("X-Title", "eSPJ OCR Struk"); + request.Content = new StringContent(json, Encoding.UTF8, "application/json"); + + var client = _httpClientFactory.CreateClient(); + using var response = await client.SendAsync(request); + var responseText = await response.Content.ReadAsStringAsync(); + + if (!response.IsSuccessStatusCode) + { + return StatusCode((int)response.StatusCode, new + { + success = false, + message = "OpenRouter request gagal.", + detail = responseText + }); + } + + using var doc = JsonDocument.Parse(responseText); + var content = doc.RootElement + .GetProperty("choices")[0] + .GetProperty("message") + .GetProperty("content") + .GetString() ?? ""; + + var jsonMatch = Regex.Match(content, @"\{[\s\S]*\}"); + if (!jsonMatch.Success) + return Ok(new { success = false, message = "AI tidak mengembalikan data valid.", raw = content }); + + try + { + using var dataDoc = JsonDocument.Parse(jsonMatch.Value); + var root = dataDoc.RootElement; + return Ok(new + { + success = true, + data = new + { + nomorStruk = GetStringValue(root, "nomorStruk"), + nomorPolisi = GetStringValue(root, "nomorPolisi"), + penugasan = GetStringValue(root, "penugasan"), + waktuMasuk = GetStringValue(root, "waktuMasuk"), + waktuKeluar = GetStringValue(root, "waktuKeluar"), + beratMasuk = GetIntValue(root, "beratMasuk"), + beratKeluar = GetIntValue(root, "beratKeluar"), + beratNett = GetIntValue(root, "beratNett") + }, + raw = content + }); + } + catch + { + return Ok(new { success = false, message = "Gagal memproses respons AI.", raw = content }); + } + } + [HttpPost("struk")] - public IActionResult ProcessStruk(string NomorStruk, string NomorPolisi, string Penugasan, - string WaktuMasuk, string WaktuKeluar, int? BeratMasuk, int? BeratKeluar, int BeratNett) + public async Task ProcessStruk( + string? NomorStruk, + string? NomorPolisi, + string? Penugasan, + string? WaktuMasuk, + string? WaktuKeluar, + int? BeratMasuk, + int? BeratKeluar, + int? BeratNett, + string? Timbang, + IFormFile? FotoStruk) { try { - // Validate required inputs - if (string.IsNullOrEmpty(NomorStruk) || BeratNett <= 0) + if (string.IsNullOrWhiteSpace(NomorStruk)) { - TempData["Error"] = "Nomor struk dan berat nett harus diisi."; + TempData["Error"] = "Nomor struk wajib diisi."; return RedirectToAction("Struk"); } - // Validate receipt number format (numbers only, 7+ digits) - if (!System.Text.RegularExpressions.Regex.IsMatch(NomorStruk, @"^\d{7,}$")) + if (!Regex.IsMatch(NomorStruk, @"^\d{3,}$")) { - TempData["Error"] = "Format nomor struk tidak valid. Harus berupa angka minimal 7 digit."; + TempData["Error"] = "Format nomor struk tidak valid. Harus berupa angka minimal 3 digit."; return RedirectToAction("Struk"); } - // Validate weight range - if (BeratNett < 100 || BeratNett > 50000) + if (string.IsNullOrWhiteSpace(NomorPolisi)) { - TempData["Error"] = "Berat nett harus antara 100 kg - 50,000 kg."; + TempData["Error"] = "Nomor polisi wajib diisi."; return RedirectToAction("Struk"); } - // Validate optional weights - if (BeratMasuk.HasValue && (BeratMasuk < 0 || BeratMasuk > 100000)) + if (string.IsNullOrWhiteSpace(Penugasan)) { - TempData["Error"] = "Berat masuk tidak valid."; + TempData["Error"] = "Penugasan wajib diisi."; return RedirectToAction("Struk"); } - if (BeratKeluar.HasValue && (BeratKeluar < 0 || BeratKeluar > 100000)) + if (string.IsNullOrWhiteSpace(WaktuMasuk)) { - TempData["Error"] = "Berat keluar tidak valid."; + TempData["Error"] = "Waktu masuk wajib diisi."; return RedirectToAction("Struk"); } - // Here you would normally save to database - // For now, just simulate success with all data - var submitData = new + if (string.IsNullOrWhiteSpace(WaktuKeluar)) { - NomorStruk, - NomorPolisi = NomorPolisi ?? "N/A", - Penugasan = Penugasan ?? "N/A", - WaktuMasuk = WaktuMasuk ?? "N/A", - WaktuKeluar = WaktuKeluar ?? "N/A", - BeratMasuk = BeratMasuk?.ToString() ?? "N/A", - BeratKeluar = BeratKeluar?.ToString() ?? "N/A", - BeratNett - }; - - TempData["Success"] = $"Struk berhasil disubmit! No: {NomorStruk}, Nett: {BeratNett} kg"; + TempData["Error"] = "Waktu keluar wajib diisi."; + return RedirectToAction("Struk"); + } + + if (!BeratMasuk.HasValue || BeratMasuk <= 0) + { + TempData["Error"] = "Berat masuk wajib diisi."; + return RedirectToAction("Struk"); + } + + if (!BeratKeluar.HasValue || BeratKeluar <= 0) + { + TempData["Error"] = "Berat keluar wajib diisi."; + return RedirectToAction("Struk"); + } + + if (!BeratNett.HasValue || BeratNett <= 0) + { + TempData["Error"] = "Berat nett wajib diisi."; + return RedirectToAction("Struk"); + } + + if (FotoStruk == null || FotoStruk.Length == 0) + { + TempData["Error"] = "Foto struk wajib dilampirkan."; + return RedirectToAction("Struk"); + } + + string? fotoStrukUrl = null; + if (FotoStruk != null && FotoStruk.Length > 0) + { + var datePart = DateTime.Now.ToString("yyyy-MM-dd"); + var uploadPath = Path.Combine(_env.ContentRootPath, "uploads", "struk", datePart); + if (!Directory.Exists(uploadPath)) + Directory.CreateDirectory(uploadPath); + + var ext = Path.GetExtension(FotoStruk.FileName).ToLowerInvariant(); + if (string.IsNullOrEmpty(ext)) ext = ".jpg"; + var fileName = $"struk_{NomorStruk}_{Guid.NewGuid():N}{ext}"; + var filePath = Path.Combine(uploadPath, fileName); + await using var stream = new FileStream(filePath, FileMode.Create); + await FotoStruk.CopyToAsync(stream); + fotoStrukUrl = $"/uploads/struk/{datePart}/{fileName}"; + } + + var timbangLabel = Timbang == "RDF" ? "Timbangan RDF" : "Timbangan TPA"; + TempData["Success"] = $"Struk berhasil disubmit! No: {NomorStruk}, {timbangLabel}, Nett: {BeratNett!.Value} kg"; return RedirectToAction("Index", "Home"); - } catch (Exception) { @@ -83,5 +264,24 @@ namespace eSPJ.Controllers.SpjDriverUpstController return RedirectToAction("Struk"); } } + + private static string? GetStringValue(JsonElement root, string key) + { + if (root.TryGetProperty(key, out var prop) && prop.ValueKind == JsonValueKind.String) + return prop.GetString(); + return null; + } + + private static int? GetIntValue(JsonElement root, string key) + { + if (root.TryGetProperty(key, out var prop)) + { + if (prop.ValueKind == JsonValueKind.Number && prop.TryGetInt32(out var val)) + return val; + if (prop.ValueKind == JsonValueKind.String && int.TryParse(prop.GetString(), out var strVal)) + return strVal; + } + return null; + } } } diff --git a/Views/Admin/Transport/SpjDriverUpst/Shared/Components/_PWAinstall.cshtml b/Views/Admin/Transport/SpjDriverUpst/Shared/Components/_PWAinstall.cshtml index ea72a8e..c435bec 100644 --- a/Views/Admin/Transport/SpjDriverUpst/Shared/Components/_PWAinstall.cshtml +++ b/Views/Admin/Transport/SpjDriverUpst/Shared/Components/_PWAinstall.cshtml @@ -6,7 +6,7 @@

Install App

-

Pasang eSPJ di perangkat

+

PKM UPST di perangkat

Akses lebih cepat langsung dari home screen tanpa buka browser dulu.

@@ -26,7 +26,7 @@