feat: Implementar endpoint PUT para actualizar facturas draft, limitado a campos permitidos por Dolibarr (ref, notas, estado, fecha expiración) y unificar campo 'number' para consistencia
This commit is contained in:
parent
1fd331ac17
commit
a06ac2bdd9
|
|
@ -50,4 +50,16 @@ public class InvoicesController : ControllerBase
|
|||
var invoiceId= await _invoiceService.CreateInvoiceAsync(createInvoiceDto);
|
||||
return CreatedAtAction(nameof(GetInvoice), new { id = invoiceId }, invoiceId);
|
||||
}
|
||||
|
||||
[HttpPut("{id:int}")]
|
||||
[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> UpdateInvoice([Range(1, int.MaxValue)] int id, [FromBody] UpdateInvoiceDto updateInvoiceDto)
|
||||
{
|
||||
await _invoiceService.UpdateInvoiceAsync(id, updateInvoiceDto);
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
|
|
@ -12,4 +12,7 @@ public class InvoiceResponse
|
|||
public string? total_ttc { get; set; }
|
||||
public string? remaintopay { get; set; }
|
||||
public string? statut { get; set; }
|
||||
|
||||
public string? note_public { get; set; }
|
||||
public string? note_private { get; set; }
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace DoliMiddlewareApi.Dtos.command;
|
||||
|
||||
public class UpdateInvoiceDto
|
||||
{
|
||||
// ClientId y Date no se cambian en PUT (Dolibarr no permite)
|
||||
|
||||
public DateTime? ExpireDate { get; set; }
|
||||
|
||||
[StringLength(100)]
|
||||
public string? Number { get; set; } // Cambiado de Reference a Number para consistencia con GET
|
||||
|
||||
public string? NotePublic { get; set; }
|
||||
public string? NotePrivate { get; set; }
|
||||
|
||||
public string Status { get; set; } = "draft";
|
||||
|
||||
// Lines quitadas - usa POST /lines para añadir líneas
|
||||
}
|
||||
|
|
@ -41,18 +41,14 @@ public class DolibarrApiClient
|
|||
// porque devuelve el id como response
|
||||
return await response.Content.ReadAsStringAsync();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public async Task<string> PutAsync(string endpoint, object requestBody)
|
||||
{
|
||||
var response = await _httpClient.PutAsJsonAsync(endpoint, requestBody);
|
||||
await EnsureSuccessOrThrowAsync(response, endpoint);
|
||||
|
||||
return await response.Content.ReadAsStringAsync();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ using System.Globalization;
|
|||
using DoliMiddlewareApi.Dtos;
|
||||
using DoliMiddlewareApi.Dtos.command;
|
||||
using DoliMiddlewareApi.Dtos.Dolibarr;
|
||||
using DoliMiddlewareApi.Exceptions;
|
||||
using DoliMiddlewareApi.Mappers;
|
||||
using DoliMiddlewareApi.Services.Clients;
|
||||
|
||||
|
|
@ -66,4 +67,22 @@ public class InvoiceService
|
|||
|
||||
return int.Parse(responseBody);
|
||||
}
|
||||
|
||||
public async Task UpdateInvoiceAsync(int id, UpdateInvoiceDto dto)
|
||||
{
|
||||
// GET el JSON completo
|
||||
var current = await _apiClient.GetResourceAsync<InvoiceDetailResponse>($"invoices/{id}");
|
||||
if (current.statut != "0") throw new ForbiddenException("Solo drafts");
|
||||
|
||||
// Modifica solo campos que Dolibarr permite en PUT
|
||||
if (dto.Number != null) current.@ref = dto.Number;
|
||||
if (dto.NotePublic != null) current.note_public = dto.NotePublic;
|
||||
if (dto.NotePrivate != null) current.note_private = dto.NotePrivate;
|
||||
current.statut = dto.Status == "unpaid" ? "1" : "0";
|
||||
if (dto.ExpireDate.HasValue) current.date_lim_reglement = ((DateTimeOffset)dto.ExpireDate.Value).ToUnixTimeSeconds();
|
||||
|
||||
// No tocar: date, socid, lines (Dolibarr no los cambia en PUT)
|
||||
|
||||
await _apiClient.PutAsync($"invoices/{id}", current);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo "🚀 Generando openapi.json..."
|
||||
|
||||
# Variables
|
||||
PORT=5123
|
||||
PROJECT_DIR="DoliMiddlewareApi"
|
||||
OUTPUT_FILE="openapi.json"
|
||||
|
||||
# Levantar la API en background
|
||||
cd "$PROJECT_DIR"
|
||||
dotnet run --urls "http://localhost:$PORT" > /dev/null 2>&1 &
|
||||
PID=$!
|
||||
|
||||
# Esperar a que arranque
|
||||
echo "⏳ Esperando a que la API arranque..."
|
||||
sleep 8
|
||||
|
||||
# Descargar el OpenAPI spec
|
||||
echo "📥 Descargando openapi.json..."
|
||||
curl -s "http://localhost:$PORT/openapi/v1.json" -o "../$OUTPUT_FILE"
|
||||
|
||||
# Matar el proceso
|
||||
kill $PID 2>/dev/null || true
|
||||
|
||||
echo "✅ openapi.json generado exitosamente en: $OUTPUT_FILE"
|
||||
echo ""
|
||||
echo "📦 Para generar cliente TypeScript:"
|
||||
echo " npx @openapitools/openapi-generator-cli generate -i openapi.json -g typescript-fetch -o ./client"
|
||||
echo ""
|
||||
echo "📦 Para importar en Postman:"
|
||||
echo " File > Import > Selecciona openapi.json"
|
||||
|
|
@ -0,0 +1,333 @@
|
|||
{
|
||||
"openapi": "3.0.4",
|
||||
"info": {
|
||||
"title": "DoliMiddlewareApi",
|
||||
"version": "1.0"
|
||||
},
|
||||
"paths": {
|
||||
"/api/Invoices": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Invoices"
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "limit",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"format": "int32",
|
||||
"default": 50
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "page",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"maximum": 2147483647,
|
||||
"minimum": 1,
|
||||
"type": "integer",
|
||||
"format": "int32",
|
||||
"default": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "status",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"content": {
|
||||
"text/plain": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/InvoiceDto"
|
||||
}
|
||||
}
|
||||
},
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/InvoiceDto"
|
||||
}
|
||||
}
|
||||
},
|
||||
"text/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/InvoiceDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"content": {
|
||||
"text/plain": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ProblemDetails"
|
||||
}
|
||||
},
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ProblemDetails"
|
||||
}
|
||||
},
|
||||
"text/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ProblemDetails"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/Invoices/{id}": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Invoices"
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"content": {
|
||||
"text/plain": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/InvoiceDetailDto"
|
||||
}
|
||||
},
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/InvoiceDetailDto"
|
||||
}
|
||||
},
|
||||
"text/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/InvoiceDetailDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Not Found",
|
||||
"content": {
|
||||
"text/plain": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ProblemDetails"
|
||||
}
|
||||
},
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ProblemDetails"
|
||||
}
|
||||
},
|
||||
"text/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ProblemDetails"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"content": {
|
||||
"text/plain": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ProblemDetails"
|
||||
}
|
||||
},
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ProblemDetails"
|
||||
}
|
||||
},
|
||||
"text/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ProblemDetails"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"InvoiceDetailDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"number": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"date": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"nullable": true
|
||||
},
|
||||
"expireDate": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"nullable": true
|
||||
},
|
||||
"clientId": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"total": {
|
||||
"type": "number",
|
||||
"format": "double",
|
||||
"nullable": true
|
||||
},
|
||||
"remainToPay": {
|
||||
"type": "number",
|
||||
"format": "double",
|
||||
"nullable": true
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"lines": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/InvoiceLineDto"
|
||||
},
|
||||
"nullable": true
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"InvoiceDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"number": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"date": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"nullable": true
|
||||
},
|
||||
"expireDate": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"nullable": true
|
||||
},
|
||||
"clientId": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"total": {
|
||||
"type": "number",
|
||||
"format": "double",
|
||||
"nullable": true
|
||||
},
|
||||
"remainToPay": {
|
||||
"type": "number",
|
||||
"format": "double",
|
||||
"nullable": true
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"InvoiceLineDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"quantity": {
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
},
|
||||
"unitPrice": {
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
},
|
||||
"taxRate": {
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
},
|
||||
"total": {
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"ProblemDetails": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"title": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"status": {
|
||||
"type": "integer",
|
||||
"format": "int32",
|
||||
"nullable": true
|
||||
},
|
||||
"detail": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"instance": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
}
|
||||
},
|
||||
"additionalProperties": { }
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
{
|
||||
"name": "Invoices"
|
||||
}
|
||||
]
|
||||
}
|
||||
Loading…
Reference in New Issue