eSPJ/Controllers/SpjDriverUpstController/SubmitController.cs

288 lines
12 KiB
C#

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()
{
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<IActionResult> 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"": ""<nomor struk tanpa prefix, contoh: 8001441>"",
""nomorPolisi"": ""<nomor polisi kendaraan, contoh: B 9125 PJA>"",
""penugasan"": ""<area penugasan, contoh: JAKARTA BARAT>"",
""waktuMasuk"": ""<waktu masuk format YYYY-MM-DD, HH:MM:SS>"",
""waktuKeluar"": ""<waktu keluar format YYYY-MM-DD, HH:MM:SS>"",
""beratMasuk"": <berat masuk dalam kg sebagai angka integer tanpa desimal>,
""beratKeluar"": <berat keluar dalam kg sebagai angka integer tanpa desimal>,
""beratNett"": <berat netto dalam kg sebagai angka integer tanpa desimal>
}
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 async Task<IActionResult> ProcessStruk(
string? NomorStruk,
string? NomorPolisi,
string? Penugasan,
string? WaktuMasuk,
string? WaktuKeluar,
int? BeratMasuk,
int? BeratKeluar,
int? BeratNett,
string? Timbang,
IFormFile? FotoStruk)
{
try
{
if (string.IsNullOrWhiteSpace(NomorStruk))
{
TempData["Error"] = "Nomor struk wajib diisi.";
return RedirectToAction("Struk");
}
if (!Regex.IsMatch(NomorStruk, @"^\d{3,}$"))
{
TempData["Error"] = "Format nomor struk tidak valid. Harus berupa angka minimal 3 digit.";
return RedirectToAction("Struk");
}
if (string.IsNullOrWhiteSpace(NomorPolisi))
{
TempData["Error"] = "Nomor polisi wajib diisi.";
return RedirectToAction("Struk");
}
if (string.IsNullOrWhiteSpace(Penugasan))
{
TempData["Error"] = "Penugasan wajib diisi.";
return RedirectToAction("Struk");
}
if (string.IsNullOrWhiteSpace(WaktuMasuk))
{
TempData["Error"] = "Waktu masuk wajib diisi.";
return RedirectToAction("Struk");
}
if (string.IsNullOrWhiteSpace(WaktuKeluar))
{
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)
{
TempData["Error"] = "Terjadi kesalahan saat memproses struk. Silakan coba lagi.";
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;
}
}
}