feat(api): suivi public du statut commande GET /api/orders/{number} (P4) (#77)
This commit is contained in:
parent
918420c497
commit
cb990404e3
5 changed files with 99 additions and 7 deletions
|
|
@ -113,12 +113,15 @@ La borne est publique (aucune session) ; cf. `mlt.md` CREATE_ORDER, declencheur
|
|||
|
||||
| Methode | Chemin | Permission | Op MCT | Statut |
|
||||
|---|---|---|---|---|
|
||||
| GET | `/api/categories` | (lecture publique) | READ_CATALOGUE | prevu |
|
||||
| GET | `/api/products` | (lecture publique) | READ_CATALOGUE | prevu |
|
||||
| GET | `/api/products/{id}` | (lecture publique) | READ_CATALOGUE | prevu |
|
||||
| GET | `/api/menus` | (lecture publique) | READ_CATALOGUE | prevu |
|
||||
| GET | `/api/menus/{id}` | (lecture publique) | READ_CATALOGUE | prevu |
|
||||
| POST | `/api/orders` | (kiosk public) | CREATE_ORDER (mlt 3.3) | prevu (idempotency_key, RG-T19) |
|
||||
| GET | `/api/categories` | (lecture publique) | READ_CATALOGUE | livre |
|
||||
| GET | `/api/products` | (lecture publique) | READ_CATALOGUE | livre |
|
||||
| GET | `/api/products/{id}` | (lecture publique) | READ_CATALOGUE | livre |
|
||||
| GET | `/api/menus` | (lecture publique) | READ_CATALOGUE | livre |
|
||||
| GET | `/api/menus/{id}` | (lecture publique) | READ_CATALOGUE | livre (slots de composition) |
|
||||
| GET | `/api/allergens` | (lecture publique) | READ_CATALOGUE | livre (14 allergenes INCO) |
|
||||
| POST | `/api/orders` | (kiosk public) | CREATE_ORDER (mlt 3.3) | livre (idempotency_key, RG-T19) |
|
||||
| POST | `/api/orders/{number}/pay` | (kiosk public) | (encaissement) | livre (paid + decrement stock RG-T20) |
|
||||
| GET | `/api/orders/{number}` | (lecture publique) | (suivi statut) | livre (champs non sensibles : numero, statut, total) |
|
||||
|
||||
### 5.3 API / pages back-office (prevu P3-P4, session + permission)
|
||||
|
||||
|
|
@ -132,7 +135,7 @@ Commandes (cote equipier) :
|
|||
| Methode | Chemin | Permission | Op MCT | Note |
|
||||
|---|---|---|---|---|
|
||||
| GET | `/api/orders` | `order.read` | READ_ORDERS | filtre par `role_visible_source` (RG-T12) |
|
||||
| GET | `/api/orders/{number}` | `order.read` | READ_ORDERS | |
|
||||
| GET | `/api/orders/{number}` | `order.read` | READ_ORDERS | vue back-office detaillee (differe) ; le suivi public minimal est livre en 5.2 |
|
||||
| POST | `/api/orders` (comptoir/drive) | `order.create` | CREATE_COUNTER_ORDER (mlt 4.1) | source auto-taggee |
|
||||
| POST | `/api/orders/{id}/deliver` | `order.deliver` | DELIVER_ORDER (mlt 6.1) | |
|
||||
| POST | `/api/orders/{id}/cancel` | `order.cancel` | CANCEL_ORDER (mlt 7.1) | PIN + audit_log (RG-T13/14) |
|
||||
|
|
|
|||
|
|
@ -57,6 +57,25 @@ class OrderController extends Controller
|
|||
return $this->json(['data' => $this->present($order)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lecture publique du statut d'une commande par son numero (suivi borne apres
|
||||
* encaissement). Anonyme, lecture seule ; 404 si le numero est inconnu.
|
||||
*
|
||||
* @param array<string, string> $params
|
||||
*/
|
||||
public function show(array $params = []): Response
|
||||
{
|
||||
$order = $this->orders()->findByNumber((string) ($params['number'] ?? ''));
|
||||
if ($order === null) {
|
||||
return $this->json(
|
||||
['data' => null, 'error' => ['code' => 'ORDER_NOT_FOUND', 'message' => $this->messageFor('ORDER_NOT_FOUND')]],
|
||||
404,
|
||||
);
|
||||
}
|
||||
|
||||
return $this->json(['data' => $this->present($order)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fabrique le repository de commande sur l'acces BDD courant. Hook de test
|
||||
* (sous-classe -> double) : redefinir db() suffit a injecter une base factice.
|
||||
|
|
|
|||
|
|
@ -59,6 +59,35 @@ class OrderRepository
|
|||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Recherche une commande par son numero (prefixe canal K/C/D + id). Lecture
|
||||
* publique du statut cote borne (suivi apres encaissement). Renvoie null si le
|
||||
* numero est inconnu. Lecture seule : ne sert que des champs non sensibles
|
||||
* (la commande kiosk est anonyme, pas de PII).
|
||||
*
|
||||
* @return array{id:int, order_number:string, total_ttc_cents:int, status:string}|null
|
||||
*/
|
||||
public function findByNumber(string $number): ?array
|
||||
{
|
||||
if ($number === '') {
|
||||
return null;
|
||||
}
|
||||
$row = $this->db->fetch(
|
||||
'SELECT id, order_number, total_ttc_cents, status FROM customer_order WHERE order_number = :n',
|
||||
['n' => $number],
|
||||
);
|
||||
if ($row === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => (int) $row['id'],
|
||||
'order_number' => (string) $row['order_number'],
|
||||
'total_ttc_cents' => (int) $row['total_ttc_cents'],
|
||||
'status' => (string) $row['status'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Cree une commande borne en pending_payment. Idempotent sur idempotency_key.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -88,6 +88,8 @@ try {
|
|||
// un seul segment (numero K+id), pas de collision avec un sous-chemin.
|
||||
$router->add('POST', '/api/orders', [OrderController::class, 'create']);
|
||||
$router->add('POST', '/api/orders/{number}/pay', [OrderController::class, 'pay']);
|
||||
// Suivi public du statut d'une commande par son numero (lecture seule, anonyme).
|
||||
$router->add('GET', '/api/orders/{number}', [OrderController::class, 'show']);
|
||||
|
||||
// Lecture catalogue borne (P4, docs/api/conventions.md section 5.2). API publique
|
||||
// kiosk, ANONYME : la borne consulte sans session. Lecture seule ; ne sert que le
|
||||
|
|
|
|||
|
|
@ -132,4 +132,43 @@ final class OrderControllerTest extends TestCase
|
|||
self::assertIsArray($data);
|
||||
self::assertSame('INVALID_TRANSITION', $data['error']['code'] ?? null);
|
||||
}
|
||||
|
||||
public function testShowReturnsOrderStatus(): void
|
||||
{
|
||||
$db = new FakeOrderDatabase();
|
||||
$db->orderByNumber = ['id' => 100, 'order_number' => 'K100', 'total_ttc_cents' => 890, 'status' => 'paid'];
|
||||
|
||||
$response = $this->controller($db, '', '/api/orders/K100')->show(['number' => 'K100']);
|
||||
|
||||
self::assertSame(200, $response->status());
|
||||
$data = json_decode($response->body(), true);
|
||||
self::assertIsArray($data);
|
||||
self::assertSame('K100', $data['data']['order_number'] ?? null);
|
||||
self::assertSame('paid', $data['data']['status'] ?? null);
|
||||
self::assertSame(890, $data['data']['total_ttc_cents'] ?? null);
|
||||
}
|
||||
|
||||
public function testShowUnknownReturns404(): void
|
||||
{
|
||||
$db = new FakeOrderDatabase();
|
||||
$db->orderByNumber = null;
|
||||
|
||||
$response = $this->controller($db, '', '/api/orders/K404')->show(['number' => 'K404']);
|
||||
|
||||
self::assertSame(404, $response->status());
|
||||
$data = json_decode($response->body(), true);
|
||||
self::assertIsArray($data);
|
||||
self::assertSame('ORDER_NOT_FOUND', $data['error']['code'] ?? null);
|
||||
}
|
||||
|
||||
public function testShowEmptyNumberReturns404(): void
|
||||
{
|
||||
$db = new FakeOrderDatabase();
|
||||
$db->orderByNumber = ['id' => 1, 'order_number' => 'K1', 'total_ttc_cents' => 100, 'status' => 'paid'];
|
||||
|
||||
// Numero vide : court-circuite avant toute lecture BDD (findByNumber renvoie null).
|
||||
$response = $this->controller($db, '', '/api/orders/')->show(['number' => '']);
|
||||
|
||||
self::assertSame(404, $response->status());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue