Menu
Commandes + Cmd SAV & INFO Contacts Comptes pro Chiffrages Idées Messages
Utilisateur
Darsy
Messages
Chargement...
Nouvelle conversation
Sélectionnez un ou plusieurs participants :
Source : docs/40-relation/99-technique.md — ce document se lit aussi dans le dépôt, sans serveur.

Clients et fournisseurs — le côté technique


Les fichiers

RôleFichier
Clientspublic/erp/clients.php, public/erp/client.php
API clientssrc/Http/Controllers/CustomerController.php, ClientController.php
Données clientssrc/Woo/WooCustomerRepository.php
Fournisseurspublic/erp/fournisseurs.php, public/erp/fournisseur.php
API fournisseurssrc/Http/Controllers/SupplierController.php
Données fournisseurssrc/Repository/SupplierRepository.php
SAVpublic/erp/sav.php + public/erp/assets/js/pages/sav-2.js
API SAVsrc/Http/Controllers/SavController.php
API publique SAVsrc/Http/Controllers/SupportController.php
L'action SAVsrc/Orders/OrderActionService.php
Données SAVsrc/Repository/CustomerContactRepository.php
Pièces jointessrc/Support/PiecesJointesSav.php
Avispublic/erp/avis.php, src/Http/Controllers/AvisController.php
Le délai de livraisonsrc/Avis/DelaiLivraison.php
Suivi des colissrc/Tracking/TrackingService.php
Le verrou de tâchesrc/Support/RunLock.php
Limitationsrc/Auth/LoginThrottle.php

Les routes

RouteQui
GET /v1/customers, /v1/clients/profileModule orders
GET /v1/suppliers, /{id}, /{id}/dashboardModule suppliers, lecture
POST/PUT /v1/suppliersModule suppliers, écriture
DELETE /v1/suppliers/{id}Administrateur
GET /v1/sav, /v1/sav/statsModule support, lecture
PUT /v1/sav/{id}/closeModule support, écriture
POST /v1/orders/{id}/customer-contactsModule orders, écriture
GET /v1/avis, POST /v1/avis/*Module orders
POST /v1/support/verify, /submitPublic, limité en fréquence

La table du SAV

customer_contacts : woo_order_id, contact_type, reply_to_id, customer_email, message, status_applied, email_status, created_by_user_id, created_by_email, created_at.

Un piège dans le schéma

status_applied porte deux sens. Soit le statut WooCommerce appliqué en même temps que le message, soit la valeur 'closed' qui dit « traité ».

Ça tient parce que les statuts WooCommerce sont tous préfixés wc- : closed n'entre jamais en collision. Mais c'est une colonne pour deux idées, et il faut le savoir avant d'y toucher.

IFNULL est obligatoire dans les conditions : une demande jamais touchée a NULL, et un simple <> 'closed' vaudrait NULL — la ligne disparaîtrait.

Le critère « entrant »

cc.created_by_user_id IS NULL. Toute action faite depuis l'ERP marque l'opérateur ; le formulaire public et l'import historique ne marquent personne.

Cela remplace l'ancienne règle contact_type <> 'sav_reply' : une réponse est toujours écrite par un opérateur.


Le formulaire public est un oracle

Les numéros de commande se suivent — environ 85 000.

La seule limite était un quota par IP, posé sur un en-tête que l'appelant choisit. Le coût d'un essai était donc d'une requête HTTP. Avec une liste d'adresses — celles d'une fuite, d'une base de prospection — on associait chaque adresse à ses commandes. Et la réponse donnait en prime le statut réel, que la page n'affiche même pas. Le couple connu, /submit permettait d'ouvrir un SAV au nom du client.

Trois changements :

  1. Le statut ne sort plus.
  2. Les échecs sont comptés par adresse et par numéro de commande, pas seulement par IP : l'un contre le balayage des commandes d'un client connu, l'autre contre le balayage des adresses sur une commande donnée.
  3. Le piège à robots est vérifié côté serveur. Un robot pris reçoit la même réponse qu'un couple inconnu : rien ne lui apprend qu'il a été vu.

C'est le même raisonnement que Certi'Clé.


Les pièces jointes, et la faute du 20/09/2026

Le formulaire WordPress dépose les fichiers sur le domaine de la boutique et envoie à Darsy le message du client suivi d'un marqueur :

[Fichiers joints: facture.pdf|sav_6ab0e92b6c68d4.05973337.pdf, …]

La fiche commande découpe là-dessus et fabrique un lien et une vignette.

Ce qui s'est passé

Un audit a conclu que « le formulaire public actuel n'envoie aucun fichier » — en lisant public/support/ de ce dépôt, qui n'est pas le formulaire des clients. Sur cette base, le marqueur a été neutralisé à l'entrée. La fiche commande, qui découpe sur l'orthographe exacte, s'est mise à afficher le marqueur en texte brut.

Le lendemain, un client a joint trois pièces. L'opérateur a vu du texte.

La leçon : ne pas juger le système vivant d'après ce qu'il y a dans le dépôt.

Ce qui protège vraiment

Le danger n'a jamais été le marqueur : c'est le nom stocké qu'il porte, parce qu'il est concaténé à une URL. Un client qui écrit facture.jpg|../../wp-login.php fait pointer une vignette d'apparence légitime ailleurs sur le domaine.

La règle est donc sur le nom : un nom de fichier, et rien d'autre — pas de barre, pas de remontée, pas d'espace.

Un nom refusé ne fait pas disparaître la pièce. Elle reste annoncée, en texte inerte, sans lien. L'effacer cacherait à l'opérateur qu'une pièce a été promise.


Le verrou de tâche

La vérification de suivi s'exécute dans la requête et peut durer une minute.

Le verrou s'appuie sur flock(), donc le système le libère seul si le processus meurt : pas de verrou fantôme après un plantage, contrairement à un simple fichier témoin.

Et il échoue ouvert. Si le stockage est indisponible, la tâche s'exécute quand même. Ce verrou évite un gaspillage, il ne protège pas une donnée : refuser de travailler parce qu'un répertoire temporaire est cassé coûterait plus cher que le doublon qu'il empêche.


Le piège des recherches

Un marqueur nommé utilisé deux fois dans une requête : PDO est en préparation native (ATTR_EMULATE_PREPARES = false), qui refuse avec HY093. Toute recherche sur la page SAV renvoyait 500.

Trois marqueurs distincts liés à la même valeur, et c'est réglé.

La saisie n'est pas un motif : un _ dans une adresse ou un % dans un message doit se chercher lui-même. Database\LikePattern les neutralise, et la clause ESCAPE dit au moteur comment les relire.


Les tests

tests/Unit/SurfacesPubliquesTest.php (la règle sur les noms de fichier, cas par cas, y compris la remontée de répertoire), tests/Unit/DelaiLivraisonTest.php (les fériés, Pâques, les bornes), tests/Unit/RunLockTest.php, tests/Unit/LikePatternTest.php, tests/Unit/LoginThrottleTest.php, tests/Unit/ConventionDeNommageTest.php, tests/Unit/VariationsTarifairesDuStockTest.php.