eSPJ/Controllers/SpjDriverUpstController/DetailController.cs

263 lines
9.6 KiB
C#

using Microsoft.AspNetCore.Mvc;
using System.Globalization;
using System.Text;
using System.Text.Json;
using System.Text.RegularExpressions;
namespace eSPJ.Controllers.SpjDriverUpstController
{
[Route("upst/detail-penjemputan")]
public class DetailPenjemputanController : Controller
{
private readonly IHttpClientFactory _httpClientFactory;
private readonly IConfiguration _configuration;
public DetailPenjemputanController(IHttpClientFactory httpClientFactory, IConfiguration configuration)
{
_httpClientFactory = httpClientFactory;
_configuration = configuration;
}
[HttpGet("")]
public IActionResult Index()
{
return View("~/Views/Admin/Transport/SpjDriverUpst/DetailPenjemputan/Index.cshtml");
}
[HttpGet("tanpa-tps")]
public IActionResult TanpaTps()
{
return View("~/Views/Admin/Transport/SpjDriverUpst/DetailPenjemputan/TanpaTps.cshtml");
}
[HttpPost("")]
[ValidateAntiForgeryToken]
public IActionResult Index(
string? Latitude,
string? Longitude,
string? AlamatJalan,
string? GpsTruck,
string? WaktuKedatangan,
decimal? TotalTimbangan,
List<decimal>? BeratTimbangan,
List<IFormFile>? FotoKedatangan,
List<IFormFile>? FotoTimbangan,
List<IFormFile>? FotoPetugas)
{
if (FotoKedatangan == null || FotoKedatangan.Count == 0)
{
TempData["Error"] = "Step 1 wajib upload minimal 1 foto kedatangan.";
return RedirectToAction(nameof(Index));
}
if (FotoTimbangan == null || FotoTimbangan.Count == 0)
{
TempData["Error"] = "Step 2 wajib upload minimal 1 foto timbangan.";
return RedirectToAction(nameof(Index));
}
if (FotoPetugas == null || FotoPetugas.Count == 0)
{
TempData["Error"] = "Step 3 wajib upload minimal 1 foto petugas.";
return RedirectToAction(nameof(Index));
}
var totalByDetail = (BeratTimbangan ?? new List<decimal>())
.Where(x => x > 0)
.Sum();
var total = TotalTimbangan.GetValueOrDefault() > 0
? TotalTimbangan.GetValueOrDefault()
: totalByDetail;
var totalDisplay = total.ToString("N2", CultureInfo.GetCultureInfo("id-ID"));
TempData["Success"] =
$"Data tersimpan. Kedatangan: {FotoKedatangan.Count} foto, " +
$"Timbangan: {FotoTimbangan.Count} foto, Total: {totalDisplay} kg, " +
$"Petugas: {FotoPetugas.Count} foto.";
// TODO: simpan ke database
_ = Latitude;
_ = Longitude;
_ = AlamatJalan;
_ = GpsTruck;
_ = WaktuKedatangan;
return RedirectToAction(nameof(Index));
}
[HttpPost("ocr-timbangan")]
[IgnoreAntiforgeryToken]
public async Task<IActionResult> OcrTimbangan(IFormFile? Foto)
{
if (Foto == null || Foto.Length == 0)
{
return BadRequest(new { success = false, message = "Foto tidak ditemukan." });
}
// limit size biar ga gila (contoh 5MB)
if (Foto.Length > 5 * 1024 * 1024)
{
return BadRequest(new { success = false, message = "Ukuran foto terlalu besar. Maksimal 5MB." });
}
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 = "nvidia/nemotron-nano-12b-v2-vl:free",
temperature = 0,
messages = new object[]
{
new
{
role = "user",
content = new object[]
{
new
{
type = "text",
text = @"
Baca angka berat timbangan digital pada foto.
Rules:
- Abaikan tulisan seperti ZERO, TARE, STABLE, AC, PACK, PCS, KG, ADD, HOLD.
- Jawab hanya angka dengan format 2 digit desimal pakai titik (contoh: 54.45).
- Jika tidak terbaca jawab: UNREADABLE
- Fokus pada angka layar LED merah yang menyala.
Saya berikan 3 contoh foto timbangan yang benar:
Foto 1 = 75.23
Foto 2 = 79.86
Foto 3 = 54.45
Sekarang baca angka pada foto terakhir."
},
new
{
type = "image_url",
image_url = new { url = "https://res.cloudinary.com/drejcprhe/image/upload/v1770888384/Notes_-_2026-02-11_08.52.31_wonhbm.jpg" }
},
new
{
type = "image_url",
image_url = new { url = "https://res.cloudinary.com/drejcprhe/image/upload/v1770888429/Notes_-_2026-02-11_08.52.34_xairzy.jpg" }
},
new
{
type = "image_url",
image_url = new { url = "https://res.cloudinary.com/drejcprhe/image/upload/v1770888473/ChatGPT_Image_Feb_11_2026_03_00_33_PM_ujhdlw.png" }
},
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://yourdomain.com");
request.Headers.TryAddWithoutValidation("X-Title", "eSPJ OCR Timbangan");
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() ?? "";
content = content.Trim();
if (content.Contains("UNREADABLE", StringComparison.OrdinalIgnoreCase))
{
return Ok(new
{
success = false,
message = "Angka tidak terbaca.",
raw = content
});
}
// cari format angka 2 desimal
var match = Regex.Match(content, @"-?\d{1,5}([.,]\d{2})");
if (!match.Success)
{
return Ok(new
{
success = false,
message = "AI tidak menemukan angka valid.",
raw = content
});
}
var normalized = match.Value.Replace(',', '.');
if (!decimal.TryParse(normalized, NumberStyles.Any, CultureInfo.InvariantCulture, out var weight))
{
return Ok(new
{
success = false,
message = "Format angka AI tidak valid.",
raw = content
});
}
return Ok(new
{
success = true,
weight = weight.ToString("0.00", CultureInfo.InvariantCulture),
raw = content
});
}
[HttpGet("batal")]
public IActionResult Batal()
{
return View("~/Views/Admin/Transport/SpjDriverUpst/DetailPenjemputan/Batal.cshtml");
}
}
}