Price with VAT was showing the same value as price without VAT because
id_tax_rules_group was hardcoded to 1 (no-tax group in this PS instance).
Now reads tva_tx from Dolibarr product, resolves the PS tax rule group
via a configurable map (integration.prestashop.tax-rule-groups), and
sets the correct group in the product XML. Defaults to
default-tax-rules-group-id when no mapping is found.
ES groups configured: 21% → 53, 10% → 54, 4% → 55.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
ProductSyncService now calls pushInitialStock() after creating a new
product in PrestaShop, so stock is correct without waiting for the next
StockSyncService run. Failures are caught and logged as warnings — the
product mapping is saved as SYNCED regardless, and StockSyncService will
correct any discrepancy on its next scheduled run.
Also applies the PS 8.2.5 false-instead-of-empty-array workaround to
getStockAvailableForProduct(), consistent with getProductByReference().
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
PS 8 introduced a state column (0=draft, 1=published). Products created
via Webservice without state default to 0, making them invisible in the
admin product list (which filters state=1 only). The storefront does not
check state, so products appeared there but not in admin.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
PS Webservice creates stock_available records with id_shop=0 when
products are created via API. The PS frontend reads id_shop=1 records
for stock display in a single-shop setup, causing OUT OF STOCK to appear
even when quantity > 0. Adding id_shop=1 to the XML ensures stock updates
target the correct shop-specific record.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Including <associations><categories> in the product XML triggers
Product::addToCategories() in PS, which may also invoke the shop
association logic needed to populate ps_product_shop.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Spring 6 RestClient wraps HttpMessageNotReadableException (thrown when
deserializing {"products":false}) into RestClientException with message
"Error while extracting response for type [...]". The previous fix only
caught HttpMessageConversionException directly, missing the wrapper.
PrestashopApiException is not a RestClientException, so 4xx/5xx error
handling is unaffected.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
PS 8.2.5 returns {"products": false} instead of {"products": []}
when a reference filter finds no matching products. Jackson cannot
deserialize false as a List, throwing HttpMessageConversionException.
Catch it and return Optional.empty() so pushProduct takes the CREATE
path correctly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Deletes the PS product via Webservice and removes the local mapping.
Use this to clean up incomplete PS products (missing ps_product_shop
entry) that block re-creation on next sync.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Products created via PS Webservice were missing the ps_product_shop
table entry, causing them to be invisible in PS admin (which queries
products with INNER JOIN ps_product_shop). Adding id_shop_default=1
forces PS to create the shop association on product create/update.
Also add available_for_order, show_price, and visibility=both to
ensure products are fully configured for catalog display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
PS soft-deletes products (sets active=0) when deleted from admin,
keeping them in the DB. The previous filter returned those inactive
products, causing pushProduct to take the UPDATE path and silently
re-update a soft-deleted product that remains invisible.
Adding filter[active]=[1] ensures only visible products are matched.
Products deleted in PS are now correctly re-created on next sync.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ProductSyncService now queries PS by reference before deciding
create vs update, preventing stale local mappings from causing
silent no-ops when PS products are deleted externally.
Also fix active field default: tosell=null now maps to active=1
instead of active=0, so products without an explicit sell flag
are created as visible in PrestaShop.
Add server.forward-headers-strategy=framework so Spring reads
X-Forwarded-Proto from Railway's reverse proxy; fixes Swagger UI
generating http:// URLs instead of https://.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Dolibarr GET /orders/{id} returns date fields as ISO strings, not
Unix timestamps, causing Jackson Long deserialization to fail.
String handles both: sends epoch string in POST body (Dolibarr accepts
it); ignores the date_commande string in GET via @JsonIgnoreProperties.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Dolibarr POST /orders requires field named "date" (not "date_commande").
Add @JsonAlias("date_commande") so GET responses are still deserialized
correctly when the response uses the alternative field name.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Both APIs return 404 (not 200+empty) when a filter finds no results.
Catch DolibarrApiException(404) in getOrCreateThirdparty and
getProductByRef; catch PrestashopApiException(404) in
getProductByReference — treat as empty/not-found instead of error.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- PrestashopClient.toStockAvailableXml: add out_of_stock=2 (use shop
default) — PS 8.2.5 returns 400 code 41 without this field
- PrestashopClient.getOrdersSince: remove filter[date_add] — PS 8.2.5
does not expose date_add as a filterable field for orders (400 code 32)
Re-import prevention handled by OrderMapping.existsByPrestashopOrderId
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- DolibarrClient.getProductByRef: use sqlfilters=(t.ref:=:'x') instead
of unsupported ref query param (caused StockSync to push wrong stock)
- PrestashopClient.getProductByReference: wrap value in [x] for PS
exact-match filter syntax (root cause of createProduct returning null)
- OrderSyncService.resolveCustomer: skip getCustomer when id_customer
is 0 or null (guest orders returned 404 and failed import)
- StockSyncService: parse stock quantity as Double to handle "5.000"
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
If PS accepts the POST but the subsequent GET by reference finds nothing,
the null return from createProduct now throws PrestashopApiException, which
is caught by the per-item handler and recorded in SyncLog without aborting
the rest of the sync loop.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
PS WebService PUT /stock_availables requires depends_on_stock even when
updating only the quantity. Hard-coded to 0 (does not depend on warehouse).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
PS WebService GET responses return name/description as plain strings, not
language-array objects. Using List<LangValue> caused Jackson to throw
HttpMessageConversionException on every getProductByReference call.
Also adds GET /api/sync/diagnostics/dolibarr to verify Dolibarr connectivity
and product count from the Railway environment.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Incremental sync via t.tms filter breaks when products were created before
the service started running: their tms is older than lastSyncStart so they
are never returned. Since the sync is idempotent (ProductMapping handles
create-vs-update), always fetching all products is safe and simpler.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
PS WebService POST response uses a different JSON structure for multilingual
fields than GET responses, causing Jackson deserialization failures. Instead
of parsing the POST body, discard it with toBodilessEntity() and fetch the
created product via getProductByReference() which uses the stable GET format.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
PS WebService POST response wraps multilingual fields as {"language":[...]}
while GET returns a flat array. Jackson fails parsing List<LangValue> from
the POST response. Fix: parse the full response as JsonNode and extract
only the id; reconstruct the DTO from the input fields.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ByteArrayHttpMessageConverter reliably handles application/xml content
type via its */* support, avoiding potential StringHttpMessageConverter
selection issues. Also add HttpMessageConversionException to per-item
catches and widen outer infrastructure catch to RuntimeException, so
any response-parsing failure is recorded in SyncLog.errorDetails
instead of propagating as HTTP 500.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Per-item catch blocks only handled DolibarrApiException|PrestashopApiException,
letting network-level errors (RestClientException wrapping SSL, timeout, etc.)
propagate uncaught → HTTP 500. Also add outer catch for infrastructure failures
(e.g. getProducts() itself failing) so SyncLog always records the error in
errorDetails and the service returns a valid SyncResult instead of throwing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
PS WebService requires XML request body even when output_format=JSON.
Build XML manually in PrestashopClient for createProduct, updateProduct
and updateStockAvailable. Add link_rewrite slug generation and XML
character escaping. Response parsing (JSON) unchanged.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
RestClient on Railway rejected the untrusted certificate on
prestashop.loading.net, causing SSLHandshakeException on every
HTTP call. Add SslConfig with a trust-all SimpleClientHttpRequestFactory
and inject it into both DolibarrClientConfig and PrestashopClientConfig.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Dolibarr-PrestaShop integration service (Fases 0-5):
- HTTP clients for Dolibarr REST API and PrestaShop Webservice
- JPA entities + Flyway migrations (product_mapping, order_mapping, sync_log)
- Three sync flows: product push, stock push, order pull
- Configurable @Scheduled jobs (disabled in dev, enabled in prod)
- REST API with Basic Auth and Swagger UI
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>