feat: Patch para el status que no dejaba por Update
This commit is contained in:
parent
c674ac66d9
commit
60a9a7f7db
|
|
@ -1,162 +0,0 @@
|
||||||
using DoliMiddlewareApi.Dtos.Dolibarr;
|
|
||||||
using DoliMiddlewareApi.Mappers;
|
|
||||||
|
|
||||||
namespace DoliMiddlewareApi.Tests.Mappers;
|
|
||||||
|
|
||||||
public class InvoiceMapperTests
|
|
||||||
{
|
|
||||||
// ARRANGE → Preparar datos de entrada
|
|
||||||
// ACT → Ejecutar método bajo test
|
|
||||||
// ASSERT → Verificar resultado
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void MapToInvoiceDto_WithValidResponse_ReturnsCorrectDto()
|
|
||||||
{
|
|
||||||
// ARRANGE: Creamos un objeto response simulado
|
|
||||||
var response = new InvoiceResponse
|
|
||||||
{
|
|
||||||
id = "123",
|
|
||||||
@ref = "FAC-2024-001",
|
|
||||||
date = 1704067200, // 01/01/2024 en Unix timestamp
|
|
||||||
date_lim_reglement = 1706745600, // 01/02/2024
|
|
||||||
socid = "456",
|
|
||||||
total_ttc = "1234.56",
|
|
||||||
remaintopay = "500.00",
|
|
||||||
statut = "1" // unpaid
|
|
||||||
};
|
|
||||||
|
|
||||||
// ACT: Ejecutamos el método bajo test
|
|
||||||
var result = InvoiceMapper.MapToInvoiceDto(response);
|
|
||||||
|
|
||||||
// ASSERT: Verificamos el resultado
|
|
||||||
Assert.Equal(123, result.Id);
|
|
||||||
Assert.Equal("FAC-2024-001", result.Number);
|
|
||||||
Assert.Equal(new DateTime(2024, 1, 1), result.Date);
|
|
||||||
Assert.Equal(new DateTime(2024, 2, 1), result.ExpireDate);
|
|
||||||
Assert.Equal(456, result.ClientId);
|
|
||||||
Assert.Equal(1234.56m, result.Total);
|
|
||||||
Assert.Equal(500.00m, result.RemainToPay);
|
|
||||||
Assert.Equal("unpaid", result.Status);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void MapToInvoiceDto_WithNullValues_HandlesGracefully()
|
|
||||||
{
|
|
||||||
// ARRANGE: Probamos edge cases (casos límite)
|
|
||||||
var response = new InvoiceResponse
|
|
||||||
{
|
|
||||||
id = "invalid",
|
|
||||||
@ref = null,
|
|
||||||
date = null,
|
|
||||||
date_lim_reglement = null,
|
|
||||||
socid = "not-a-number",
|
|
||||||
total_ttc = "not-a-decimal",
|
|
||||||
remaintopay = "",
|
|
||||||
statut = "999" // status desconocido
|
|
||||||
};
|
|
||||||
|
|
||||||
// ACT
|
|
||||||
var result = InvoiceMapper.MapToInvoiceDto(response);
|
|
||||||
|
|
||||||
// ASSERT: Verificamos que se maneja gracefulmente
|
|
||||||
Assert.Equal(0, result.Id); // fallback para parseo fallido
|
|
||||||
Assert.Equal("SIN-REF", result.Number); // valor por defecto
|
|
||||||
Assert.Null(result.Date); // null si no hay timestamp
|
|
||||||
Assert.Null(result.ExpireDate);
|
|
||||||
Assert.Equal(0, result.ClientId);
|
|
||||||
Assert.Null(result.Total); // null si parseo falla
|
|
||||||
Assert.Null(result.RemainToPay);
|
|
||||||
Assert.Equal("unknown", result.Status); // status desconocido
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData("0", "draft")]
|
|
||||||
[InlineData("1", "unpaid")]
|
|
||||||
[InlineData("2", "paid")]
|
|
||||||
[InlineData("3", "cancelled")]
|
|
||||||
[InlineData("999", "unknown")]
|
|
||||||
public void ConvertStatusToWord_WithDifferentStatuses_ReturnsCorrectWord(
|
|
||||||
string statusCode,
|
|
||||||
string expectedWord)
|
|
||||||
{
|
|
||||||
// ARRANGE
|
|
||||||
var response = new InvoiceResponse
|
|
||||||
{
|
|
||||||
id = "1",
|
|
||||||
statut = statusCode
|
|
||||||
};
|
|
||||||
|
|
||||||
// ACT
|
|
||||||
var result = InvoiceMapper.MapToInvoiceDto(response);
|
|
||||||
|
|
||||||
// ASSERT
|
|
||||||
Assert.Equal(expectedWord, result.Status);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void MapToInvoiceDetailDto_WithLines_ReturnsDtoWithLines()
|
|
||||||
{
|
|
||||||
// ARRANGE: Creamos una respuesta compleja con líneas
|
|
||||||
var response = new InvoiceDetailResponse
|
|
||||||
{
|
|
||||||
id = "1",
|
|
||||||
@ref = "FAC-001",
|
|
||||||
date = 1704067200,
|
|
||||||
socid = "10",
|
|
||||||
total_ttc = "100.00",
|
|
||||||
remaintopay = "0.00",
|
|
||||||
statut = "2", // paid
|
|
||||||
Lines = new List<InvoiceLineResponse>
|
|
||||||
{
|
|
||||||
new InvoiceLineResponse
|
|
||||||
{
|
|
||||||
id = "1",
|
|
||||||
description = "Product A",
|
|
||||||
qty = "2",
|
|
||||||
subprice = "50.00",
|
|
||||||
tva_tx = "21.00",
|
|
||||||
total_ttc = "121.00"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// ACT
|
|
||||||
var result = InvoiceMapper.MapToInvoiceDetailDto(response);
|
|
||||||
|
|
||||||
// ASSERT
|
|
||||||
Assert.NotEmpty(result.Lines);
|
|
||||||
Assert.Single(result.Lines);
|
|
||||||
Assert.Equal("Product A", result.Lines[0].Description);
|
|
||||||
Assert.Equal(2, result.Lines[0].Quantity);
|
|
||||||
Assert.Equal(50.00m, result.Lines[0].UnitPrice);
|
|
||||||
Assert.Equal(21.00m, result.Lines[0].TaxRate);
|
|
||||||
Assert.Equal(121.00m, result.Lines[0].Total);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void MapToInvoiceLineDto_WithValidLine_ReturnsCorrectDto()
|
|
||||||
{
|
|
||||||
// ARRANGE
|
|
||||||
var lineResponse = new InvoiceLineResponse
|
|
||||||
{
|
|
||||||
id = "42",
|
|
||||||
desc = "Fallback description",
|
|
||||||
description = "Main description",
|
|
||||||
qty = "3",
|
|
||||||
subprice = "99.99",
|
|
||||||
tva_tx = "10.00",
|
|
||||||
total_ttc = "329.97"
|
|
||||||
};
|
|
||||||
|
|
||||||
// ACT
|
|
||||||
var result = InvoiceMapper.MapToInvoiceLineDto(lineResponse);
|
|
||||||
|
|
||||||
// ASSERT
|
|
||||||
Assert.Equal(42, result.Id);
|
|
||||||
Assert.Equal("Main description", result.Description); // priority: description > desc
|
|
||||||
Assert.Equal(3, result.Quantity);
|
|
||||||
Assert.Equal(99.99m, result.UnitPrice);
|
|
||||||
Assert.Equal(10.00m, result.TaxRate);
|
|
||||||
Assert.Equal(329.97m, result.Total);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,277 +0,0 @@
|
||||||
using System.Globalization;
|
|
||||||
using System.Net;
|
|
||||||
using System.Net.Http.Json;
|
|
||||||
using DoliMiddlewareApi.Dtos.Dolibarr;
|
|
||||||
using DoliMiddlewareApi.Exceptions;
|
|
||||||
using DoliMiddlewareApi.Services;
|
|
||||||
using DoliMiddlewareApi.Services.Clients;
|
|
||||||
using Moq;
|
|
||||||
using Moq.Protected;
|
|
||||||
|
|
||||||
namespace DoliMiddlewareApi.Tests.Services;
|
|
||||||
|
|
||||||
public class InvoiceServiceTests
|
|
||||||
{
|
|
||||||
private readonly Mock<IDolibarrApiClient> _mockApiClient;
|
|
||||||
private readonly InvoiceService _invoiceService;
|
|
||||||
|
|
||||||
public InvoiceServiceTests()
|
|
||||||
{
|
|
||||||
// ARRANGE: Crear mock del cliente API
|
|
||||||
_mockApiClient = new Mock<IDolibarrApiClient>();
|
|
||||||
_invoiceService = new InvoiceService(_mockApiClient.Object);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ===== TESTS POSITIVOS =====
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task GetInvoiceAsync_WithValidId_ReturnsInvoiceDetail()
|
|
||||||
{
|
|
||||||
// ARRANGE: Preparamos el mock para que devuelva datos
|
|
||||||
var expectedResponse = new InvoiceDetailResponse
|
|
||||||
{
|
|
||||||
id = "1",
|
|
||||||
@ref = "FAC-001",
|
|
||||||
date = 1704067200,
|
|
||||||
socid = "10",
|
|
||||||
total_ttc = "100.00",
|
|
||||||
remaintopay = "0.00",
|
|
||||||
statut = "2"
|
|
||||||
};
|
|
||||||
|
|
||||||
_mockApiClient
|
|
||||||
.Setup(client => client.GetResourceAsync<InvoiceDetailResponse>("invoices/1"))
|
|
||||||
.ReturnsAsync(expectedResponse);
|
|
||||||
|
|
||||||
// ACT: Ejecutamos el método bajo test
|
|
||||||
var result = await _invoiceService.GetInvoiceAsync(1);
|
|
||||||
|
|
||||||
// ASSERT: Verificamos que el mock fue llamado con el endpoint correcto
|
|
||||||
_mockApiClient.Verify(
|
|
||||||
client => client.GetResourceAsync<InvoiceDetailResponse>("invoices/1"),
|
|
||||||
Times.Once
|
|
||||||
);
|
|
||||||
|
|
||||||
// Y que el resultado es el esperado
|
|
||||||
Assert.Equal(1, result.Id);
|
|
||||||
Assert.Equal("FAC-001", result.Number);
|
|
||||||
Assert.Equal(100.00m, result.Total);
|
|
||||||
Assert.Equal("paid", result.Status);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task GetInvoicesAsync_WithDefaultParams_ReturnsList()
|
|
||||||
{
|
|
||||||
// ARRANGE
|
|
||||||
var expectedList = new List<InvoiceResponse>
|
|
||||||
{
|
|
||||||
new InvoiceResponse { id = "1", @ref = "FAC-001", statut = "0" },
|
|
||||||
new InvoiceResponse { id = "2", @ref = "FAC-002", statut = "1" }
|
|
||||||
};
|
|
||||||
|
|
||||||
_mockApiClient
|
|
||||||
.Setup(client => client.GetCollectionAsync<InvoiceResponse>("invoices?limit=50&page=0"))
|
|
||||||
.ReturnsAsync(expectedList);
|
|
||||||
|
|
||||||
// ACT
|
|
||||||
var result = await _invoiceService.GetInvoicesAsync();
|
|
||||||
|
|
||||||
// ASSERT
|
|
||||||
Assert.Equal(2, result.Count);
|
|
||||||
Assert.Equal(1, result[0].Id);
|
|
||||||
Assert.Equal(2, result[1].Id);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task GetInvoicesAsync_WithParams_ReturnsCorrectList()
|
|
||||||
{
|
|
||||||
// ARRANGE
|
|
||||||
var expectedList = new List<InvoiceResponse>
|
|
||||||
{
|
|
||||||
new InvoiceResponse { id = "3", @ref = "FAC-003", statut = "2" }
|
|
||||||
};
|
|
||||||
|
|
||||||
_mockApiClient
|
|
||||||
.Setup(client => client.GetCollectionAsync<InvoiceResponse>("invoices?limit=10&page=1&status=2"))
|
|
||||||
.ReturnsAsync(expectedList);
|
|
||||||
|
|
||||||
// ACT: Pasamos página 2 (que se convierte a page=1 en el endpoint)
|
|
||||||
var result = await _invoiceService.GetInvoicesAsync(limit: 10, page: 2, status: "2");
|
|
||||||
|
|
||||||
// ASSERT
|
|
||||||
Assert.Single(result);
|
|
||||||
Assert.Equal(3, result[0].Id);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task CreateInvoiceAsync_WithValidDto_ReturnsNewId()
|
|
||||||
{
|
|
||||||
// ARRANGE
|
|
||||||
var dto = new Dtos.command.CreateInvoiceDto
|
|
||||||
{
|
|
||||||
ClientId = 10,
|
|
||||||
Date = DateTime.Now,
|
|
||||||
Status = "unpaid",
|
|
||||||
Reference = "TEST-001",
|
|
||||||
Lines = new List<Dtos.command.CreateInvoiceLineDto>
|
|
||||||
{
|
|
||||||
new Dtos.command.CreateInvoiceLineDto
|
|
||||||
{
|
|
||||||
Description = "Test Product",
|
|
||||||
Quantity = 2,
|
|
||||||
UnitPrice = 50.00m,
|
|
||||||
TaxRate = 21.00m
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
_mockApiClient
|
|
||||||
.Setup(client => client.PostAsync("invoices", It.IsAny<object>()))
|
|
||||||
.ReturnsAsync("42");
|
|
||||||
|
|
||||||
// ACT
|
|
||||||
var result = await _invoiceService.CreateInvoiceAsync(dto);
|
|
||||||
|
|
||||||
// ASSERT
|
|
||||||
Assert.Equal(42, result);
|
|
||||||
_mockApiClient.Verify(client => client.PostAsync("invoices", It.IsAny<object>()), Times.Once);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ===== TESTS DE EXCEPCIONES =====
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task GetInvoiceAsync_WhenNotFound_ThrowsNotFoundException()
|
|
||||||
{
|
|
||||||
// ARRANGE: Simulamos que el API client lanza NotFoundException
|
|
||||||
_mockApiClient
|
|
||||||
.Setup(client => client.GetResourceAsync<InvoiceDetailResponse>("invoices/999"))
|
|
||||||
.ThrowsAsync(new NotFoundException("Invoice not found"));
|
|
||||||
|
|
||||||
// ACT + ASSERT: Verificamos que se propaga la excepción
|
|
||||||
await Assert.ThrowsAsync<NotFoundException>(
|
|
||||||
async () => await _invoiceService.GetInvoiceAsync(999)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task AddInvoiceLineAsync_WhenInvoiceNotDraft_ThrowsForbiddenException()
|
|
||||||
{
|
|
||||||
// ARRANGE: Invoice no está en borrador
|
|
||||||
var invoiceResponse = new InvoiceDetailResponse
|
|
||||||
{
|
|
||||||
id = "1",
|
|
||||||
statut = "2" // paid, no draft (0)
|
|
||||||
};
|
|
||||||
|
|
||||||
_mockApiClient
|
|
||||||
.Setup(client => client.GetResourceAsync<InvoiceDetailResponse>("invoices/1"))
|
|
||||||
.ReturnsAsync(invoiceResponse);
|
|
||||||
|
|
||||||
var lineDto = new Dtos.command.CreateInvoiceLineDto
|
|
||||||
{
|
|
||||||
Description = "Test",
|
|
||||||
Quantity = 1,
|
|
||||||
UnitPrice = 10.00m,
|
|
||||||
TaxRate = 21.00m
|
|
||||||
};
|
|
||||||
|
|
||||||
// ACT + ASSERT
|
|
||||||
await Assert.ThrowsAsync<ForbiddenException>(
|
|
||||||
async () => await _invoiceService.AddInvoiceLineAsync(1, lineDto)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task UpdateInvoiceAsync_WhenInvoiceNotDraft_ThrowsForbiddenException()
|
|
||||||
{
|
|
||||||
// ARRANGE
|
|
||||||
var currentInvoice = new InvoiceDetailResponse
|
|
||||||
{
|
|
||||||
id = "1",
|
|
||||||
statut = "1" // unpaid, no draft (0)
|
|
||||||
};
|
|
||||||
|
|
||||||
_mockApiClient
|
|
||||||
.Setup(client => client.GetResourceAsync<InvoiceDetailResponse>("invoices/1"))
|
|
||||||
.ReturnsAsync(currentInvoice);
|
|
||||||
|
|
||||||
var updateDto = new Dtos.command.UpdateInvoiceDto
|
|
||||||
{
|
|
||||||
Number = "NEW-REF"
|
|
||||||
};
|
|
||||||
|
|
||||||
// ACT + ASSERT
|
|
||||||
await Assert.ThrowsAsync<ForbiddenException>(
|
|
||||||
async () => await _invoiceService.UpdateInvoiceAsync(1, updateDto)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task UpdateInvoiceAsync_WithValidDraft_UpdatesCorrectly()
|
|
||||||
{
|
|
||||||
// ARRANGE
|
|
||||||
var currentInvoice = new InvoiceDetailResponse
|
|
||||||
{
|
|
||||||
id = "1",
|
|
||||||
@ref = "OLD-REF",
|
|
||||||
statut = "0", // draft
|
|
||||||
note_public = null,
|
|
||||||
note_private = null
|
|
||||||
};
|
|
||||||
|
|
||||||
_mockApiClient
|
|
||||||
.Setup(client => client.GetResourceAsync<InvoiceDetailResponse>("invoices/1"))
|
|
||||||
.ReturnsAsync(currentInvoice);
|
|
||||||
|
|
||||||
_mockApiClient
|
|
||||||
.Setup(client => client.PutAsync("invoices/1", It.IsAny<object>()))
|
|
||||||
.ReturnsAsync("OK");
|
|
||||||
|
|
||||||
var updateDto = new Dtos.command.UpdateInvoiceDto
|
|
||||||
{
|
|
||||||
Number = "NEW-REF",
|
|
||||||
NotePublic = "Public note",
|
|
||||||
Status = "unpaid" // Cambia a status "1"
|
|
||||||
};
|
|
||||||
|
|
||||||
// ACT
|
|
||||||
await _invoiceService.UpdateInvoiceAsync(1, updateDto);
|
|
||||||
|
|
||||||
// ASSERT
|
|
||||||
_mockApiClient.Verify(client => client.PutAsync("invoices/1", It.IsAny<object>()), Times.Once);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task AddInvoiceLineAsync_WithValidDraft_AddsLineSuccessfully()
|
|
||||||
{
|
|
||||||
// ARRANGE
|
|
||||||
var invoiceResponse = new InvoiceDetailResponse
|
|
||||||
{
|
|
||||||
id = "1",
|
|
||||||
statut = "0" // draft
|
|
||||||
};
|
|
||||||
|
|
||||||
_mockApiClient
|
|
||||||
.Setup(client => client.GetResourceAsync<InvoiceDetailResponse>("invoices/1"))
|
|
||||||
.ReturnsAsync(invoiceResponse);
|
|
||||||
|
|
||||||
_mockApiClient
|
|
||||||
.Setup(client => client.PostAsync("invoices/1/lines", It.IsAny<object>()))
|
|
||||||
.ReturnsAsync("new-line-id");
|
|
||||||
|
|
||||||
var lineDto = new Dtos.command.CreateInvoiceLineDto
|
|
||||||
{
|
|
||||||
Description = "Test Product",
|
|
||||||
Quantity = 3,
|
|
||||||
UnitPrice = 25.00m,
|
|
||||||
TaxRate = 10.00m
|
|
||||||
};
|
|
||||||
|
|
||||||
// ACT
|
|
||||||
var result = await _invoiceService.AddInvoiceLineAsync(1, lineDto);
|
|
||||||
|
|
||||||
// ASSERT
|
|
||||||
Assert.Equal("new-line-id", result);
|
|
||||||
_mockApiClient.Verify(client => client.PostAsync("invoices/1/lines", It.IsAny<object>()), Times.Once);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -70,4 +70,18 @@ public class InvoicesController(InvoiceService invoiceService) : ControllerBase
|
||||||
var result = await invoiceService.AddInvoiceLineAsync(id, lineDto);
|
var result = await invoiceService.AddInvoiceLineAsync(id, lineDto);
|
||||||
return CreatedAtAction(nameof(GetInvoice), new { id }, result);
|
return CreatedAtAction(nameof(GetInvoice), new { id }, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpPatch("{id:int}/status")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||||
|
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status403Forbidden)]
|
||||||
|
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||||
|
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status500InternalServerError)]
|
||||||
|
public async Task<IActionResult> UpdateInvoiceStatus(
|
||||||
|
[Range(1, int.MaxValue)] int id,
|
||||||
|
[FromBody] UpdateInvoiceStatusDto updateStatusDto)
|
||||||
|
{
|
||||||
|
await invoiceService.ChangeInvoiceStatusAsync(id, updateStatusDto.Status);
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,5 @@ public class UpdateInvoiceDto
|
||||||
public string? NotePublic { get; set; }
|
public string? NotePublic { get; set; }
|
||||||
public string? NotePrivate { get; set; }
|
public string? NotePrivate { get; set; }
|
||||||
|
|
||||||
public string Status { get; set; } = "draft";
|
|
||||||
|
|
||||||
// Lines quitadas - usa POST /lines para añadir líneas
|
// Lines quitadas - usa POST /lines para añadir líneas
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace DoliMiddlewareApi.Dtos.command;
|
||||||
|
|
||||||
|
public class UpdateInvoiceStatusDto
|
||||||
|
{
|
||||||
|
[Required]
|
||||||
|
[RegularExpression("^(draft|unpaid|paid)$")]
|
||||||
|
public string Status { get; set; } = "draft";
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using DoliMiddlewareApi.Dtos;
|
using DoliMiddlewareApi.Dtos;
|
||||||
using DoliMiddlewareApi.Dtos.command;
|
using DoliMiddlewareApi.Dtos.command;
|
||||||
|
|
@ -108,7 +109,6 @@ public class InvoiceService(IDolibarrApiClient apiClient)
|
||||||
if (dto.Number != null) current.@ref = dto.Number;
|
if (dto.Number != null) current.@ref = dto.Number;
|
||||||
if (dto.NotePublic != null) current.note_public = dto.NotePublic;
|
if (dto.NotePublic != null) current.note_public = dto.NotePublic;
|
||||||
if (dto.NotePrivate != null) current.note_private = dto.NotePrivate;
|
if (dto.NotePrivate != null) current.note_private = dto.NotePrivate;
|
||||||
current.statut = InvoiceMapper.ConvertStatusToDolibarr(dto.Status);
|
|
||||||
if (dto.ExpireDate.HasValue) current.date_lim_reglement = ((DateTimeOffset)dto.ExpireDate.Value).ToUnixTimeSeconds();
|
if (dto.ExpireDate.HasValue) current.date_lim_reglement = ((DateTimeOffset)dto.ExpireDate.Value).ToUnixTimeSeconds();
|
||||||
|
|
||||||
// No tocar: date, socid, lines (Dolibarr no los cambia en PUT)
|
// No tocar: date, socid, lines (Dolibarr no los cambia en PUT)
|
||||||
|
|
@ -116,6 +116,20 @@ public class InvoiceService(IDolibarrApiClient apiClient)
|
||||||
await apiClient.PutAsync($"invoices/{id}", current);
|
await apiClient.PutAsync($"invoices/{id}", current);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task ChangeInvoiceStatusAsync(int id, string status)
|
||||||
|
{
|
||||||
|
var normalized = status.Trim().ToLowerInvariant();
|
||||||
|
var endpoint = normalized switch
|
||||||
|
{
|
||||||
|
"draft" => $"invoices/{id}/settodraft",
|
||||||
|
"unpaid" => $"invoices/{id}/settounpaid",
|
||||||
|
"paid" => $"invoices/{id}/settopaid",
|
||||||
|
_ => throw new ValidationException("Estado invalido. Usa: draft, unpaid, paid.")
|
||||||
|
};
|
||||||
|
|
||||||
|
await apiClient.PostAsync(endpoint, new { });
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<Dictionary<int, string>> GetClientNamesAsync(List<int> clientIds)
|
private async Task<Dictionary<int, string>> GetClientNamesAsync(List<int> clientIds)
|
||||||
{
|
{
|
||||||
var uniqueIds = clientIds.Distinct().ToList();
|
var uniqueIds = clientIds.Distinct().ToList();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue