using Microsoft.AspNetCore.Mvc; using System.Globalization; using System.Text; using System.Text.Json; using System.Text.RegularExpressions; using eSPJ.Models; using eSPJ.Services; namespace eSPJ.Controllers.SpjDriverUpstController { [Route("upst/detail-penjemputan")] public class DetailPenjemputanController : Controller { private readonly IHttpClientFactory _httpClientFactory; private readonly IConfiguration _configuration; private readonly DetailPenjemputanService _detailService; private readonly ILogger _logger; private readonly IWebHostEnvironment _env; public DetailPenjemputanController( IHttpClientFactory httpClientFactory, IConfiguration configuration, DetailPenjemputanService detailService, ILogger logger, IWebHostEnvironment env) { _httpClientFactory = httpClientFactory; _configuration = configuration; _detailService = detailService; _logger = logger; _env = env; } private static string ResolveDraftKey(string? draftKey, string? sessionKey, string? spjDetailId = null, string? lokasiAngkutId = null) { var rawKey = !string.IsNullOrWhiteSpace(draftKey) ? draftKey : !string.IsNullOrWhiteSpace(sessionKey) ? sessionKey : $"non-tps-{spjDetailId}-{lokasiAngkutId}"; return string.Concat((rawKey ?? string.Empty).Where(c => char.IsLetterOrDigit(c) || c == '-' || c == '_')); } private string GetUploadDirectory(string dateFolder) { var uploadDir = Path.Combine(_env.ContentRootPath, "uploads", "penjemputan", dateFolder); Directory.CreateDirectory(uploadDir); return uploadDir; } private static string BuildUploadUrl(string dateFolder, string fileName) { return $"/uploads/penjemputan/{dateFolder}/{fileName}"; } [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"); } [HttpGet("batal")] public IActionResult Batal() { return View("~/Views/Admin/Transport/SpjDriverUpst/DetailPenjemputan/Batal.cshtml"); } [HttpGet("detail-batal")] public IActionResult DetailBatal() { return View("~/Views/Admin/Transport/SpjDriverUpst/DetailPenjemputan/DetailBatal.cshtml"); } [HttpGet("detail-selesai")] public IActionResult DetailSelesai() { return View("~/Views/Admin/Transport/SpjDriverUpst/DetailPenjemputan/DetailSelesai.cshtml"); } [HttpGet("detail-selesai-tanpa-tps")] public IActionResult DetailSelesaiTanpaTps() { return View("~/Views/Admin/Transport/SpjDriverUpst/DetailPenjemputan/DetailSelesaiTanpaTps.cshtml"); } [HttpPost("")] [ValidateAntiForgeryToken] public async Task Submit([FromForm] DetailPenjemputanRequest request) { try { var result = await _detailService.SubmitPenjemputanAsync(request); if (result.Success) { TempData["Success"] = result.Message; } else { TempData["Error"] = result.Message; } return RedirectToAction(nameof(Index)); } catch (Exception ex) { _logger.LogError(ex, "Error submitting penjemputan data"); TempData["Error"] = "Terjadi kesalahan saat menyimpan data."; return RedirectToAction(nameof(Index)); } } [HttpPost("save-draft-non-tps")] [IgnoreAntiforgeryToken] public async Task SaveDraftNonTps([FromBody] DraftSaveRequest request) { if (request == null) return BadRequest(new DraftSaveResponse { Success = false, Message = "Request tidak valid." }); request.DraftKey = ResolveDraftKey(request.DraftKey, request.SessionKey, request.SpjDetailId, request.LokasiAngkutId); request.SessionKey = request.DraftKey; if (string.IsNullOrWhiteSpace(request.DraftKey)) return BadRequest(new DraftSaveResponse { Success = false, Message = "Draft key tidak valid." }); var result = await _detailService.SaveDraftNonTpsAsync(request); return result.Success ? Ok(result) : StatusCode(500, result); } [HttpGet("load-draft-non-tps")] [IgnoreAntiforgeryToken] public async Task LoadDraftNonTps([FromQuery] string? draftKey = null, [FromQuery] string? sessionKey = null) { var key = ResolveDraftKey(draftKey, sessionKey); if (string.IsNullOrWhiteSpace(key)) return Ok(new DraftLoadResponse { Success = true, HasDraft = false, Message = "Draft key kosong." }); var result = await _detailService.LoadDraftNonTpsAsync(key); return Ok(result); } [HttpDelete("delete-draft-non-tps")] [IgnoreAntiforgeryToken] public async Task DeleteDraftNonTps([FromQuery] string? draftKey = null, [FromQuery] string? sessionKey = null) { var key = ResolveDraftKey(draftKey, sessionKey); if (string.IsNullOrWhiteSpace(key)) return Ok(new { success = false }); var ok = await _detailService.DeleteDraftNonTpsAsync(key); return Ok(new { success = ok }); } [HttpPost("save-draft")] [IgnoreAntiforgeryToken] public async Task SaveDraft([FromBody] DraftSaveRequest request) { if (request == null) return BadRequest(new DraftSaveResponse { Success = false, Message = "Request tidak valid." }); request.DraftKey = ResolveDraftKey(request.DraftKey, request.SessionKey, request.SpjDetailId, request.LokasiAngkutId); request.SessionKey = request.DraftKey; if (string.IsNullOrWhiteSpace(request.DraftKey)) return BadRequest(new DraftSaveResponse { Success = false, Message = "Draft key tidak valid." }); var result = await _detailService.SaveDraftTpsAsync(request); return result.Success ? Ok(result) : StatusCode(500, result); } [HttpGet("load-draft")] [IgnoreAntiforgeryToken] public async Task LoadDraft([FromQuery] string? draftKey = null, [FromQuery] string? sessionKey = null) { var key = ResolveDraftKey(draftKey, sessionKey); if (string.IsNullOrWhiteSpace(key)) return Ok(new DraftLoadResponse { Success = true, HasDraft = false, Message = "Draft key kosong." }); var result = await _detailService.LoadDraftTpsAsync(key); return Ok(result); } [HttpDelete("delete-draft")] [IgnoreAntiforgeryToken] public async Task DeleteDraft([FromQuery] string? draftKey = null, [FromQuery] string? sessionKey = null) { var key = ResolveDraftKey(draftKey, sessionKey); if (string.IsNullOrWhiteSpace(key)) return Ok(new { success = false }); var ok = await _detailService.DeleteDraftTpsAsync(key); return Ok(new { success = ok }); } [HttpPost("upload-foto-kedatangan-non-tps")] [IgnoreAntiforgeryToken] public async Task UploadFotoKedatanganNonTps( [FromForm] List? FotoKedatangan, [FromForm] string? DraftKey, [FromForm] string? SessionKey, [FromForm] string? SpjDetailId, [FromForm] string? LokasiAngkutId, [FromForm] string? WaktuKedatangan, [FromForm] string? Latitude, [FromForm] string? Longitude, [FromForm] string? AlamatJalan) { if (FotoKedatangan == null || FotoKedatangan.Count == 0) return BadRequest(new { success = false, message = "Tidak ada foto." }); var dateFolder = DateTime.Now.ToString("yyyy-MM-dd"); var uploadDir = GetUploadDirectory(dateFolder); var fileNames = new List(); foreach (var file in FotoKedatangan) { if (file.Length == 0) continue; var ext = Path.GetExtension(file.FileName).ToLowerInvariant(); var name = $"kedatangan_{Guid.NewGuid()}{ext}"; var path = Path.Combine(uploadDir, name); await using var stream = new FileStream(path, FileMode.Create); await file.CopyToAsync(stream); fileNames.Add(name); } var fileUrls = fileNames.Select(n => BuildUploadUrl(dateFolder, n)).ToList(); var resolvedDraftKey = ResolveDraftKey(DraftKey, SessionKey, SpjDetailId, LokasiAngkutId); if (!string.IsNullOrWhiteSpace(resolvedDraftKey)) { var loadResult = await _detailService.LoadDraftNonTpsAsync(resolvedDraftKey); var draft = loadResult.Draft ?? new DraftPenjemputanNonTps { SessionKey = resolvedDraftKey, SpjDetailId = SpjDetailId ?? string.Empty, LokasiAngkutId = LokasiAngkutId ?? string.Empty }; draft.FotoKedatanganFileNames = fileUrls; draft.FotoKedatanganUploaded = true; if (!string.IsNullOrWhiteSpace(SpjDetailId)) draft.SpjDetailId = SpjDetailId; if (!string.IsNullOrWhiteSpace(LokasiAngkutId)) draft.LokasiAngkutId = LokasiAngkutId; if (!string.IsNullOrWhiteSpace(WaktuKedatangan)) draft.WaktuKedatangan = WaktuKedatangan; if (!string.IsNullOrWhiteSpace(Latitude)) draft.Latitude = Latitude; if (!string.IsNullOrWhiteSpace(Longitude)) draft.Longitude = Longitude; if (!string.IsNullOrWhiteSpace(AlamatJalan)) draft.AlamatJalan = AlamatJalan; await _detailService.SaveDraftNonTpsAsync(new DraftSaveRequest { DraftKey = resolvedDraftKey, SessionKey = resolvedDraftKey, LokasiAngkutId = draft.LokasiAngkutId, SpjDetailId = draft.SpjDetailId, Latitude = draft.Latitude, Longitude = draft.Longitude, AlamatJalan = draft.AlamatJalan, WaktuKedatangan = draft.WaktuKedatangan, FotoKedatanganFileNames = fileUrls, FotoKedatanganUploaded = true, Timbangan = draft.Timbangan, TotalOrganik = draft.TotalOrganik, TotalAnorganik = draft.TotalAnorganik, TotalResidu = draft.TotalResidu, TotalTimbangan = draft.TotalTimbangan, FotoPetugasFileNames = draft.FotoPetugasFileNames, FotoPetugasUploaded = draft.FotoPetugasUploaded, NamaPetugas = draft.NamaPetugas }); } return Ok(new { success = true, fileNames, fileUrls, message = $"{fileNames.Count} foto kedatangan berhasil diupload." }); } [HttpPost("upload-foto-timbangan-non-tps")] [IgnoreAntiforgeryToken] public async Task UploadFotoTimbanganNonTps( [FromForm] IFormFile? FotoTimbangan, [FromForm] string? DraftKey, [FromForm] string? SessionKey, [FromForm] string? SpjDetailId, [FromForm] string? LokasiAngkutId, [FromForm] int ItemIndex, [FromForm] string? JenisSampah, [FromForm] decimal Berat) { if (FotoTimbangan == null || FotoTimbangan.Length == 0) return BadRequest(new { success = false, message = "Tidak ada foto." }); var dateFolder = DateTime.Now.ToString("yyyy-MM-dd"); var uploadDir = GetUploadDirectory(dateFolder); var ext = Path.GetExtension(FotoTimbangan.FileName).ToLowerInvariant(); var jenisSafe = (JenisSampah ?? "residu").ToLowerInvariant(); var beratStr = Berat.ToString("0.00", System.Globalization.CultureInfo.InvariantCulture).Replace('.', '_'); var name = $"timbangan{ItemIndex + 1}-{jenisSafe}-{beratStr}{ext}"; var filePath = Path.Combine(uploadDir, name); await using var stream = new FileStream(filePath, FileMode.Create); await FotoTimbangan.CopyToAsync(stream); var fileUrl = BuildUploadUrl(dateFolder, name); var resolvedDraftKey = ResolveDraftKey(DraftKey, SessionKey, SpjDetailId, LokasiAngkutId); if (!string.IsNullOrWhiteSpace(resolvedDraftKey)) { var loadResult = await _detailService.LoadDraftNonTpsAsync(resolvedDraftKey); var draft = loadResult.Draft ?? new DraftPenjemputanNonTps { SessionKey = resolvedDraftKey, SpjDetailId = SpjDetailId ?? string.Empty, LokasiAngkutId = LokasiAngkutId ?? string.Empty }; while (draft.Timbangan.Count <= ItemIndex) draft.Timbangan.Add(new DraftTimbanganItem()); if (!string.IsNullOrWhiteSpace(SpjDetailId)) draft.SpjDetailId = SpjDetailId; if (!string.IsNullOrWhiteSpace(LokasiAngkutId)) draft.LokasiAngkutId = LokasiAngkutId; draft.Timbangan[ItemIndex] = new DraftTimbanganItem { FotoFileName = fileUrl, JenisSampah = JenisSampah ?? "Residu", Berat = Berat, Uploaded = true }; await _detailService.SaveDraftNonTpsAsync(new DraftSaveRequest { DraftKey = resolvedDraftKey, SessionKey = resolvedDraftKey, LokasiAngkutId = draft.LokasiAngkutId, SpjDetailId = draft.SpjDetailId, Latitude = draft.Latitude, Longitude = draft.Longitude, AlamatJalan = draft.AlamatJalan, WaktuKedatangan = draft.WaktuKedatangan, FotoKedatanganFileNames = draft.FotoKedatanganFileNames, FotoKedatanganUploaded = draft.FotoKedatanganUploaded, Timbangan = draft.Timbangan, TotalOrganik = draft.TotalOrganik, TotalAnorganik = draft.TotalAnorganik, TotalResidu = draft.TotalResidu, TotalTimbangan = draft.TotalTimbangan, FotoPetugasFileNames = draft.FotoPetugasFileNames, FotoPetugasUploaded = draft.FotoPetugasUploaded, NamaPetugas = draft.NamaPetugas }); } return Ok(new { success = true, fileName = name, fileUrl, message = $"Foto timbangan #{ItemIndex + 1} berhasil diupload." }); } [HttpPost("upload-foto-petugas-non-tps")] [IgnoreAntiforgeryToken] public async Task UploadFotoPetugasNonTps( [FromForm] List? FotoPetugas, [FromForm] string? DraftKey, [FromForm] string? SessionKey, [FromForm] string? SpjDetailId, [FromForm] string? LokasiAngkutId, [FromForm] string? NamaPetugas) { if (FotoPetugas == null || FotoPetugas.Count == 0) return BadRequest(new { success = false, message = "Tidak ada foto." }); var dateFolder = DateTime.Now.ToString("yyyy-MM-dd"); var uploadDir = GetUploadDirectory(dateFolder); var fileNames = new List(); foreach (var file in FotoPetugas) { if (file.Length == 0) continue; var ext = Path.GetExtension(file.FileName).ToLowerInvariant(); var name = $"petugas_{Guid.NewGuid()}{ext}"; var path = Path.Combine(uploadDir, name); await using var stream = new FileStream(path, FileMode.Create); await file.CopyToAsync(stream); fileNames.Add(name); } var fileUrls = fileNames.Select(n => BuildUploadUrl(dateFolder, n)).ToList(); var resolvedDraftKey = ResolveDraftKey(DraftKey, SessionKey, SpjDetailId, LokasiAngkutId); if (!string.IsNullOrWhiteSpace(resolvedDraftKey)) { var loadResult = await _detailService.LoadDraftNonTpsAsync(resolvedDraftKey); var draft = loadResult.Draft ?? new DraftPenjemputanNonTps { SessionKey = resolvedDraftKey, SpjDetailId = SpjDetailId ?? string.Empty, LokasiAngkutId = LokasiAngkutId ?? string.Empty }; draft.FotoPetugasFileNames = fileUrls; draft.FotoPetugasUploaded = true; if (!string.IsNullOrWhiteSpace(SpjDetailId)) draft.SpjDetailId = SpjDetailId; if (!string.IsNullOrWhiteSpace(LokasiAngkutId)) draft.LokasiAngkutId = LokasiAngkutId; if (!string.IsNullOrWhiteSpace(NamaPetugas)) draft.NamaPetugas = NamaPetugas; await _detailService.SaveDraftNonTpsAsync(new DraftSaveRequest { DraftKey = resolvedDraftKey, SessionKey = resolvedDraftKey, LokasiAngkutId = draft.LokasiAngkutId, SpjDetailId = draft.SpjDetailId, Latitude = draft.Latitude, Longitude = draft.Longitude, AlamatJalan = draft.AlamatJalan, WaktuKedatangan = draft.WaktuKedatangan, FotoKedatanganFileNames = draft.FotoKedatanganFileNames, FotoKedatanganUploaded = draft.FotoKedatanganUploaded, Timbangan = draft.Timbangan, TotalOrganik = draft.TotalOrganik, TotalAnorganik = draft.TotalAnorganik, TotalResidu = draft.TotalResidu, TotalTimbangan = draft.TotalTimbangan, FotoPetugasFileNames = fileUrls, FotoPetugasUploaded = true, NamaPetugas = draft.NamaPetugas }); } return Ok(new { success = true, fileNames, fileUrls, message = $"{fileNames.Count} foto petugas berhasil diupload." }); } [HttpPost("upload-foto-kedatangan")] [IgnoreAntiforgeryToken] public async Task UploadFotoKedatangan( [FromForm] List? FotoKedatangan, [FromForm] string? DraftKey, [FromForm] string? SessionKey, [FromForm] string? SpjDetailId, [FromForm] string? LokasiAngkutId, [FromForm] string? WaktuKedatangan, [FromForm] string? Latitude, [FromForm] string? Longitude, [FromForm] string? AlamatJalan) { if (FotoKedatangan == null || FotoKedatangan.Count == 0) return BadRequest(new { success = false, message = "Tidak ada foto." }); var dateFolder = DateTime.Now.ToString("yyyy-MM-dd"); var uploadDir = GetUploadDirectory(dateFolder); var fileNames = new List(); foreach (var file in FotoKedatangan) { if (file.Length == 0) continue; var ext = Path.GetExtension(file.FileName).ToLowerInvariant(); var name = $"kedatangan_{Guid.NewGuid()}{ext}"; var path = Path.Combine(uploadDir, name); await using var stream = new FileStream(path, FileMode.Create); await file.CopyToAsync(stream); fileNames.Add(name); } var fileUrls = fileNames.Select(n => BuildUploadUrl(dateFolder, n)).ToList(); var resolvedDraftKeyTps = ResolveDraftKey(DraftKey, SessionKey, SpjDetailId, LokasiAngkutId); if (!string.IsNullOrWhiteSpace(resolvedDraftKeyTps)) { var loadResult = await _detailService.LoadDraftTpsAsync(resolvedDraftKeyTps); var draft = loadResult.Draft ?? new DraftPenjemputanNonTps { SessionKey = resolvedDraftKeyTps, SpjDetailId = SpjDetailId ?? string.Empty, LokasiAngkutId = LokasiAngkutId ?? string.Empty }; draft.FotoKedatanganFileNames = fileUrls; draft.FotoKedatanganUploaded = true; if (!string.IsNullOrWhiteSpace(SpjDetailId)) draft.SpjDetailId = SpjDetailId; if (!string.IsNullOrWhiteSpace(LokasiAngkutId)) draft.LokasiAngkutId = LokasiAngkutId; if (!string.IsNullOrWhiteSpace(WaktuKedatangan)) draft.WaktuKedatangan = WaktuKedatangan; if (!string.IsNullOrWhiteSpace(Latitude)) draft.Latitude = Latitude; if (!string.IsNullOrWhiteSpace(Longitude)) draft.Longitude = Longitude; if (!string.IsNullOrWhiteSpace(AlamatJalan)) draft.AlamatJalan = AlamatJalan; await _detailService.SaveDraftTpsAsync(new DraftSaveRequest { DraftKey = resolvedDraftKeyTps, SessionKey = resolvedDraftKeyTps, LokasiAngkutId = draft.LokasiAngkutId, SpjDetailId = draft.SpjDetailId, Latitude = draft.Latitude, Longitude = draft.Longitude, AlamatJalan = draft.AlamatJalan, WaktuKedatangan = draft.WaktuKedatangan, FotoKedatanganFileNames = fileUrls, FotoKedatanganUploaded = true, Timbangan = draft.Timbangan, TotalOrganik = draft.TotalOrganik, TotalAnorganik = draft.TotalAnorganik, TotalResidu = draft.TotalResidu, TotalTimbangan = draft.TotalTimbangan, FotoPetugasFileNames = draft.FotoPetugasFileNames, FotoPetugasUploaded = draft.FotoPetugasUploaded, NamaPetugas = draft.NamaPetugas }); } return Ok(new { success = true, fileNames, fileUrls, message = $"{fileNames.Count} foto kedatangan berhasil diupload." }); } [HttpPost("upload-foto-timbangan")] [IgnoreAntiforgeryToken] public async Task UploadFotoTimbangan( [FromForm] IFormFile? FotoTimbangan, [FromForm] string? DraftKey, [FromForm] string? SessionKey, [FromForm] string? SpjDetailId, [FromForm] string? LokasiAngkutId, [FromForm] int ItemIndex, [FromForm] string? JenisSampah, [FromForm] decimal Berat) { if (FotoTimbangan == null || FotoTimbangan.Length == 0) return BadRequest(new { success = false, message = "Tidak ada foto." }); var dateFolder = DateTime.Now.ToString("yyyy-MM-dd"); var uploadDir = GetUploadDirectory(dateFolder); var ext = Path.GetExtension(FotoTimbangan.FileName).ToLowerInvariant(); var jenisSafe = (JenisSampah ?? "residu").ToLowerInvariant(); var beratStr = Berat.ToString("0.00", System.Globalization.CultureInfo.InvariantCulture).Replace('.', '_'); var name = $"timbangan{ItemIndex + 1}-{jenisSafe}-{beratStr}{ext}"; var filePath = Path.Combine(uploadDir, name); await using var stream = new FileStream(filePath, FileMode.Create); await FotoTimbangan.CopyToAsync(stream); var fileUrl = BuildUploadUrl(dateFolder, name); var resolvedDraftKeyTps = ResolveDraftKey(DraftKey, SessionKey, SpjDetailId, LokasiAngkutId); if (!string.IsNullOrWhiteSpace(resolvedDraftKeyTps)) { var loadResult = await _detailService.LoadDraftTpsAsync(resolvedDraftKeyTps); var draft = loadResult.Draft ?? new DraftPenjemputanNonTps { SessionKey = resolvedDraftKeyTps, SpjDetailId = SpjDetailId ?? string.Empty, LokasiAngkutId = LokasiAngkutId ?? string.Empty }; while (draft.Timbangan.Count <= ItemIndex) draft.Timbangan.Add(new DraftTimbanganItem()); if (!string.IsNullOrWhiteSpace(SpjDetailId)) draft.SpjDetailId = SpjDetailId; if (!string.IsNullOrWhiteSpace(LokasiAngkutId)) draft.LokasiAngkutId = LokasiAngkutId; draft.Timbangan[ItemIndex] = new DraftTimbanganItem { FotoFileName = fileUrl, JenisSampah = JenisSampah ?? "Residu", Berat = Berat, Uploaded = true }; await _detailService.SaveDraftTpsAsync(new DraftSaveRequest { DraftKey = resolvedDraftKeyTps, SessionKey = resolvedDraftKeyTps, LokasiAngkutId = draft.LokasiAngkutId, SpjDetailId = draft.SpjDetailId, Latitude = draft.Latitude, Longitude = draft.Longitude, AlamatJalan = draft.AlamatJalan, WaktuKedatangan = draft.WaktuKedatangan, FotoKedatanganFileNames = draft.FotoKedatanganFileNames, FotoKedatanganUploaded = draft.FotoKedatanganUploaded, Timbangan = draft.Timbangan, TotalOrganik = draft.TotalOrganik, TotalAnorganik = draft.TotalAnorganik, TotalResidu = draft.TotalResidu, TotalTimbangan = draft.TotalTimbangan, FotoPetugasFileNames = draft.FotoPetugasFileNames, FotoPetugasUploaded = draft.FotoPetugasUploaded, NamaPetugas = draft.NamaPetugas }); } return Ok(new { success = true, fileName = name, fileUrl, message = $"Foto timbangan #{ItemIndex + 1} berhasil diupload." }); } [HttpPost("upload-foto-petugas")] [IgnoreAntiforgeryToken] public async Task UploadFotoPetugas( [FromForm] List? FotoPetugas, [FromForm] string? DraftKey, [FromForm] string? SessionKey, [FromForm] string? SpjDetailId, [FromForm] string? LokasiAngkutId, [FromForm] string? NamaPetugas) { if (FotoPetugas == null || FotoPetugas.Count == 0) return BadRequest(new { success = false, message = "Tidak ada foto." }); var dateFolder = DateTime.Now.ToString("yyyy-MM-dd"); var uploadDir = GetUploadDirectory(dateFolder); var fileNames = new List(); foreach (var file in FotoPetugas) { if (file.Length == 0) continue; var ext = Path.GetExtension(file.FileName).ToLowerInvariant(); var name = $"petugas_{Guid.NewGuid()}{ext}"; var path = Path.Combine(uploadDir, name); await using var stream = new FileStream(path, FileMode.Create); await file.CopyToAsync(stream); fileNames.Add(name); } var fileUrls = fileNames.Select(n => BuildUploadUrl(dateFolder, n)).ToList(); var resolvedDraftKeyTps = ResolveDraftKey(DraftKey, SessionKey, SpjDetailId, LokasiAngkutId); if (!string.IsNullOrWhiteSpace(resolvedDraftKeyTps)) { var loadResult = await _detailService.LoadDraftTpsAsync(resolvedDraftKeyTps); var draft = loadResult.Draft ?? new DraftPenjemputanNonTps { SessionKey = resolvedDraftKeyTps, SpjDetailId = SpjDetailId ?? string.Empty, LokasiAngkutId = LokasiAngkutId ?? string.Empty }; draft.FotoPetugasFileNames = fileUrls; draft.FotoPetugasUploaded = true; if (!string.IsNullOrWhiteSpace(SpjDetailId)) draft.SpjDetailId = SpjDetailId; if (!string.IsNullOrWhiteSpace(LokasiAngkutId)) draft.LokasiAngkutId = LokasiAngkutId; if (!string.IsNullOrWhiteSpace(NamaPetugas)) draft.NamaPetugas = NamaPetugas; await _detailService.SaveDraftTpsAsync(new DraftSaveRequest { DraftKey = resolvedDraftKeyTps, SessionKey = resolvedDraftKeyTps, LokasiAngkutId = draft.LokasiAngkutId, SpjDetailId = draft.SpjDetailId, Latitude = draft.Latitude, Longitude = draft.Longitude, AlamatJalan = draft.AlamatJalan, WaktuKedatangan = draft.WaktuKedatangan, FotoKedatanganFileNames = draft.FotoKedatanganFileNames, FotoKedatanganUploaded = draft.FotoKedatanganUploaded, Timbangan = draft.Timbangan, TotalOrganik = draft.TotalOrganik, TotalAnorganik = draft.TotalAnorganik, TotalResidu = draft.TotalResidu, TotalTimbangan = draft.TotalTimbangan, FotoPetugasFileNames = fileUrls, FotoPetugasUploaded = true, NamaPetugas = draft.NamaPetugas }); } return Ok(new { success = true, fileNames, fileUrls, message = $"{fileNames.Count} foto petugas berhasil diupload." }); } [HttpPost("ocr-timbangan")] [IgnoreAntiforgeryToken] public async Task OcrTimbangan(IFormFile? Foto) { if (Foto == null || Foto.Length == 0) { return BadRequest(new { success = false, message = "Foto tidak ditemukan." }); } 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", model = "google/gemini-2.5-flash-image", // model = "google/gemini-2.5-flash-lite", // model = "google/gemini-2.5-flash-lite-preview-09-2025", 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 }); } } }