feat(logistique) : pesée pont bascule stub + allocateur DSD + endpoint (ERP-184)
- WeighbridgeReaderInterface (contrat) + RandomWeighbridgeReader (stub,
poids aléatoire ∈ [10000,50000] kg, RG-5.06) + WeighbridgeUnavailableException
- DsdAllocator : compteur DSD par site (weighbridge_dsd_counter) incrémenté
sous verrou ligne SELECT ... FOR UPDATE (RG-5.04, § 2.7)
- endpoint POST /api/weighbridge_readings : ressource virtuelle
WeighbridgeReadingResource + WeighbridgeReadingProcessor (pas de controller)
- AUTO -> {weight, dsd, mode} ; MANUAL -> {weight, dsd, manualNumber, mode}
- WeighbridgeUnavailableException -> HTTP 503 explicite (RG-5.06)
- site courant via CurrentSiteProviderInterface (contrat Sites)
- is_granted('logistique.weighing_tickets.manage')
- dsd renvoyé prévisionnel : attribution autoritaire refaite à la création
du ticket (ERP-185)
- tests : WeighbridgeReaderStubTest, DsdAllocatorTest, processor (503/400),
WeighbridgeReadingApiTest (RBAC + AUTO/MANUAL + 422)
This commit is contained in:
+81
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Module\Logistique\Infrastructure\ApiPlatform\State\Processor;
|
||||
|
||||
use ApiPlatform\Metadata\Operation;
|
||||
use ApiPlatform\State\ProcessorInterface;
|
||||
use App\Module\Logistique\Application\Service\DsdAllocatorInterface;
|
||||
use App\Module\Logistique\Domain\Contract\WeighbridgeReaderInterface;
|
||||
use App\Module\Logistique\Domain\Exception\WeighbridgeUnavailableException;
|
||||
use App\Module\Logistique\Infrastructure\ApiPlatform\Resource\WeighbridgeReadingResource;
|
||||
use App\Module\Sites\Application\Service\CurrentSiteProviderInterface;
|
||||
use LogicException;
|
||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException;
|
||||
|
||||
/**
|
||||
* Processor de l'action `POST /api/weighbridge_readings` (§ 4.2).
|
||||
*
|
||||
* Resout le site courant (CurrentSiteProviderInterface — contrat Sites, seule
|
||||
* logique cross-module autorisee, regle ABSOLUE n°1) puis :
|
||||
* - AUTO : lit le pont (WeighbridgeReaderInterface) → poids + DSD. Si la
|
||||
* bascule est indisponible (WeighbridgeUnavailableException) → HTTP 503
|
||||
* « Pont bascule indisponible — passez en pesee manuelle » (RG-5.06).
|
||||
* - MANUAL : conserve le poids saisi et alloue le DSD (dernier + 1, RG-5.04).
|
||||
*
|
||||
* @implements ProcessorInterface<WeighbridgeReadingResource, WeighbridgeReadingResource>
|
||||
*/
|
||||
final class WeighbridgeReadingProcessor implements ProcessorInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly CurrentSiteProviderInterface $currentSiteProvider,
|
||||
private readonly WeighbridgeReaderInterface $weighbridgeReader,
|
||||
private readonly DsdAllocatorInterface $dsdAllocator,
|
||||
) {}
|
||||
|
||||
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): WeighbridgeReadingResource
|
||||
{
|
||||
if (!$data instanceof WeighbridgeReadingResource) {
|
||||
throw new LogicException(sprintf(
|
||||
'WeighbridgeReadingProcessor attend une instance de %s, %s recu.',
|
||||
WeighbridgeReadingResource::class,
|
||||
get_debug_type($data),
|
||||
));
|
||||
}
|
||||
|
||||
// Site courant resolu serveur (jamais envoye par le client). Absent =
|
||||
// aucun site selectionne dans le sélecteur → on ne peut pas peser.
|
||||
$site = $this->currentSiteProvider->get();
|
||||
if (null === $site) {
|
||||
throw new BadRequestHttpException('Aucun site courant sélectionné — sélectionnez un site avant de peser.');
|
||||
}
|
||||
|
||||
if ('AUTO' === $data->mode) {
|
||||
try {
|
||||
$reading = $this->weighbridgeReader->read($site);
|
||||
} catch (WeighbridgeUnavailableException $e) {
|
||||
// RG-5.06 : le pont ne repond pas → 503 explicite, le front bascule
|
||||
// en pesee manuelle. (Le stub M5 ne leve jamais — chemin teste.)
|
||||
throw new ServiceUnavailableHttpException(
|
||||
null,
|
||||
'Pont bascule indisponible — passez en pesée manuelle.',
|
||||
$e,
|
||||
);
|
||||
}
|
||||
|
||||
$data->weight = $reading->weight;
|
||||
$data->dsd = $reading->dsd;
|
||||
$data->manualNumber = null; // pas de numero papier en mode bascule
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
// MANUAL : le poids est saisi (validateManualWeight garantit sa presence),
|
||||
// seul le DSD est attribue serveur (dernier DSD du site + 1, RG-5.04).
|
||||
$data->dsd = $this->dsdAllocator->next($site);
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user