using DoliMiddlewareApi.Dtos.command; using DoliMiddlewareApi.Dtos.Dolibarr; using DoliMiddlewareApi.Exceptions; using DoliMiddlewareApi.Services.Clients; using System.Text.Json; namespace DoliMiddlewareApi.Services; public class DocumentService(IDolibarrApiClient apiClient) { public async Task<(byte[] content, string filename)> BuildInvoicePdfAsync(string invoiceRef) { var request = new BuildDocumentRequest { modulepart = "invoice", original_file = $"{invoiceRef}/{invoiceRef}.pdf", doctemplate = "crabe", langcode = "es_ES" }; var response = await apiClient.PutAsync("documents/builddoc", request); var result = JsonSerializer.Deserialize(response); if (result == null || string.IsNullOrEmpty(result.content)) throw new Exception("Dolibarr no devolvió PDF"); var bytes = Convert.FromBase64String(result.content); return (bytes, result.filename); } public async Task> GetDocumentsAsync(string modulePart, int id) { try { var endpoint = $"documents?modulepart={modulePart}&id={id}"; var responseString = await apiClient.GetAsyncRaw(endpoint); var response = JsonSerializer.Deserialize>>(responseString); if (response == null) return []; return response.Select(d => { // Dolibarr's ECM array_merge overwrites 'name' with null (inherited CommonObject property). // Fall back to 'filename' which the ECM record keeps correctly. var name = d.TryGetValue("name", out var nameProp) && nameProp.ValueKind == JsonValueKind.String ? nameProp.GetString() : null; if (string.IsNullOrEmpty(name)) name = d.TryGetValue("filename", out var fnProp) && fnProp.ValueKind == JsonValueKind.String ? fnProp.GetString() : null; var path = d.TryGetValue("path", out var pathProp) && pathProp.ValueKind == JsonValueKind.String ? pathProp.GetString() : null; var size = d.TryGetValue("size", out var sizeProp) && sizeProp.ValueKind == JsonValueKind.Number ? sizeProp.GetInt64() : 0; // level1name is the invoice ref directory (e.g. "IN2605-0001"). // Dolibarr's download endpoint needs a relative path like "IN2605-0001/IN2605-0001.pdf", // not the absolute filesystem path stored in 'path'. var level1name = d.TryGetValue("level1name", out var l1Prop) && l1Prop.ValueKind == JsonValueKind.String ? l1Prop.GetString() : null; var relativePath = (!string.IsNullOrEmpty(level1name) && !string.IsNullOrEmpty(name)) ? $"{level1name}/{name}" : name; return new DocumentItem { Name = name, Path = path, Size = size, RelativePath = relativePath }; }).ToList(); } catch { // Module may not be active in Dolibarr — return empty list gracefully return []; } } public async Task<(byte[] content, string filename, string contentType)> DownloadDocumentAsync(string modulePart, string fileRef) { var endpoint = $"documents/download?modulepart={modulePart}&original_file={Uri.EscapeDataString(fileRef)}"; var responseString = await apiClient.GetAsyncRaw(endpoint); var response = JsonSerializer.Deserialize(responseString); if (response == null || string.IsNullOrEmpty(response.content)) throw new NotFoundException("Documento no encontrado"); var bytes = Convert.FromBase64String(response.content); var filename = response.filename ?? "document"; var contentType = filename.EndsWith(".pdf", StringComparison.OrdinalIgnoreCase) ? "application/pdf" : "application/octet-stream"; return (bytes, filename, contentType); } } public class DocumentItem { public string? Name { get; set; } public string? Path { get; set; } public string? RelativePath { get; set; } public long Size { get; set; } }