diff --git a/DoliMiddlewareApi/Controllers/InvoicesController.cs b/DoliMiddlewareApi/Controllers/InvoicesController.cs index 51de1f6..0c0c0ec 100644 --- a/DoliMiddlewareApi/Controllers/InvoicesController.cs +++ b/DoliMiddlewareApi/Controllers/InvoicesController.cs @@ -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 UpdateInvoice([Range(1, int.MaxValue)] int id, [FromBody] UpdateInvoiceDto updateInvoiceDto) + { + await _invoiceService.UpdateInvoiceAsync(id, updateInvoiceDto); + return NoContent(); + } } \ No newline at end of file diff --git a/DoliMiddlewareApi/Dtos/Dolibarr/InvoiceResponse.cs b/DoliMiddlewareApi/Dtos/Dolibarr/InvoiceResponse.cs index b3c67d5..8ccfd48 100644 --- a/DoliMiddlewareApi/Dtos/Dolibarr/InvoiceResponse.cs +++ b/DoliMiddlewareApi/Dtos/Dolibarr/InvoiceResponse.cs @@ -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; } } \ No newline at end of file diff --git a/DoliMiddlewareApi/Dtos/command/UpdateInvoiceDto.cs b/DoliMiddlewareApi/Dtos/command/UpdateInvoiceDto.cs new file mode 100644 index 0000000..6184eca --- /dev/null +++ b/DoliMiddlewareApi/Dtos/command/UpdateInvoiceDto.cs @@ -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 +} \ No newline at end of file diff --git a/DoliMiddlewareApi/Services/Clients/DolibarrApiClient.cs b/DoliMiddlewareApi/Services/Clients/DolibarrApiClient.cs index 488e503..e1f23a0 100644 --- a/DoliMiddlewareApi/Services/Clients/DolibarrApiClient.cs +++ b/DoliMiddlewareApi/Services/Clients/DolibarrApiClient.cs @@ -41,18 +41,14 @@ public class DolibarrApiClient // porque devuelve el id como response return await response.Content.ReadAsStringAsync(); } - - - - - - - - - - - + public async Task PutAsync(string endpoint, object requestBody) + { + var response = await _httpClient.PutAsJsonAsync(endpoint, requestBody); + await EnsureSuccessOrThrowAsync(response, endpoint); + + return await response.Content.ReadAsStringAsync(); + } diff --git a/DoliMiddlewareApi/Services/InvoiceService.cs b/DoliMiddlewareApi/Services/InvoiceService.cs index 4507bd2..57913fd 100644 --- a/DoliMiddlewareApi/Services/InvoiceService.cs +++ b/DoliMiddlewareApi/Services/InvoiceService.cs @@ -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($"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); + } } \ No newline at end of file diff --git a/generate-openapi.sh b/generate-openapi.sh new file mode 100755 index 0000000..0188534 --- /dev/null +++ b/generate-openapi.sh @@ -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" diff --git a/openapi.json b/openapi.json new file mode 100644 index 0000000..720072c --- /dev/null +++ b/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" + } + ] +} \ No newline at end of file