addresses
This commit is contained in:
parent
877cb594b2
commit
c3c5b22b12
|
|
@ -1,6 +1,7 @@
|
|||
package com.teterialosjuanjos.tfg.sync_service.integration.dolibarr;
|
||||
|
||||
import com.teterialosjuanjos.tfg.sync_service.integration.dolibarr.dto.*;
|
||||
import java.util.Collections;
|
||||
import com.teterialosjuanjos.tfg.sync_service.integration.dolibarr.exception.DolibarrApiException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
|
|
@ -167,10 +168,28 @@ public class DolibarrClient {
|
|||
.retrieve()
|
||||
.body(Integer.class);
|
||||
|
||||
return restClient.get()
|
||||
DolibarrThirdpartyDto created = restClient.get()
|
||||
.uri("/thirdparties/{id}", newId)
|
||||
.retrieve()
|
||||
.body(DolibarrThirdpartyDto.class);
|
||||
|
||||
// Dolibarr returns the existing thirdparty ID when name already exists (name uniqueness constraint).
|
||||
// If the returned thirdparty belongs to a different customer, retry with email suffix to disambiguate.
|
||||
if (created != null && !email.equalsIgnoreCase(created.email())) {
|
||||
log.warn("Dolibarr name collision for '{}', retrying with email suffix", name);
|
||||
DolibarrThirdpartyDto disambiguated = new DolibarrThirdpartyDto(
|
||||
null, name + " (" + email + ")", email, 1, null, address, zip, city, null, null);
|
||||
Integer newId2 = restClient.post()
|
||||
.uri("/thirdparties")
|
||||
.body(disambiguated)
|
||||
.retrieve()
|
||||
.body(Integer.class);
|
||||
return restClient.get()
|
||||
.uri("/thirdparties/{id}", newId2)
|
||||
.retrieve()
|
||||
.body(DolibarrThirdpartyDto.class);
|
||||
}
|
||||
return created;
|
||||
}
|
||||
|
||||
public DolibarrAddressDto createThirdpartyAddress(Integer socid, DolibarrAddressDto dto) {
|
||||
|
|
@ -229,6 +248,62 @@ public class DolibarrClient {
|
|||
.toBodilessEntity();
|
||||
}
|
||||
|
||||
// ── Contacts (llx_socpeople) ──────────────────────────────────────────
|
||||
|
||||
public List<DolibarrContactDto> getThirdpartyContacts(Integer socid) {
|
||||
try {
|
||||
List<DolibarrContactDto> contacts = restClient.get()
|
||||
.uri(u -> u.path("/contacts")
|
||||
.queryParam("sqlfilters", "(t.fk_soc:=:" + socid + ")")
|
||||
.queryParam("limit", 100)
|
||||
.build())
|
||||
.retrieve()
|
||||
.body(new ParameterizedTypeReference<List<DolibarrContactDto>>() {});
|
||||
return contacts != null ? contacts : Collections.emptyList();
|
||||
} catch (DolibarrApiException e) {
|
||||
if (e.getStatusCode().value() == 404) return Collections.emptyList();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public DolibarrContactDto createContact(DolibarrContactDto dto) {
|
||||
log.debug("Creating Dolibarr contact fk_soc={}", dto.fkSoc());
|
||||
Integer newId = restClient.post()
|
||||
.uri("/contacts")
|
||||
.body(dto)
|
||||
.retrieve()
|
||||
.body(Integer.class);
|
||||
return restClient.get()
|
||||
.uri("/contacts/{id}", newId)
|
||||
.retrieve()
|
||||
.body(DolibarrContactDto.class);
|
||||
}
|
||||
|
||||
public void updateContact(Integer id, DolibarrContactDto dto) {
|
||||
log.debug("Updating Dolibarr contact id={}", id);
|
||||
restClient.put()
|
||||
.uri("/contacts/{id}", id)
|
||||
.body(dto)
|
||||
.retrieve()
|
||||
.toBodilessEntity();
|
||||
}
|
||||
|
||||
public void deleteContact(Integer id) {
|
||||
log.debug("Deleting Dolibarr contact id={}", id);
|
||||
restClient.delete()
|
||||
.uri("/contacts/{id}", id)
|
||||
.retrieve()
|
||||
.toBodilessEntity();
|
||||
}
|
||||
|
||||
public void linkContactToInvoice(Integer invoiceId, Integer contactId, String type) {
|
||||
log.debug("Linking contact {} to invoice {} as {}", contactId, invoiceId, type);
|
||||
restClient.post()
|
||||
.uri("/invoices/{id}/contact/{contactId}/{type}", invoiceId, contactId, type)
|
||||
.retrieve()
|
||||
.toBodilessEntity();
|
||||
}
|
||||
|
||||
// ── Orders ────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
package com.teterialosjuanjos.tfg.sync_service.integration.dolibarr.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public record DolibarrContactDto(
|
||||
Integer id,
|
||||
@JsonProperty("fk_soc") Integer fkSoc,
|
||||
String firstname,
|
||||
String lastname,
|
||||
String address,
|
||||
String zip,
|
||||
String town,
|
||||
@JsonProperty("phone_pro") String phonePro,
|
||||
String email
|
||||
) {}
|
||||
|
|
@ -2,7 +2,7 @@ package com.teterialosjuanjos.tfg.sync_service.sync;
|
|||
|
||||
import com.teterialosjuanjos.tfg.sync_service.config.IntegrationProperties;
|
||||
import com.teterialosjuanjos.tfg.sync_service.integration.dolibarr.DolibarrClient;
|
||||
import com.teterialosjuanjos.tfg.sync_service.integration.dolibarr.dto.DolibarrAddressDto;
|
||||
import com.teterialosjuanjos.tfg.sync_service.integration.dolibarr.dto.DolibarrContactDto;
|
||||
import com.teterialosjuanjos.tfg.sync_service.integration.dolibarr.dto.DolibarrInvoiceDto;
|
||||
import com.teterialosjuanjos.tfg.sync_service.integration.dolibarr.dto.DolibarrOrderDto;
|
||||
import com.teterialosjuanjos.tfg.sync_service.integration.dolibarr.dto.DolibarrThirdpartyDto;
|
||||
|
|
@ -134,9 +134,11 @@ public class OrderSyncService {
|
|||
|
||||
DolibarrOrderDto createdOrder = dolibarrClient.createOrder(orderDto);
|
||||
|
||||
// 3. Create linked invoice with lines copied from the order and billing address
|
||||
Integer fkAddress = resolveInvoiceAddress(psOrder, customer.id());
|
||||
DolibarrInvoiceDto invoice = dolibarrClient.createInvoiceFromOrder(createdOrder, fkAddress);
|
||||
// 3. Create linked invoice with lines copied from the order
|
||||
DolibarrInvoiceDto invoice = dolibarrClient.createInvoiceFromOrder(createdOrder, null);
|
||||
|
||||
// 4a. Link billing contact to invoice (populates "Contactos/Direcciones" on the invoice)
|
||||
linkInvoiceBillingContact(psOrder, customer.id(), invoice.id());
|
||||
|
||||
// 4. Persist mapping to prevent re-import
|
||||
orderMappingRepository.save(OrderMapping.builder()
|
||||
|
|
@ -192,125 +194,106 @@ public class OrderSyncService {
|
|||
|
||||
DolibarrThirdpartyDto thirdparty = dolibarrClient.getOrCreateThirdparty(email, name, address, city, zip);
|
||||
|
||||
// Sync additional addresses if customer exists and is not a guest
|
||||
// Sync additional addresses as Dolibarr contacts (llx_socpeople → "Contactos/Direcciones" tab)
|
||||
if (!isGuest && psCustomer != null) {
|
||||
syncCustomerAddresses(Integer.parseInt(psOrder.idCustomer()), thirdparty.id());
|
||||
syncCustomerContacts(Integer.parseInt(psOrder.idCustomer()), thirdparty.id());
|
||||
}
|
||||
|
||||
return thirdparty;
|
||||
}
|
||||
|
||||
private void syncCustomerAddresses(Integer psCustomerId, Integer dolibarrThirdpartyId) {
|
||||
private void syncCustomerContacts(Integer psCustomerId, Integer dolibarrThirdpartyId) {
|
||||
try {
|
||||
// 1. Obtener direcciones de PrestaShop
|
||||
List<PrestashopAddressDto> psAddresses = prestashopClient.getCustomerAddresses(psCustomerId);
|
||||
|
||||
// 2. Obtener direcciones existentes en Dolibarr
|
||||
List<DolibarrAddressDto> dolibarrAddresses = dolibarrClient.getThirdpartyAddresses(dolibarrThirdpartyId);
|
||||
List<DolibarrContactDto> dolibarrContacts = dolibarrClient.getThirdpartyContacts(dolibarrThirdpartyId);
|
||||
|
||||
PrestashopCustomerDto psCustomer = prestashopClient.getCustomer(psCustomerId);
|
||||
String defaultAddressId = psCustomer != null ? psCustomer.idDefaultAddress() : null;
|
||||
|
||||
// 3. Procesar cada dirección de PrestaShop
|
||||
for (PrestashopAddressDto psAddr : psAddresses) {
|
||||
// Ignorar la dirección por defecto (ya está en thirdparty.address/city/zip)
|
||||
if (psAddr.id() != null && psAddr.id().toString().equals(defaultAddressId)) {
|
||||
continue;
|
||||
}
|
||||
// Default address is already on the thirdparty itself (address/zip/city fields)
|
||||
if (psAddr.id() != null && psAddr.id().toString().equals(defaultAddressId)) continue;
|
||||
|
||||
String formattedAddress = formatAddress(psAddr.address1(), psAddr.address2());
|
||||
String formattedCity = psAddr.city();
|
||||
String formattedAddr = formatAddress(psAddr.address1(), psAddr.address2());
|
||||
|
||||
// Buscar si existe una dirección con los mismos datos en Dolibarr
|
||||
DolibarrAddressDto existingAddr = dolibarrAddresses.stream()
|
||||
.filter(a -> a.address() != null && a.address().equals(formattedAddress)
|
||||
&& a.city() != null && a.city().equals(formattedCity))
|
||||
DolibarrContactDto existing = dolibarrContacts.stream()
|
||||
.filter(c -> c.address() != null && c.address().equals(formattedAddr)
|
||||
&& c.town() != null && c.town().equals(psAddr.city()))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
|
||||
if (existingAddr != null) {
|
||||
// ACTUALIZAR: La dirección existe pero podría tener cambios en zip, phone, etc.
|
||||
DolibarrAddressDto updatedAddr = new DolibarrAddressDto(
|
||||
existingAddr.id(),
|
||||
dolibarrThirdpartyId,
|
||||
formatPersonName(psAddr.firstname(), psAddr.lastname()),
|
||||
formattedAddress,
|
||||
psAddr.postcode(),
|
||||
formattedCity,
|
||||
null,
|
||||
psAddr.phone(),
|
||||
0
|
||||
);
|
||||
dolibarrClient.updateThirdpartyAddress(existingAddr.id(), updatedAddr);
|
||||
log.debug("Updated PS address {} in Dolibarr", psAddr.id());
|
||||
if (existing != null) {
|
||||
dolibarrClient.updateContact(existing.id(), new DolibarrContactDto(
|
||||
existing.id(), dolibarrThirdpartyId,
|
||||
psAddr.firstname(), psAddr.lastname(),
|
||||
formattedAddr, psAddr.postcode(), psAddr.city(),
|
||||
psAddr.phone(), null));
|
||||
log.debug("Updated PS address {} as Dolibarr contact", psAddr.id());
|
||||
} else {
|
||||
// CREAR: Dirección nueva en PrestaShop
|
||||
DolibarrAddressDto newAddr = new DolibarrAddressDto(
|
||||
null,
|
||||
dolibarrThirdpartyId,
|
||||
formatPersonName(psAddr.firstname(), psAddr.lastname()),
|
||||
formattedAddress,
|
||||
psAddr.postcode(),
|
||||
formattedCity,
|
||||
null,
|
||||
psAddr.phone(),
|
||||
0
|
||||
);
|
||||
dolibarrClient.createThirdpartyAddress(dolibarrThirdpartyId, newAddr);
|
||||
log.debug("Created PS address {} in Dolibarr", psAddr.id());
|
||||
dolibarrClient.createContact(new DolibarrContactDto(
|
||||
null, dolibarrThirdpartyId,
|
||||
psAddr.firstname(), psAddr.lastname(),
|
||||
formattedAddr, psAddr.postcode(), psAddr.city(),
|
||||
psAddr.phone(), null));
|
||||
log.debug("Created PS address {} as Dolibarr contact", psAddr.id());
|
||||
}
|
||||
}
|
||||
|
||||
// 4. ELIMINAR direcciones que ya no existen en PrestaShop
|
||||
Set<String> psAddressStrings = psAddresses.stream()
|
||||
// Remove contacts that no longer exist in PrestaShop
|
||||
Set<String> psKeys = psAddresses.stream()
|
||||
.filter(a -> !(a.id() != null && a.id().toString().equals(defaultAddressId)))
|
||||
.map(a -> formatAddress(a.address1(), a.address2()) + "|" + a.city())
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
for (DolibarrAddressDto dolibarrAddr : dolibarrAddresses) {
|
||||
String dolibarrAddressStr = dolibarrAddr.address() + "|" + dolibarrAddr.city();
|
||||
if (!psAddressStrings.contains(dolibarrAddressStr)) {
|
||||
dolibarrClient.deleteThirdpartyAddress(dolibarrAddr.id());
|
||||
log.debug("Deleted orphaned address {} from Dolibarr", dolibarrAddr.id());
|
||||
for (DolibarrContactDto c : dolibarrContacts) {
|
||||
if (!psKeys.contains(c.address() + "|" + c.town())) {
|
||||
dolibarrClient.deleteContact(c.id());
|
||||
log.debug("Deleted orphaned Dolibarr contact {}", c.id());
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to sync addresses for PS customer {}: {}", psCustomerId, e.getMessage());
|
||||
log.warn("Failed to sync contacts for PS customer {}: {}", psCustomerId, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private Integer resolveInvoiceAddress(PrestashopOrderDto psOrder, Integer dolibarrThirdpartyId) {
|
||||
private void linkInvoiceBillingContact(PrestashopOrderDto psOrder, Integer dolibarrThirdpartyId, Integer invoiceId) {
|
||||
String addrId = psOrder.idAddressInvoice();
|
||||
if (addrId == null || addrId.isBlank() || "0".equals(addrId)) return null;
|
||||
if (addrId == null || addrId.isBlank() || "0".equals(addrId)) return;
|
||||
|
||||
// If billing addr = customer default addr → already on thirdparty, no fk_address needed
|
||||
boolean isGuest = psOrder.idCustomer() == null
|
||||
|| psOrder.idCustomer().isBlank()
|
||||
|| "0".equals(psOrder.idCustomer());
|
||||
|
||||
// If billing addr = customer default addr → thirdparty main address, no contact to link
|
||||
if (!isGuest) {
|
||||
try {
|
||||
PrestashopCustomerDto c = prestashopClient.getCustomer(Integer.parseInt(psOrder.idCustomer()));
|
||||
if (c != null && addrId.equals(c.idDefaultAddress())) return null;
|
||||
if (c != null && addrId.equals(c.idDefaultAddress())) return;
|
||||
} catch (Exception e) {
|
||||
log.warn("Could not fetch PS customer for invoice address resolution: {}", e.getMessage());
|
||||
log.warn("Could not fetch PS customer for billing contact resolution: {}", e.getMessage());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Find matching Dolibarr socaddress by content
|
||||
try {
|
||||
PrestashopAddressDto billing = prestashopClient.getAddress(Integer.parseInt(addrId));
|
||||
if (billing == null) return null;
|
||||
if (billing == null) return;
|
||||
String formatted = formatAddress(billing.address1(), billing.address2());
|
||||
return dolibarrClient.getThirdpartyAddresses(dolibarrThirdpartyId).stream()
|
||||
.filter(a -> a.address() != null && a.address().equals(formatted)
|
||||
&& a.city() != null && a.city().equals(billing.city()))
|
||||
.map(DolibarrAddressDto::id)
|
||||
|
||||
dolibarrClient.getThirdpartyContacts(dolibarrThirdpartyId).stream()
|
||||
.filter(c -> c.address() != null && c.address().equals(formatted)
|
||||
&& c.town() != null && c.town().equals(billing.city()))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
.ifPresent(contact -> {
|
||||
try {
|
||||
dolibarrClient.linkContactToInvoice(invoiceId, contact.id(), "BILLING");
|
||||
log.debug("Linked contact {} to invoice {} as BILLING", contact.id(), invoiceId);
|
||||
} catch (Exception ex) {
|
||||
log.warn("Could not link billing contact {} to invoice {}: {}", contact.id(), invoiceId, ex.getMessage());
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
log.warn("Could not resolve billing address for PS order {}: {}", psOrder.id(), e.getMessage());
|
||||
return null;
|
||||
log.warn("Could not resolve billing contact for PS order {}: {}", psOrder.id(), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -320,11 +303,6 @@ public class OrderSyncService {
|
|||
return addr1 + " " + addr2;
|
||||
}
|
||||
|
||||
private String formatPersonName(String firstname, String lastname) {
|
||||
String full = (firstname != null ? firstname : "") + " " + (lastname != null ? lastname : "");
|
||||
return full.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps PrestaShop order rows to Dolibarr order lines.
|
||||
* Converts tax-included unit price to excl-tax using the configured {@code defaultTaxRate}.
|
||||
|
|
|
|||
Loading…
Reference in New Issue