using eSPJ.Models; namespace eSPJ.Services { public class DetailPenjemputanService { private readonly IDetailPenjemputanStore _store; private readonly IWebHostEnvironment _env; private readonly ILogger _logger; public DetailPenjemputanService( IWebHostEnvironment env, IDetailPenjemputanStore store, ILogger logger) { _env = env; _store = store; _logger = logger; } private static string SanitizePathSegment(string? value, string fallback = "umum") { var safe = string.Concat((value ?? string.Empty).Trim().Select(c => char.IsLetterOrDigit(c) || c == '-' || c == '_' ? c : '-')); while (safe.Contains("--")) { safe = safe.Replace("--", "-"); } safe = safe.Trim('-'); return string.IsNullOrWhiteSpace(safe) ? fallback : safe; } public async Task> GetAllTpsDataAsync() { return await _store.GetSubmittedAsync(); } public async Task> GetRecordsByNomorSpjAsync(string nomorSpj) { return await _store.GetByNomorSpjAsync(nomorSpj); } public async Task SaveTpsDataAsync(List data) { try { foreach (var item in data) { await _store.SaveSubmittedAsync(item); } return true; } catch (Exception ex) { _logger.LogError(ex, "Error saving TPS data to JSON"); return false; } } public async Task SubmitPenjemputanAsync(DetailPenjemputanRequest request) { try { if (string.IsNullOrEmpty(request.TpsName)) { return new DetailPenjemputanResponse { Success = false, Message = "Nama TPS harus diisi" }; } if (string.IsNullOrEmpty(request.NamaPetugas)) { return new DetailPenjemputanResponse { Success = false, Message = "Nama petugas harus diisi" }; } var existingRecord = await GetRecordDetailAsync(request.NomorSpj, request.SpjDetailId, request.LokasiAngkutId, request.TpsName); var now = DateTime.Now; var datePart = now.ToString("yyyy-MM-dd"); var spjFolder = SanitizePathSegment(request.NomorSpj, "spj-umum"); var tpsFolder = SanitizePathSegment(request.TpsName, "tps-1"); var uploadPath = Path.Combine(_env.ContentRootPath, "uploads", "penjemputan", datePart, spjFolder, tpsFolder); var uploadBaseUrl = $"/uploads/penjemputan/{datePart}/{spjFolder}/{tpsFolder}"; if (!Directory.Exists(uploadPath)) { Directory.CreateDirectory(uploadPath); } var tpsData = existingRecord != null ? CloneRecord(existingRecord) : new TpsData(); tpsData.NomorSpj = request.NomorSpj; tpsData.LokasiAngkutId = request.LokasiAngkutId; tpsData.SpjDetailId = request.SpjDetailId; tpsData.Name = request.TpsName; tpsData.Latitude = request.Latitude; tpsData.Longitude = request.Longitude; tpsData.AlamatJalan = request.AlamatJalan; tpsData.WaktuKedatangan = request.WaktuKedatangan; tpsData.TotalTimbangan = request.TotalTimbangan; tpsData.TotalOrganik = request.TotalOrganik; tpsData.TotalAnorganik = request.TotalAnorganik; tpsData.TotalResidu = request.TotalResidu; tpsData.NamaPetugas = request.NamaPetugas; tpsData.IsSubmit = true; tpsData.SubmittedAt ??= DateTime.Now; tpsData.UpdatedAt = DateTime.Now; if (request.FotoKedatangan != null && request.FotoKedatangan.Any()) { tpsData.FotoKedatangan = new List(); foreach (var file in request.FotoKedatangan) { var fileName = $"kedatangan_{Guid.NewGuid()}{Path.GetExtension(file.FileName)}"; var filePath = Path.Combine(uploadPath, fileName); using (var stream = new FileStream(filePath, FileMode.Create)) { await file.CopyToAsync(stream); } tpsData.FotoKedatangan.Add($"{uploadBaseUrl}/{fileName}"); } tpsData.FotoKedatanganUploaded = tpsData.FotoKedatangan.Count > 0; } else if (existingRecord?.FotoKedatangan?.Any() == true) { tpsData.FotoKedatangan = new List(existingRecord.FotoKedatangan); tpsData.FotoKedatanganUploaded = existingRecord.FotoKedatanganUploaded || tpsData.FotoKedatangan.Count > 0; } else { return new DetailPenjemputanResponse { Success = false, Message = "Foto kedatangan harus diupload" }; } if (request.FotoTimbangan != null && request.FotoTimbangan.Any() && request.BeratTimbangan != null && request.JenisSampahList != null) { tpsData.Timbangan = new List(); for (int i = 0; i < request.FotoTimbangan.Count; i++) { var file = request.FotoTimbangan[i]; var fileName = $"timbangan_{Guid.NewGuid()}{Path.GetExtension(file.FileName)}"; var filePath = Path.Combine(uploadPath, fileName); using (var stream = new FileStream(filePath, FileMode.Create)) { await file.CopyToAsync(stream); } var jenisSampah = JenisSampah.Residu; if (i < request.JenisSampahList.Count && Enum.TryParse(request.JenisSampahList[i], out var parsed)) { jenisSampah = parsed; } tpsData.Timbangan.Add(new TimbanganItem { FotoFileName = $"{uploadBaseUrl}/{fileName}", Berat = new List { i < request.BeratTimbangan.Count ? request.BeratTimbangan[i] : 0 }, LokasiAngkut = new List(), JenisSampah = new List { jenisSampah }, IsUploaded = true, WaktuUpload = DateTime.Now }); } } else if (existingRecord?.Timbangan?.Any() == true) { tpsData.Timbangan = existingRecord.Timbangan; } else { return new DetailPenjemputanResponse { Success = false, Message = "Foto timbangan harus diupload" }; } if (request.FotoPetugas != null && request.FotoPetugas.Any()) { tpsData.FotoPetugas = new List(); foreach (var file in request.FotoPetugas) { var fileName = $"petugas_{Guid.NewGuid()}{Path.GetExtension(file.FileName)}"; var filePath = Path.Combine(uploadPath, fileName); using (var stream = new FileStream(filePath, FileMode.Create)) { await file.CopyToAsync(stream); } tpsData.FotoPetugas.Add($"{uploadBaseUrl}/{fileName}"); } tpsData.FotoPetugasUploaded = tpsData.FotoPetugas.Count > 0; } else if (existingRecord?.FotoPetugas?.Any() == true) { tpsData.FotoPetugas = new List(existingRecord.FotoPetugas); tpsData.FotoPetugasUploaded = existingRecord.FotoPetugasUploaded || tpsData.FotoPetugas.Count > 0; } else { return new DetailPenjemputanResponse { Success = false, Message = "Foto petugas harus diupload" }; } await _store.SaveSubmittedAsync(tpsData); return new DetailPenjemputanResponse { Success = true, Message = "Data penjemputan berhasil disimpan", Data = tpsData }; } catch (Exception ex) { _logger.LogError(ex, "Error submitting penjemputan data"); return new DetailPenjemputanResponse { Success = false, Message = $"Terjadi kesalahan: {ex.Message}" }; } } private Task SaveRecordAsync(RecordSaveRequest request) { return SaveRecordInternalAsync(request); } private async Task SaveRecordInternalAsync(RecordSaveRequest request) { try { await _store.SaveRecordAsync(request); return new RecordSaveResponse { Success = true, Message = "Data tersimpan." }; } catch (Exception ex) { _logger.LogError(ex, "Error saving penjemputan record"); return new RecordSaveResponse { Success = false, Message = $"Gagal menyimpan data: {ex.Message}" }; } } public async Task SaveRecordNonTpsAsync(RecordSaveRequest request) { return await SaveRecordAsync(request); } public async Task SaveRecordTpsAsync(RecordSaveRequest request) { return await SaveRecordAsync(request); } public async Task> GetSubmittedByNomorSpjAsync(string nomorSpj) { var normalizedNomorSpj = (nomorSpj ?? string.Empty).Trim(); var allData = await _store.GetSubmittedAsync(); return allData .Where(item => string.Equals((item.NomorSpj ?? string.Empty).Trim(), normalizedNomorSpj, StringComparison.OrdinalIgnoreCase)) .OrderBy(item => item.SubmittedAt ?? DateTime.MinValue) .ToList(); } public async Task GetSubmittedDetailAsync(string nomorSpj, string? spjDetailId = null, string? lokasiAngkutId = null, string? namaTps = null) { return await _store.GetSubmittedDetailAsync(nomorSpj, spjDetailId, lokasiAngkutId, namaTps); } public async Task GetRecordDetailAsync(string nomorSpj, string? spjDetailId = null, string? lokasiAngkutId = null, string? namaTps = null) { var normalizedNomorSpj = (nomorSpj ?? string.Empty).Trim(); var normalizedSpjDetailId = (spjDetailId ?? string.Empty).Trim(); var normalizedLokasiAngkutId = (lokasiAngkutId ?? string.Empty).Trim(); var normalizedNamaTps = (namaTps ?? string.Empty).Trim(); var allData = await _store.GetByNomorSpjAsync(normalizedNomorSpj); return allData .OrderByDescending(item => item.UpdatedAt) .FirstOrDefault(item => (string.IsNullOrWhiteSpace(normalizedSpjDetailId) || string.Equals((item.SpjDetailId ?? string.Empty).Trim(), normalizedSpjDetailId, StringComparison.OrdinalIgnoreCase)) && (string.IsNullOrWhiteSpace(normalizedLokasiAngkutId) || string.Equals((item.LokasiAngkutId ?? string.Empty).Trim(), normalizedLokasiAngkutId, StringComparison.OrdinalIgnoreCase)) && (string.IsNullOrWhiteSpace(normalizedNamaTps) || string.Equals((item.Name ?? string.Empty).Trim(), normalizedNamaTps, StringComparison.OrdinalIgnoreCase))); } private static TpsData CloneRecord(TpsData source) { return new TpsData { NomorSpj = source.NomorSpj, LokasiAngkutId = source.LokasiAngkutId, SpjDetailId = source.SpjDetailId, Name = source.Name, Index = source.Index, Latitude = source.Latitude, Longitude = source.Longitude, AlamatJalan = source.AlamatJalan, WaktuKedatangan = source.WaktuKedatangan, FotoKedatangan = new List(source.FotoKedatangan ?? new List()), FotoKedatanganUploaded = source.FotoKedatanganUploaded, Timbangan = (source.Timbangan ?? new List()).Select(item => new TimbanganItem { FotoFileName = item.FotoFileName, Berat = new List(item.Berat ?? new List()), LokasiAngkut = new List(item.LokasiAngkut ?? new List()), JenisSampah = new List(item.JenisSampah ?? new List()), IsUploaded = item.IsUploaded, WaktuUpload = item.WaktuUpload }).ToList(), TotalOrganik = source.TotalOrganik, TotalAnorganik = source.TotalAnorganik, TotalResidu = source.TotalResidu, TotalTimbangan = source.TotalTimbangan, FotoPetugas = new List(source.FotoPetugas ?? new List()), FotoPetugasUploaded = source.FotoPetugasUploaded, NamaPetugas = source.NamaPetugas, IsSubmit = source.IsSubmit, UpdatedAt = source.UpdatedAt, SubmittedAt = source.SubmittedAt, }; } public async Task ProcessOcrTimbanganAsync(IFormFile foto) { try { await Task.Delay(500); return new OcrTimbanganResponse { Success = true, Weight = "54.50", Raw = "54.50 kg", Message = "OCR processed successfully (mock)" }; } catch (Exception ex) { _logger.LogError(ex, "Error processing OCR timbangan"); return new OcrTimbanganResponse { Success = false, Message = $"Terjadi kesalahan: {ex.Message}" }; } } } }