fix(orders): copy order lines into invoice on creation
POST /invoices with only fk_commande+socid creates an empty invoice. Lines must be included in the request body explicitly. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
312829e5f4
commit
c02e771f0a
|
|
@ -9,7 +9,6 @@ import org.springframework.stereotype.Component;
|
||||||
import org.springframework.web.client.RestClient;
|
import org.springframework.web.client.RestClient;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Typed HTTP client for the Dolibarr REST API.
|
* Typed HTTP client for the Dolibarr REST API.
|
||||||
|
|
@ -182,17 +181,38 @@ public class DolibarrClient {
|
||||||
// ── Invoices ─────────────────────────────────────────────────────────
|
// ── Invoices ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an invoice linked to an existing order.
|
* Creates an invoice linked to an existing Dolibarr order, copying its lines.
|
||||||
*
|
*
|
||||||
* @param orderId Dolibarr internal order ID
|
* <p>Dolibarr's {@code POST /invoices} does not copy order lines automatically when
|
||||||
* @param socid customer (thirdparty) internal ID — required by Dolibarr even with fk_commande
|
* only {@code fk_commande + socid} are sent — lines must be included in the body.</p>
|
||||||
|
*
|
||||||
|
* @param order the created order (must have {@code id}, {@code socid}, and {@code lines})
|
||||||
*/
|
*/
|
||||||
public DolibarrInvoiceDto createInvoiceFromOrder(Integer orderId, Integer socid) {
|
public DolibarrInvoiceDto createInvoiceFromOrder(DolibarrOrderDto order) {
|
||||||
log.debug("Creating Dolibarr invoice from orderId={}", orderId);
|
log.debug("Creating Dolibarr invoice from orderId={}", order.id());
|
||||||
Map<String, Object> body = Map.of("fk_commande", orderId, "socid", socid);
|
|
||||||
|
List<DolibarrInvoiceDto.InvoiceLine> invoiceLines = order.lines() == null
|
||||||
|
? List.of()
|
||||||
|
: order.lines().stream()
|
||||||
|
.map(l -> DolibarrInvoiceDto.InvoiceLine.builder()
|
||||||
|
.fkProduct(l.fkProduct())
|
||||||
|
.productRef(l.productRef())
|
||||||
|
.desc(l.desc())
|
||||||
|
.subprice(l.subprice())
|
||||||
|
.qty(l.qty())
|
||||||
|
.tvaTx(l.tvaTx())
|
||||||
|
.build())
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
DolibarrInvoiceDto invoiceBody = DolibarrInvoiceDto.builder()
|
||||||
|
.fkCommande(order.id())
|
||||||
|
.socid(order.socid())
|
||||||
|
.lines(invoiceLines)
|
||||||
|
.build();
|
||||||
|
|
||||||
Integer newId = restClient.post()
|
Integer newId = restClient.post()
|
||||||
.uri("/invoices")
|
.uri("/invoices")
|
||||||
.body(body)
|
.body(invoiceBody)
|
||||||
.retrieve()
|
.retrieve()
|
||||||
.body(Integer.class);
|
.body(Integer.class);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,18 +3,41 @@ package com.teterialosjuanjos.tfg.sync_service.integration.dolibarr.dto;
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.Builder;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Minimal representation of a Dolibarr invoice.
|
* Represents a Dolibarr invoice.
|
||||||
* Used for {@code GET /invoices/{id}} responses after creation via {@code POST /invoices}.
|
* Used for {@code POST /invoices} request bodies (with lines) and {@code GET /invoices/{id}} responses.
|
||||||
*/
|
*/
|
||||||
|
@Builder
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
public record DolibarrInvoiceDto(
|
public record DolibarrInvoiceDto(
|
||||||
Integer id,
|
Integer id,
|
||||||
String ref,
|
String ref,
|
||||||
|
Integer socid,
|
||||||
/** Linked order internal ID */
|
/** Linked order internal ID */
|
||||||
@JsonProperty("fk_commande") Integer fkCommande,
|
@JsonProperty("fk_commande") Integer fkCommande,
|
||||||
/** 0 = draft, 1 = validated, 2 = paid, 3 = abandoned */
|
/** 0 = draft, 1 = validated, 2 = paid, 3 = abandoned */
|
||||||
Integer statut
|
Integer statut,
|
||||||
) {}
|
List<InvoiceLine> lines
|
||||||
|
) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One line of an invoice. Mirrors {@link DolibarrOrderDto.OrderLine} fields
|
||||||
|
* so order lines can be copied directly into the invoice on creation.
|
||||||
|
*/
|
||||||
|
@Builder
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
|
public record InvoiceLine(
|
||||||
|
@JsonProperty("fk_product") Integer fkProduct,
|
||||||
|
@JsonProperty("product_ref") String productRef,
|
||||||
|
String desc,
|
||||||
|
Double subprice,
|
||||||
|
Double qty,
|
||||||
|
@JsonProperty("tva_tx") Double tvaTx
|
||||||
|
) {}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -130,9 +130,8 @@ public class OrderSyncService {
|
||||||
|
|
||||||
DolibarrOrderDto createdOrder = dolibarrClient.createOrder(orderDto);
|
DolibarrOrderDto createdOrder = dolibarrClient.createOrder(orderDto);
|
||||||
|
|
||||||
// 3. Create linked invoice
|
// 3. Create linked invoice with lines copied from the order
|
||||||
DolibarrInvoiceDto invoice = dolibarrClient.createInvoiceFromOrder(
|
DolibarrInvoiceDto invoice = dolibarrClient.createInvoiceFromOrder(createdOrder);
|
||||||
createdOrder.id(), customer.id());
|
|
||||||
|
|
||||||
// 4. Persist mapping to prevent re-import
|
// 4. Persist mapping to prevent re-import
|
||||||
orderMappingRepository.save(OrderMapping.builder()
|
orderMappingRepository.save(OrderMapping.builder()
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue