ProyectoGrupal/dolibarr-bff/DoliMiddlewareApi/Services/SupplierInvoiceService.cs

221 lines
9.6 KiB
C#
Raw Permalink Normal View History

using System.ComponentModel.DataAnnotations;
using System.Globalization;
using DoliMiddlewareApi.Dtos;
using DoliMiddlewareApi.Dtos.Dolibarr;
using DoliMiddlewareApi.Dtos.command;
using DoliMiddlewareApi.Dtos.query;
using DoliMiddlewareApi.Exceptions;
using DoliMiddlewareApi.Mappers;
using DoliMiddlewareApi.Services.Clients;
using DoliMiddlewareApi.Services.Notifications;
namespace DoliMiddlewareApi.Services;
public class SupplierInvoiceService(IDolibarrApiClient apiClient, INotificationService notifications)
{
public async Task<List<SupplierInvoiceDto>> GetInvoicesAsync(int limit, int page, string? status)
{
var endpoint = $"supplierinvoices?limit={limit}&page={page - 1}";
if (!string.IsNullOrEmpty(status))
endpoint += $"&status={status}";
var list = await apiClient.GetCollectionAsync<SupplierInvoiceResponse>(endpoint);
var dtos = list.Select(SupplierInvoiceMapper.MapToDto).ToList();
if (dtos.Count == 0) return dtos;
var supplierIds = dtos.Select(d => d.SupplierId).Distinct().ToList();
var names = await GetSupplierNamesAsync(supplierIds);
foreach (var dto in dtos)
dto.SupplierName = names.GetValueOrDefault(dto.SupplierId);
return dtos;
}
public async Task<SupplierInvoiceDetailDto> GetInvoiceAsync(int id)
{
var data = await apiClient.GetResourceAsync<SupplierInvoiceDetailResponse>($"supplierinvoices/{id}");
var dto = SupplierInvoiceMapper.MapToDetailDto(data);
if (dto.SupplierId > 0)
{
try
{
var supplier = await apiClient.GetResourceAsync<ClientResponse>($"thirdparties/{dto.SupplierId}");
dto.SupplierName = supplier?.name;
}
catch { /* supplier lookup is best-effort */ }
}
return dto;
}
public async Task<int> CreateInvoiceAsync(CreateSupplierInvoiceDto dto)
{
var payload = new
{
socid = dto.SupplierId.ToString(),
ref_supplier = dto.SupplierRef,
date = ((DateTimeOffset)dto.Date).ToUnixTimeSeconds().ToString(),
date_lim_reglement = dto.ExpireDate.HasValue
? ((DateTimeOffset)dto.ExpireDate.Value).ToUnixTimeSeconds().ToString()
: null,
note_public = dto.NotePublic,
note_private = dto.NotePrivate,
lines = dto.Lines.Select(l => new
{
desc = l.Description,
qty = l.Quantity.ToString(CultureInfo.InvariantCulture),
subprice = l.UnitPrice.ToString(CultureInfo.InvariantCulture),
tva_tx = l.TaxRate.ToString(CultureInfo.InvariantCulture)
}).ToArray()
};
var result = await apiClient.PostAsync("supplierinvoices", payload);
return int.TryParse(result.Trim('"'), out int id) ? id : 0;
}
public async Task UpdateInvoiceAsync(int id, UpdateSupplierInvoiceDto dto)
{
var current = await apiClient.GetResourceAsync<SupplierInvoiceDetailResponse>($"supplierinvoices/{id}");
if (dto.SupplierRef != null) current.ref_supplier = dto.SupplierRef;
if (dto.NotePublic != null) current.note_public = dto.NotePublic;
if (dto.NotePrivate != null) current.note_private = dto.NotePrivate;
if (dto.ExpireDate.HasValue)
current.date_lim_reglement = ((DateTimeOffset)dto.ExpireDate.Value).ToUnixTimeSeconds();
await apiClient.PutAsync($"supplierinvoices/{id}", current);
}
public async Task DeleteInvoiceAsync(int id)
{
var invoice = await apiClient.GetResourceAsync<SupplierInvoiceDetailResponse>($"supplierinvoices/{id}");
if (invoice.statut != "0") throw new ForbiddenException("Solo se pueden eliminar facturas de proveedor en borrador");
await apiClient.DeleteAsync($"supplierinvoices/{id}");
}
public async Task ChangeStatusAsync(int id, string status)
{
var normalized = status.Trim().ToLowerInvariant();
if (normalized == "paid")
{
var inv = await apiClient.GetResourceAsync<SupplierInvoiceDetailResponse>($"supplierinvoices/{id}");
var remain = double.TryParse(inv.remaintopay, System.Globalization.NumberStyles.Any,
System.Globalization.CultureInfo.InvariantCulture, out var r) ? r : 0;
var amount = remain > 0 ? remain
: double.TryParse(inv.total_ttc, System.Globalization.NumberStyles.Any,
System.Globalization.CultureInfo.InvariantCulture, out var t) ? t : 0;
var unixNow = new DateTimeOffset(DateTime.UtcNow).ToUnixTimeSeconds();
var payBody = new Dictionary<string, object>
{
["datepaye"] = unixNow,
["payment_mode_id"] = 6,
["closepaidinvoices"] = "yes",
["accountid"] = 1,
["amount"] = amount
};
await apiClient.PostAsync($"supplierinvoices/{id}/payments", payBody);
var paidInv = await apiClient.GetResourceAsync<SupplierInvoiceDetailResponse>($"supplierinvoices/{id}");
await notifications.NotifyInvoiceStatusChangedAsync(id, paidInv.@ref ?? $"#{id}", "paid");
return;
}
var endpoint = normalized switch
{
"draft" => $"supplierinvoices/{id}/settodraft",
"unpaid" => $"supplierinvoices/{id}/validate",
_ => throw new ValidationException("Estado invalido. Usa: draft, unpaid, paid.")
};
await apiClient.PostAsync(endpoint, new { });
var updatedInv = await apiClient.GetResourceAsync<SupplierInvoiceDetailResponse>($"supplierinvoices/{id}");
await notifications.NotifyInvoiceStatusChangedAsync(id, updatedInv.@ref ?? $"#{id}", normalized);
}
public async Task<string> AddLineAsync(int invoiceId, CreateInvoiceLineDto dto)
{
var invoice = await apiClient.GetResourceAsync<SupplierInvoiceDetailResponse>($"supplierinvoices/{invoiceId}");
if (invoice.statut != "0") throw new ForbiddenException("Solo se pueden añadir líneas a facturas en borrador");
var requestBody = new
{
desc = dto.Description,
qty = dto.Quantity.ToString(CultureInfo.InvariantCulture),
subprice = dto.UnitPrice.ToString(CultureInfo.InvariantCulture),
tva_tx = dto.TaxRate.ToString(CultureInfo.InvariantCulture)
};
return await apiClient.PostAsync($"supplierinvoices/{invoiceId}/lines", requestBody);
}
public async Task UpdateLineAsync(int invoiceId, int lineId, UpdateInvoiceLineDto dto)
{
var invoice = await apiClient.GetResourceAsync<SupplierInvoiceDetailResponse>($"supplierinvoices/{invoiceId}");
if (invoice.statut != "0") throw new ForbiddenException("Solo se pueden modificar líneas de facturas en borrador");
var existingLine = invoice.Lines?.FirstOrDefault(l => l.id == lineId.ToString());
if (existingLine == null) throw new NotFoundException($"Línea {lineId} no encontrada en factura {invoiceId}");
var requestBody = new Dictionary<string, object?>
{
["desc"] = dto.Description ?? existingLine.description ?? existingLine.desc,
["qty"] = dto.Quantity?.ToString(CultureInfo.InvariantCulture) ?? existingLine.qty,
["subprice"] = dto.UnitPrice?.ToString(CultureInfo.InvariantCulture) ?? existingLine.subprice,
["tva_tx"] = dto.TaxRate?.ToString(CultureInfo.InvariantCulture) ?? existingLine.tva_tx
};
await apiClient.PutAsync($"supplierinvoices/{invoiceId}/lines/{lineId}", requestBody);
}
public async Task DeleteLineAsync(int invoiceId, int lineId)
{
var invoice = await apiClient.GetResourceAsync<SupplierInvoiceDetailResponse>($"supplierinvoices/{invoiceId}");
if (invoice.statut != "0") throw new ForbiddenException("Solo se pueden eliminar líneas de facturas en borrador");
await apiClient.DeleteAsync($"supplierinvoices/{invoiceId}/lines/{lineId}");
}
public async Task<List<InvoicePaymentDto>> GetPaymentsAsync(int invoiceId)
{
var dataList = await apiClient.GetCollectionAsync<InvoicePaymentResponse>($"supplierinvoices/{invoiceId}/payments");
return dataList.Select(InvoiceMapper.MapToInvoicePaymentDto).ToList();
}
public async Task<int> AddPaymentAsync(int invoiceId, CreateInvoicePaymentDto dto)
{
var paymentModeId = dto.PaymentMethodId ?? dto.PaymentModeId;
var requestBody = new
{
datepaye = dto.PaymentDate.ToString("yyyy-MM-dd"),
paymentid = paymentModeId,
closepaidinvoices = dto.ClosePaidInvoices,
accountid = dto.AccountId,
num_payment = dto.PaymentNumber,
amount = dto.Amount?.ToString(CultureInfo.InvariantCulture)
};
var responseBody = await apiClient.PostAsync($"supplierinvoices/{invoiceId}/payments", requestBody);
return int.TryParse(responseBody.Trim('"'), out int result) ? result : 0;
}
private async Task<Dictionary<int, string>> GetSupplierNamesAsync(List<int> ids)
{
var validIds = ids.Distinct().Where(id => id > 0).ToList();
if (validIds.Count == 0) return [];
var tasks = validIds.Select(async id =>
{
try { return await apiClient.GetResourceAsync<ClientResponse>($"thirdparties/{id}"); }
catch { return null; }
});
var results = await Task.WhenAll(tasks);
return results
.Where(c => c is { id: not null })
.ToDictionary(c => int.Parse(c!.id!), c => c!.name ?? "");
}
}