addresses
This commit is contained in:
parent
877cb594b2
commit
c3c5b22b12
|
|
@ -1,6 +1,7 @@
|
||||||
package com.teterialosjuanjos.tfg.sync_service.integration.dolibarr;
|
package com.teterialosjuanjos.tfg.sync_service.integration.dolibarr;
|
||||||
|
|
||||||
import com.teterialosjuanjos.tfg.sync_service.integration.dolibarr.dto.*;
|
import com.teterialosjuanjos.tfg.sync_service.integration.dolibarr.dto.*;
|
||||||
|
import java.util.Collections;
|
||||||
import com.teterialosjuanjos.tfg.sync_service.integration.dolibarr.exception.DolibarrApiException;
|
import com.teterialosjuanjos.tfg.sync_service.integration.dolibarr.exception.DolibarrApiException;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
|
|
@ -167,10 +168,28 @@ public class DolibarrClient {
|
||||||
.retrieve()
|
.retrieve()
|
||||||
.body(Integer.class);
|
.body(Integer.class);
|
||||||
|
|
||||||
return restClient.get()
|
DolibarrThirdpartyDto created = restClient.get()
|
||||||
.uri("/thirdparties/{id}", newId)
|
.uri("/thirdparties/{id}", newId)
|
||||||
.retrieve()
|
.retrieve()
|
||||||
.body(DolibarrThirdpartyDto.class);
|
.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) {
|
public DolibarrAddressDto createThirdpartyAddress(Integer socid, DolibarrAddressDto dto) {
|
||||||
|
|
@ -229,6 +248,62 @@ public class DolibarrClient {
|
||||||
.toBodilessEntity();
|
.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 ────────────────────────────────────────────────────────────
|
// ── 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.config.IntegrationProperties;
|
||||||
import com.teterialosjuanjos.tfg.sync_service.integration.dolibarr.DolibarrClient;
|
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.DolibarrInvoiceDto;
|
||||||
import com.teterialosjuanjos.tfg.sync_service.integration.dolibarr.dto.DolibarrOrderDto;
|
import com.teterialosjuanjos.tfg.sync_service.integration.dolibarr.dto.DolibarrOrderDto;
|
||||||
import com.teterialosjuanjos.tfg.sync_service.integration.dolibarr.dto.DolibarrThirdpartyDto;
|
import com.teterialosjuanjos.tfg.sync_service.integration.dolibarr.dto.DolibarrThirdpartyDto;
|
||||||
|
|
@ -134,9 +134,11 @@ public class OrderSyncService {
|
||||||
|
|
||||||
DolibarrOrderDto createdOrder = dolibarrClient.createOrder(orderDto);
|
DolibarrOrderDto createdOrder = dolibarrClient.createOrder(orderDto);
|
||||||
|
|
||||||
// 3. Create linked invoice with lines copied from the order and billing address
|
// 3. Create linked invoice with lines copied from the order
|
||||||
Integer fkAddress = resolveInvoiceAddress(psOrder, customer.id());
|
DolibarrInvoiceDto invoice = dolibarrClient.createInvoiceFromOrder(createdOrder, null);
|
||||||
DolibarrInvoiceDto invoice = dolibarrClient.createInvoiceFromOrder(createdOrder, fkAddress);
|
|
||||||
|
// 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
|
// 4. Persist mapping to prevent re-import
|
||||||
orderMappingRepository.save(OrderMapping.builder()
|
orderMappingRepository.save(OrderMapping.builder()
|
||||||
|
|
@ -192,125 +194,106 @@ public class OrderSyncService {
|
||||||
|
|
||||||
DolibarrThirdpartyDto thirdparty = dolibarrClient.getOrCreateThirdparty(email, name, address, city, zip);
|
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) {
|
if (!isGuest && psCustomer != null) {
|
||||||
syncCustomerAddresses(Integer.parseInt(psOrder.idCustomer()), thirdparty.id());
|
syncCustomerContacts(Integer.parseInt(psOrder.idCustomer()), thirdparty.id());
|
||||||
}
|
}
|
||||||
|
|
||||||
return thirdparty;
|
return thirdparty;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void syncCustomerAddresses(Integer psCustomerId, Integer dolibarrThirdpartyId) {
|
private void syncCustomerContacts(Integer psCustomerId, Integer dolibarrThirdpartyId) {
|
||||||
try {
|
try {
|
||||||
// 1. Obtener direcciones de PrestaShop
|
|
||||||
List<PrestashopAddressDto> psAddresses = prestashopClient.getCustomerAddresses(psCustomerId);
|
List<PrestashopAddressDto> psAddresses = prestashopClient.getCustomerAddresses(psCustomerId);
|
||||||
|
List<DolibarrContactDto> dolibarrContacts = dolibarrClient.getThirdpartyContacts(dolibarrThirdpartyId);
|
||||||
// 2. Obtener direcciones existentes en Dolibarr
|
|
||||||
List<DolibarrAddressDto> dolibarrAddresses = dolibarrClient.getThirdpartyAddresses(dolibarrThirdpartyId);
|
|
||||||
|
|
||||||
PrestashopCustomerDto psCustomer = prestashopClient.getCustomer(psCustomerId);
|
PrestashopCustomerDto psCustomer = prestashopClient.getCustomer(psCustomerId);
|
||||||
String defaultAddressId = psCustomer != null ? psCustomer.idDefaultAddress() : null;
|
String defaultAddressId = psCustomer != null ? psCustomer.idDefaultAddress() : null;
|
||||||
|
|
||||||
// 3. Procesar cada dirección de PrestaShop
|
|
||||||
for (PrestashopAddressDto psAddr : psAddresses) {
|
for (PrestashopAddressDto psAddr : psAddresses) {
|
||||||
// Ignorar la dirección por defecto (ya está en thirdparty.address/city/zip)
|
// Default address is already on the thirdparty itself (address/zip/city fields)
|
||||||
if (psAddr.id() != null && psAddr.id().toString().equals(defaultAddressId)) {
|
if (psAddr.id() != null && psAddr.id().toString().equals(defaultAddressId)) continue;
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
String formattedAddress = formatAddress(psAddr.address1(), psAddr.address2());
|
String formattedAddr = formatAddress(psAddr.address1(), psAddr.address2());
|
||||||
String formattedCity = psAddr.city();
|
|
||||||
|
|
||||||
// Buscar si existe una dirección con los mismos datos en Dolibarr
|
DolibarrContactDto existing = dolibarrContacts.stream()
|
||||||
DolibarrAddressDto existingAddr = dolibarrAddresses.stream()
|
.filter(c -> c.address() != null && c.address().equals(formattedAddr)
|
||||||
.filter(a -> a.address() != null && a.address().equals(formattedAddress)
|
&& c.town() != null && c.town().equals(psAddr.city()))
|
||||||
&& a.city() != null && a.city().equals(formattedCity))
|
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
|
|
||||||
if (existingAddr != null) {
|
if (existing != null) {
|
||||||
// ACTUALIZAR: La dirección existe pero podría tener cambios en zip, phone, etc.
|
dolibarrClient.updateContact(existing.id(), new DolibarrContactDto(
|
||||||
DolibarrAddressDto updatedAddr = new DolibarrAddressDto(
|
existing.id(), dolibarrThirdpartyId,
|
||||||
existingAddr.id(),
|
psAddr.firstname(), psAddr.lastname(),
|
||||||
dolibarrThirdpartyId,
|
formattedAddr, psAddr.postcode(), psAddr.city(),
|
||||||
formatPersonName(psAddr.firstname(), psAddr.lastname()),
|
psAddr.phone(), null));
|
||||||
formattedAddress,
|
log.debug("Updated PS address {} as Dolibarr contact", psAddr.id());
|
||||||
psAddr.postcode(),
|
|
||||||
formattedCity,
|
|
||||||
null,
|
|
||||||
psAddr.phone(),
|
|
||||||
0
|
|
||||||
);
|
|
||||||
dolibarrClient.updateThirdpartyAddress(existingAddr.id(), updatedAddr);
|
|
||||||
log.debug("Updated PS address {} in Dolibarr", psAddr.id());
|
|
||||||
} else {
|
} else {
|
||||||
// CREAR: Dirección nueva en PrestaShop
|
dolibarrClient.createContact(new DolibarrContactDto(
|
||||||
DolibarrAddressDto newAddr = new DolibarrAddressDto(
|
null, dolibarrThirdpartyId,
|
||||||
null,
|
psAddr.firstname(), psAddr.lastname(),
|
||||||
dolibarrThirdpartyId,
|
formattedAddr, psAddr.postcode(), psAddr.city(),
|
||||||
formatPersonName(psAddr.firstname(), psAddr.lastname()),
|
psAddr.phone(), null));
|
||||||
formattedAddress,
|
log.debug("Created PS address {} as Dolibarr contact", psAddr.id());
|
||||||
psAddr.postcode(),
|
|
||||||
formattedCity,
|
|
||||||
null,
|
|
||||||
psAddr.phone(),
|
|
||||||
0
|
|
||||||
);
|
|
||||||
dolibarrClient.createThirdpartyAddress(dolibarrThirdpartyId, newAddr);
|
|
||||||
log.debug("Created PS address {} in Dolibarr", psAddr.id());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. ELIMINAR direcciones que ya no existen en PrestaShop
|
// Remove contacts that no longer exist in PrestaShop
|
||||||
Set<String> psAddressStrings = psAddresses.stream()
|
Set<String> psKeys = psAddresses.stream()
|
||||||
.filter(a -> !(a.id() != null && a.id().toString().equals(defaultAddressId)))
|
.filter(a -> !(a.id() != null && a.id().toString().equals(defaultAddressId)))
|
||||||
.map(a -> formatAddress(a.address1(), a.address2()) + "|" + a.city())
|
.map(a -> formatAddress(a.address1(), a.address2()) + "|" + a.city())
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
for (DolibarrAddressDto dolibarrAddr : dolibarrAddresses) {
|
for (DolibarrContactDto c : dolibarrContacts) {
|
||||||
String dolibarrAddressStr = dolibarrAddr.address() + "|" + dolibarrAddr.city();
|
if (!psKeys.contains(c.address() + "|" + c.town())) {
|
||||||
if (!psAddressStrings.contains(dolibarrAddressStr)) {
|
dolibarrClient.deleteContact(c.id());
|
||||||
dolibarrClient.deleteThirdpartyAddress(dolibarrAddr.id());
|
log.debug("Deleted orphaned Dolibarr contact {}", c.id());
|
||||||
log.debug("Deleted orphaned address {} from Dolibarr", dolibarrAddr.id());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (Exception e) {
|
} 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();
|
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
|
boolean isGuest = psOrder.idCustomer() == null
|
||||||
|| psOrder.idCustomer().isBlank()
|
|| psOrder.idCustomer().isBlank()
|
||||||
|| "0".equals(psOrder.idCustomer());
|
|| "0".equals(psOrder.idCustomer());
|
||||||
|
|
||||||
|
// If billing addr = customer default addr → thirdparty main address, no contact to link
|
||||||
if (!isGuest) {
|
if (!isGuest) {
|
||||||
try {
|
try {
|
||||||
PrestashopCustomerDto c = prestashopClient.getCustomer(Integer.parseInt(psOrder.idCustomer()));
|
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) {
|
} 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 {
|
try {
|
||||||
PrestashopAddressDto billing = prestashopClient.getAddress(Integer.parseInt(addrId));
|
PrestashopAddressDto billing = prestashopClient.getAddress(Integer.parseInt(addrId));
|
||||||
if (billing == null) return null;
|
if (billing == null) return;
|
||||||
String formatted = formatAddress(billing.address1(), billing.address2());
|
String formatted = formatAddress(billing.address1(), billing.address2());
|
||||||
return dolibarrClient.getThirdpartyAddresses(dolibarrThirdpartyId).stream()
|
|
||||||
.filter(a -> a.address() != null && a.address().equals(formatted)
|
dolibarrClient.getThirdpartyContacts(dolibarrThirdpartyId).stream()
|
||||||
&& a.city() != null && a.city().equals(billing.city()))
|
.filter(c -> c.address() != null && c.address().equals(formatted)
|
||||||
.map(DolibarrAddressDto::id)
|
&& c.town() != null && c.town().equals(billing.city()))
|
||||||
.findFirst()
|
.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) {
|
} catch (Exception e) {
|
||||||
log.warn("Could not resolve billing address for PS order {}: {}", psOrder.id(), e.getMessage());
|
log.warn("Could not resolve billing contact for PS order {}: {}", psOrder.id(), e.getMessage());
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -320,11 +303,6 @@ public class OrderSyncService {
|
||||||
return addr1 + " " + addr2;
|
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.
|
* Maps PrestaShop order rows to Dolibarr order lines.
|
||||||
* Converts tax-included unit price to excl-tax using the configured {@code defaultTaxRate}.
|
* Converts tax-included unit price to excl-tax using the configured {@code defaultTaxRate}.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue