Files
api/services/nginx/app/tests/Api/OrdersApiTest.php
T
Jeppe Bundgaard 7d450e285e Remove outdated edge gateway object classes, add new agent implementation
Transitioned from obsolete gateway object classes (`edge_gateway_shell_action_jobs_o`, `edge_gateway_shell_events_o`, `edge_gateway_shell_sessions_o`, `edge_gateway_update_jobs_o`) to the new agent implementation (`edge-gateway-agent/agent.php`).
2026-04-21 14:13:17 +02:00

828 lines
32 KiB
PHP

<?php
declare(strict_types=1);
usesApiSuite();
function activeWashCertificateAttachmentIdsForOrder(int $orderId): array
{
$statement = api_test_runtime()->db()->prepare(
'SELECT id, content
FROM object_attachments
WHERE object_type IN (?, ?)
AND object_id = ?
AND deleted_at IS NULL
ORDER BY id ASC'
);
expect($statement)->not->toBeFalse();
$objectType = 'orders';
$backtickedObjectType = '`orders`';
$statement->bind_param('ssi', $objectType, $backtickedObjectType, $orderId);
$statement->execute();
$result = $statement->get_result();
$attachmentIds = [];
while ($row = $result->fetch_assoc()) {
$content = json_decode((string)($row['content'] ?? ''), true);
$other = is_array($content) ? ($content['other'] ?? null) : null;
if (is_string($other) && strtolower($other) === 'wash_certificate') {
$attachmentIds[] = (int)($row['id'] ?? 0);
}
}
$result->free();
$statement->close();
return array_values(array_filter($attachmentIds, static fn(int $id): bool => $id > 0));
}
it('lists orders for an admin-scoped user and limits the results to the permitted departments', function (): void {
api_test_covers('GET /orders', 'happy');
$departmentOne = api_fixtures()->createDepartment(['name' => 'Department One']);
$departmentTwo = api_fixtures()->createDepartment(['name' => 'Department Two']);
$customerOne = api_fixtures()->createUser(['display_name' => 'Customer One']);
$customerTwo = api_fixtures()->createUser(['display_name' => 'Customer Two']);
$cashier = api_fixtures()->createUser(['display_name' => 'Cashier']);
$visibleOrder = api_fixtures()->createOrder([
'customer_id' => $customerOne['customer_number'],
'cashier_id' => $cashier['id'],
'department_id' => $departmentOne['id'],
'reference' => 'VISIBLE',
'reg_1' => 'VISIBLE1',
]);
$hiddenOrder = api_fixtures()->createOrder([
'customer_id' => $customerTwo['customer_number'],
'cashier_id' => $cashier['id'],
'department_id' => $departmentTwo['id'],
'reference' => 'HIDDEN',
'reg_1' => 'HIDDEN1',
]);
$session = api_fixtures()->createUserSession([
'list_orders',
'department_access_' . $departmentOne['id'],
]);
$response = api_client()->get('/orders', $session['headers']);
$response
->assertStatus(200)
->assertEnvelope()
->assertSuccess();
$orderIds = array_map(
static fn(array $order): int => (int)($order['id'] ?? 0),
is_array($response->data()) ? $response->data() : []
);
expect($orderIds)
->toContain($visibleOrder['id'])
->not->toContain($hiddenOrder['id']);
});
it('lists only the targeted customer orders for subuser sessions', function (): void {
api_test_covers('GET /orders', 'happy');
$department = api_fixtures()->createDepartment();
$targetCustomer = api_fixtures()->createUser(['display_name' => 'Target Customer']);
$otherCustomer = api_fixtures()->createUser(['display_name' => 'Other Customer']);
$cashier = api_fixtures()->createUser(['display_name' => 'Subuser Cashier']);
$targetOrder = api_fixtures()->createOrder([
'customer_id' => $targetCustomer['customer_number'],
'cashier_id' => $cashier['id'],
'department_id' => $department['id'],
'reference' => 'TARGET',
'reg_1' => 'TARGET1',
]);
$otherOrder = api_fixtures()->createOrder([
'customer_id' => $otherCustomer['customer_number'],
'cashier_id' => $cashier['id'],
'department_id' => $department['id'],
'reference' => 'OTHER',
'reg_1' => 'OTHER1',
]);
$session = api_fixtures()->createSubuserSession(
(int)$targetCustomer['customer_number'],
['ORDERS_LIST']
);
$response = api_client()->get('/orders', $session['headers']);
$response
->assertStatus(200)
->assertEnvelope()
->assertSuccess();
$orderIds = array_map(
static fn(array $order): int => (int)($order['id'] ?? 0),
is_array($response->data()) ? $response->data() : []
);
expect($orderIds)
->toContain($targetOrder['id'])
->not->toContain($otherOrder['id']);
expect($response->meta())->toHaveKey('target_customer_number', $targetCustomer['customer_number']);
});
it('returns the current auth and permission failures when order listing is not allowed', function (): void {
api_test_covers('GET /orders', 'auth');
$missingToken = api_client()->get('/orders');
$missingToken
->assertStatus(400)
->assertEnvelope()
->assertSuccess(false)
->assertMessage('Invalid session');
$session = api_fixtures()->createUserSession([]);
$missingPermissions = api_client()->get('/orders', $session['headers']);
$missingPermissions
->assertStatus(403)
->assertEnvelope()
->assertSuccess(false)
->assertMissingPermissions(['list_own_orders', 'list_orders']);
});
it('creates orders through the real endpoint', function (): void {
api_test_covers('POST /orders', 'happy');
$customer = api_fixtures()->createUser(['display_name' => 'Order Customer']);
$department = api_fixtures()->createDepartment();
api_fixtures()->createInvoiceCollection([
'customer_number' => $customer['customer_number'],
]);
$session = api_fixtures()->createUserSession(['add_order']);
$response = api_client()->post('/orders', [
'customer_id' => $customer['customer_number'],
'department_id' => $department['id'],
'reference' => 'ORDER-POST',
'notes' => 'Created through HTTP',
'safety_seal' => 'SEAL-CREATE',
'reg_1' => ' post-123 ',
'reg_2' => ' tr 9-8 ',
'reg_3' => ' 7z/x ',
], $session['headers']);
$response
->assertStatus(200)
->assertEnvelope()
->assertSuccess();
$orderId = (int)($response->data()['id'] ?? 0);
expect($orderId)->toBeGreaterThan(0);
$row = api_fixtures()->fetchRowById('orders', $orderId);
expect($row)->not->toBeNull();
expect($row['reg_1'] ?? null)->toBe('POST123');
expect($row['reg_2'] ?? null)->toBe('TR98');
expect($row['reg_3'] ?? null)->toBe('7ZX');
expect($row['safety_seal'] ?? null)->toBe('SEAL-CREATE');
api_fixtures()->cleanupDeleteById('orders', $orderId);
});
it('rejects invalid order creation requests', function (): void {
api_test_covers('POST /orders', 'failure');
$customer = api_fixtures()->createUser();
$department = api_fixtures()->createDepartment();
$session = api_fixtures()->createUserSession(['add_order']);
$missingReference = api_client()->post('/orders', [
'customer_id' => $customer['customer_number'],
'department_id' => $department['id'],
'notes' => 'Missing reference',
'reg_1' => 'MISSREF',
], $session['headers']);
$missingReference
->assertStatus(400)
->assertEnvelope()
->assertSuccess(false)
->assertMessage('Reference is required');
$invalidDepartment = api_client()->post('/orders', [
'customer_id' => $customer['customer_number'],
'department_id' => 999999,
'reference' => 'BAD-DEPT',
'notes' => 'Bad department',
'reg_1' => 'BADDEPT',
], $session['headers']);
$invalidDepartment
->assertStatus(500)
->assertEnvelope()
->assertSuccess(false)
->assertMessageContains('getDepartmentById()');
$invalidCustomer = api_client()->post('/orders', [
'customer_id' => 999999,
'department_id' => $department['id'],
'reference' => 'BAD-CUSTOMER',
'notes' => 'Bad customer',
'reg_1' => 'BADCUST',
], $session['headers']);
$invalidCustomer
->assertStatus(500)
->assertEnvelope()
->assertSuccess(false)
->assertMessageContains('e-conomic request failed with HTTP');
api_fixtures()->addCustomerAttribute((int)$customer['id'], 'requiresReferenceNumber');
$missingRequiredReference = api_client()->post('/orders', [
'customer_id' => $customer['customer_number'],
'department_id' => $department['id'],
'reference' => '',
'notes' => 'Empty reference',
'reg_1' => 'REQREF1',
], $session['headers']);
$missingRequiredReference
->assertStatus(400)
->assertEnvelope()
->assertSuccess(false)
->assertMessage('Reference is required by the customer');
});
it('updates orders through the primary endpoint', function (): void {
api_test_covers('PUT /orders', 'happy');
$department = api_fixtures()->createDepartment(['name' => 'Original Department']);
$updatedDepartment = api_fixtures()->createDepartment(['name' => 'Updated Department']);
$customer = api_fixtures()->createUser(['display_name' => 'Original Customer']);
$updatedCustomer = api_fixtures()->createUser(['display_name' => 'Updated Customer']);
$cashier = api_fixtures()->createUser(['display_name' => 'Order Editor']);
$updatedInvoiceCollection = api_fixtures()->createInvoiceCollection([
'customer_number' => $updatedCustomer['customer_number'],
]);
$order = api_fixtures()->createOrder([
'customer_id' => $customer['customer_number'],
'cashier_id' => $cashier['id'],
'department_id' => $department['id'],
'reference' => 'BEFORE-REF',
'reg_1' => 'BEFR123',
'reg_2' => 'OLD-2',
'reg_3' => 'OLD-3',
'notes' => 'Before update',
'po' => 'PO-BEFORE',
'lane' => 2,
'wash_id' => 'WASH-BEFORE',
'booking_id' => 321,
'safety_seal' => 'SEAL-BEFORE',
'include_in_invoice' => true,
'created_at' => '2026-04-08 08:44:07',
]);
$session = api_fixtures()->createUserSession(['edit_order']);
$response = api_client()->put('/orders', [
'id' => $order['id'],
'customer_id' => $updatedCustomer['customer_number'],
'department_id' => $updatedDepartment['id'],
'reference' => 'AFTER-REF',
'reg_1' => ' af-tr 123 ',
'reg_2' => ' new-2 ',
'reg_3' => ' new/3 ',
'notes' => 'After update',
'po' => 'PO-123',
'lane' => 7,
'wash_id' => 'WASH-123',
'booking_id' => 9876,
'safety_seal' => 'SEAL-AFTER',
'invoice_collection_id' => $updatedInvoiceCollection['id'],
'created_at' => '2026-04-09 13:37:00',
'include_in_invoice' => false,
], $session['headers']);
$response
->assertStatus(200)
->assertEnvelope()
->assertSuccess()
->assertMessage('Order updated successfully');
$row = api_fixtures()->fetchRowById('orders', (int)$order['id']);
expect($row)->not->toBeNull();
expect((int)($row['customer_id'] ?? 0))->toBe((int)$updatedCustomer['customer_number']);
expect((int)($row['department_id'] ?? 0))->toBe((int)$updatedDepartment['id']);
expect($row['reference'] ?? null)->toBe('AFTER-REF');
expect($row['reg_1'] ?? null)->toBe('AFTR123');
expect($row['reg_2'] ?? null)->toBe('NEW2');
expect($row['reg_3'] ?? null)->toBe('NEW3');
expect($row['notes'] ?? null)->toBe('After update');
expect($row['po'] ?? null)->toBe('PO-123');
expect((int)($row['lane'] ?? 0))->toBe(7);
expect($row['wash_id'] ?? null)->toBe('WASH-123');
expect((int)($row['booking_id'] ?? 0))->toBe(9876);
expect($row['safety_seal'] ?? null)->toBe('SEAL-AFTER');
expect((int)($row['invoice_collection_id'] ?? 0))->toBe((int)$updatedInvoiceCollection['id']);
expect($row['created_at'] ?? null)->toBe('2026-04-09 13:37:00');
expect((int)($row['include_in_invoice'] ?? 1))->toBe(0);
});
it('reassigns invoice collections when changing an order across the draft customer boundary', function (): void {
api_test_covers('PUT /orders', 'happy');
$draftCustomer = api_fixtures()->createUser(['display_name' => 'Draft Customer']);
$regularCustomer = api_fixtures()->createUser(['display_name' => 'Regular Customer']);
$department = api_fixtures()->createDepartment(['name' => 'Draft Boundary Department']);
$cashier = api_fixtures()->createUser(['display_name' => 'Draft Boundary Cashier']);
api_fixtures()->setModuleConfig('economic', 'transactionDraftCustomerNumber', (string)$draftCustomer['customer_number'], 'int');
$order = api_fixtures()->createOrder([
'customer_id' => $regularCustomer['customer_number'],
'cashier_id' => $cashier['id'],
'department_id' => $department['id'],
'reference' => 'DRAFT-BOUNDARY',
'reg_1' => 'DRAFT123',
]);
$session = api_fixtures()->createUserSession(['edit_order']);
api_client()->put('/orders', [
'id' => $order['id'],
'customer_id' => $draftCustomer['customer_number'],
], $session['headers'])
->assertStatus(200)
->assertEnvelope()
->assertSuccess()
->assertMessage('Order updated successfully');
$draftRow = api_fixtures()->fetchRowById('orders', (int)$order['id']);
expect($draftRow)->not->toBeNull();
$draftInvoiceCollectionId = (int)($draftRow['invoice_collection_id'] ?? 0);
expect($draftInvoiceCollectionId)->toBeGreaterThan(0);
$draftCollectionRow = api_fixtures()->fetchRowById('collected_order_invoices', $draftInvoiceCollectionId);
expect($draftCollectionRow)->not->toBeNull();
expect((int)($draftCollectionRow['customer_number'] ?? 0))->toBe((int)$draftCustomer['customer_number']);
api_client()->put('/orders', [
'id' => $order['id'],
'customer_id' => $regularCustomer['customer_number'],
], $session['headers'])
->assertStatus(200)
->assertEnvelope()
->assertSuccess()
->assertMessage('Order updated successfully');
$regularRow = api_fixtures()->fetchRowById('orders', (int)$order['id']);
expect($regularRow)->not->toBeNull();
$regularInvoiceCollectionId = (int)($regularRow['invoice_collection_id'] ?? 0);
expect($regularInvoiceCollectionId)->toBeGreaterThan(0);
$regularCollectionRow = api_fixtures()->fetchRowById('collected_order_invoices', $regularInvoiceCollectionId);
expect($regularCollectionRow)->not->toBeNull();
expect((int)($regularCollectionRow['customer_number'] ?? 0))->toBe((int)$regularCustomer['customer_number']);
});
it('regenerates attached wash certificates when certificate metadata changes through the primary endpoint', function (): void {
api_test_covers('PUT /orders', 'happy');
$department = api_fixtures()->createDepartment(['name' => 'Wash Certificate Department']);
$customer = api_fixtures()->createUser(['display_name' => 'Original Certificate Customer']);
$updatedCustomer = api_fixtures()->createUser(['display_name' => 'Updated Certificate Customer']);
$cashier = api_fixtures()->createUser(['display_name' => 'Certificate Cashier']);
$order = api_fixtures()->createOrder([
'customer_id' => $customer['customer_number'],
'cashier_id' => $cashier['id'],
'department_id' => $department['id'],
'reference' => 'CERT-REF',
'reg_1' => 'CERT123',
'reg_2' => 'TRAIL1',
'safety_seal' => 'SEAL-OLD',
'created_at' => '2026-04-12 10:15:00',
]);
$session = api_fixtures()->createUserSession([
'edit_order',
'add_order_attachments',
'department_access_' . $department['id'],
]);
api_client()->post('/orders/attachments/upload', [
'order_id' => $order['id'],
'base64_file' => base64_encode('wash-certificate'),
'file_name' => 'wash_certificate',
], $session['headers'])
->assertStatus(200)
->assertEnvelope()
->assertSuccess();
$initialAttachmentIds = activeWashCertificateAttachmentIdsForOrder((int)$order['id']);
expect($initialAttachmentIds)->toHaveCount(1);
$initialAttachmentId = $initialAttachmentIds[0];
$initialAttachmentRow = api_fixtures()->fetchRowById('object_attachments', $initialAttachmentId);
expect($initialAttachmentRow)->not->toBeNull();
$initialDocument = (json_decode((string)($initialAttachmentRow['content'] ?? ''), true) ?? [])['document'] ?? null;
api_client()->put('/orders', [
'id' => $order['id'],
'customer_id' => $updatedCustomer['customer_number'],
'reg_2' => ' new-trail-55 ',
'safety_seal' => 'SEAL-NEW',
], $session['headers'])
->assertStatus(200)
->assertEnvelope()
->assertSuccess()
->assertMessage('Order updated successfully');
$orderRow = api_fixtures()->fetchRowById('orders', (int)$order['id']);
expect($orderRow)->not->toBeNull();
expect((int)($orderRow['customer_id'] ?? 0))->toBe((int)$updatedCustomer['customer_number']);
expect($orderRow['reg_2'] ?? null)->toBe('NEWTRAIL55');
expect($orderRow['safety_seal'] ?? null)->toBe('SEAL-NEW');
$updatedAttachmentIds = activeWashCertificateAttachmentIdsForOrder((int)$order['id']);
expect($updatedAttachmentIds)->toHaveCount(1);
expect($updatedAttachmentIds)->not->toContain($initialAttachmentId);
$updatedAttachmentId = $updatedAttachmentIds[0];
$updatedAttachmentRow = api_fixtures()->fetchRowById('object_attachments', $updatedAttachmentId);
$deletedAttachmentRow = api_fixtures()->fetchRowById('object_attachments', $initialAttachmentId);
expect($updatedAttachmentRow)->not->toBeNull();
expect($deletedAttachmentRow)->not->toBeNull();
expect($deletedAttachmentRow['deleted_at'] ?? null)->not->toBeNull();
expect((json_decode((string)($updatedAttachmentRow['content'] ?? ''), true) ?? [])['document'] ?? null)->not->toBe($initialDocument);
});
it('regenerates attached wash certificates for legacy field-value updates through the alias endpoint', function (): void {
api_test_covers('PUT /order', 'happy');
$department = api_fixtures()->createDepartment(['name' => 'Alias Wash Certificate Department']);
$customer = api_fixtures()->createUser(['display_name' => 'Alias Certificate Customer']);
$cashier = api_fixtures()->createUser(['display_name' => 'Alias Certificate Cashier']);
$order = api_fixtures()->createOrder([
'customer_id' => $customer['customer_number'],
'cashier_id' => $cashier['id'],
'department_id' => $department['id'],
'reference' => 'ALIAS-CERT',
'reg_1' => 'ALIAS123',
'safety_seal' => 'ALIAS-SEAL',
]);
$session = api_fixtures()->createUserSession([
'edit_order',
'add_order_attachments',
'department_access_' . $department['id'],
]);
api_client()->post('/orders/attachments/upload', [
'order_id' => $order['id'],
'base64_file' => base64_encode('wash-certificate'),
'file_name' => 'wash_certificate',
], $session['headers'])
->assertStatus(200)
->assertEnvelope()
->assertSuccess();
$initialAttachmentIds = activeWashCertificateAttachmentIdsForOrder((int)$order['id']);
expect($initialAttachmentIds)->toHaveCount(1);
$initialAttachmentId = $initialAttachmentIds[0];
api_client()->put('/order', [
'id' => $order['id'],
'field' => 'reg_1',
'value' => ' zz-88 11 ',
], $session['headers'])
->assertStatus(200)
->assertEnvelope()
->assertSuccess()
->assertMessage('Order updated successfully');
$orderRow = api_fixtures()->fetchRowById('orders', (int)$order['id']);
expect($orderRow)->not->toBeNull();
expect($orderRow['reg_1'] ?? null)->toBe('ZZ8811');
$updatedAttachmentIds = activeWashCertificateAttachmentIdsForOrder((int)$order['id']);
expect($updatedAttachmentIds)->toHaveCount(1);
expect($updatedAttachmentIds)->not->toContain($initialAttachmentId);
$deletedAttachmentRow = api_fixtures()->fetchRowById('object_attachments', $initialAttachmentId);
expect($deletedAttachmentRow)->not->toBeNull();
expect($deletedAttachmentRow['deleted_at'] ?? null)->not->toBeNull();
});
it('supports legacy field-value metadata updates through the primary endpoint', function (): void {
api_test_covers('PUT /orders', 'happy');
$department = api_fixtures()->createDepartment(['name' => 'Legacy Field Department']);
$customer = api_fixtures()->createUser(['display_name' => 'Legacy Field Customer']);
$cashier = api_fixtures()->createUser(['display_name' => 'Legacy Field Cashier']);
$order = api_fixtures()->createOrder([
'customer_id' => $customer['customer_number'],
'cashier_id' => $cashier['id'],
'department_id' => $department['id'],
'reference' => 'START-REF',
'reg_1' => 'START123',
'reg_2' => 'START2',
'reg_3' => 'START3',
'notes' => 'Start note',
]);
$session = api_fixtures()->createUserSession(['edit_order']);
$payloads = [
['field' => 'reference', 'value' => 'LEGACY-REF'],
['field' => 'notes', 'value' => 'Legacy note'],
['field' => 'safety_seal', 'value' => 'LEGACY-SEAL'],
['field' => 'reg_1', 'value' => ' ab-12 34 '],
['field' => 'reg_2', 'value' => ' cd/56 78 '],
['field' => 'reg_3', 'value' => ' ef_90 12 '],
];
foreach ($payloads as $payload) {
api_client()->put('/orders', [
'id' => $order['id'],
...$payload,
], $session['headers'])
->assertStatus(200)
->assertEnvelope()
->assertSuccess()
->assertMessage('Order updated successfully');
}
$row = api_fixtures()->fetchRowById('orders', (int)$order['id']);
expect($row)->not->toBeNull();
expect($row['reference'] ?? null)->toBe('LEGACY-REF');
expect($row['notes'] ?? null)->toBe('Legacy note');
expect($row['safety_seal'] ?? null)->toBe('LEGACY-SEAL');
expect($row['reg_1'] ?? null)->toBe('AB1234');
expect($row['reg_2'] ?? null)->toBe('CD5678');
expect($row['reg_3'] ?? null)->toBe('EF9012');
});
it('rejects invalid updates through the primary order endpoint', function (): void {
api_test_covers('PUT /orders', 'failure');
$session = api_fixtures()->createUserSession(['edit_order']);
$missingId = api_client()->put('/orders', [
'notes' => 'No id',
], $session['headers']);
$missingId
->assertStatus(400)
->assertEnvelope()
->assertSuccess(false)
->assertMessage('ID is required');
$missingOrder = api_client()->put('/orders', [
'id' => 999999,
'notes' => 'Missing order',
], $session['headers']);
$missingOrder
->assertStatus(500)
->assertEnvelope()
->assertSuccess(false)
->assertMessageContains('must not be accessed before initialization');
});
it('updates orders through the legacy alias endpoint', function (): void {
api_test_covers('PUT /order', 'happy');
$department = api_fixtures()->createDepartment(['name' => 'Legacy Department']);
$updatedDepartment = api_fixtures()->createDepartment(['name' => 'Legacy Updated Department']);
$customer = api_fixtures()->createUser(['display_name' => 'Legacy Original Customer']);
$updatedCustomer = api_fixtures()->createUser(['display_name' => 'Legacy Updated Customer']);
$cashier = api_fixtures()->createUser(['display_name' => 'Legacy Editor']);
$updatedInvoiceCollection = api_fixtures()->createInvoiceCollection([
'customer_number' => $updatedCustomer['customer_number'],
]);
$order = api_fixtures()->createOrder([
'customer_id' => $customer['customer_number'],
'cashier_id' => $cashier['id'],
'department_id' => $department['id'],
'reference' => 'LEGACY-BEFORE',
'reg_1' => 'LGCY123',
'reg_2' => 'LGCY-2',
'reg_3' => 'LGCY-3',
'notes' => 'Legacy before',
'po' => 'LEGACY-PO',
'lane' => 4,
'wash_id' => 'LEGACY-WASH',
'booking_id' => 654,
'safety_seal' => 'LEGACY-SEAL-BEFORE',
'include_in_invoice' => false,
'created_at' => '2026-04-10 08:15:00',
]);
$session = api_fixtures()->createUserSession(['edit_order']);
$response = api_client()->put('/order', [
'id' => $order['id'],
'customer_id' => $updatedCustomer['customer_number'],
'department_id' => $updatedDepartment['id'],
'reference' => 'LEGACY-AFTER',
'reg_1' => ' lgcy-999 ',
'reg_2' => ' leg-2 ',
'reg_3' => ' leg/3 ',
'notes' => 'Legacy after',
'po' => 'LEGACY-PO-NEW',
'lane' => 9,
'wash_id' => 'LEGACY-WASH-NEW',
'booking_id' => 7654,
'safety_seal' => 'LEGACY-SEAL-AFTER',
'invoice_collection_id' => $updatedInvoiceCollection['id'],
'created_at' => '2026-04-11 11:22:33',
'include_in_invoice' => true,
], $session['headers']);
$response
->assertStatus(200)
->assertEnvelope()
->assertSuccess()
->assertMessage('Order updated successfully');
$row = api_fixtures()->fetchRowById('orders', (int)$order['id']);
expect($row)->not->toBeNull();
expect((int)($row['customer_id'] ?? 0))->toBe((int)$updatedCustomer['customer_number']);
expect((int)($row['department_id'] ?? 0))->toBe((int)$updatedDepartment['id']);
expect($row['reference'] ?? null)->toBe('LEGACY-AFTER');
expect($row['reg_1'] ?? null)->toBe('LGCY999');
expect($row['reg_2'] ?? null)->toBe('LEG2');
expect($row['reg_3'] ?? null)->toBe('LEG3');
expect($row['notes'] ?? null)->toBe('Legacy after');
expect($row['po'] ?? null)->toBe('LEGACY-PO-NEW');
expect((int)($row['lane'] ?? 0))->toBe(9);
expect($row['wash_id'] ?? null)->toBe('LEGACY-WASH-NEW');
expect((int)($row['booking_id'] ?? 0))->toBe(7654);
expect($row['safety_seal'] ?? null)->toBe('LEGACY-SEAL-AFTER');
expect((int)($row['invoice_collection_id'] ?? 0))->toBe((int)$updatedInvoiceCollection['id']);
expect($row['created_at'] ?? null)->toBe('2026-04-11 11:22:33');
expect((int)($row['include_in_invoice'] ?? 0))->toBe(1);
});
it('supports legacy field-value metadata updates through the alias endpoint', function (): void {
api_test_covers('PUT /order', 'happy');
$department = api_fixtures()->createDepartment(['name' => 'Alias Field Department']);
$customer = api_fixtures()->createUser(['display_name' => 'Alias Field Customer']);
$cashier = api_fixtures()->createUser(['display_name' => 'Alias Field Cashier']);
$order = api_fixtures()->createOrder([
'customer_id' => $customer['customer_number'],
'cashier_id' => $cashier['id'],
'department_id' => $department['id'],
'reference' => 'ALIAS-START',
'reg_1' => 'ALIAS123',
'reg_2' => 'ALIAS2',
'reg_3' => 'ALIAS3',
'notes' => 'Alias note',
]);
$session = api_fixtures()->createUserSession(['edit_order']);
$payloads = [
['field' => 'reference', 'value' => 'ALIAS-REF'],
['field' => 'notes', 'value' => 'Alias updated note'],
['field' => 'safety_seal', 'value' => 'ALIAS-SEAL'],
['field' => 'reg_1', 'value' => ' gh-12 34 '],
['field' => 'reg_2', 'value' => ' ij/56 78 '],
['field' => 'reg_3', 'value' => ' kl_90 12 '],
];
foreach ($payloads as $payload) {
api_client()->put('/order', [
'id' => $order['id'],
...$payload,
], $session['headers'])
->assertStatus(200)
->assertEnvelope()
->assertSuccess()
->assertMessage('Order updated successfully');
}
$row = api_fixtures()->fetchRowById('orders', (int)$order['id']);
expect($row)->not->toBeNull();
expect($row['reference'] ?? null)->toBe('ALIAS-REF');
expect($row['notes'] ?? null)->toBe('Alias updated note');
expect($row['safety_seal'] ?? null)->toBe('ALIAS-SEAL');
expect($row['reg_1'] ?? null)->toBe('GH1234');
expect($row['reg_2'] ?? null)->toBe('IJ5678');
expect($row['reg_3'] ?? null)->toBe('KL9012');
});
it('rejects invalid updates through the legacy alias endpoint', function (): void {
api_test_covers('PUT /order', 'failure');
$session = api_fixtures()->createUserSession(['edit_order']);
$response = api_client()->put('/order', [
'id' => 999999,
'notes' => 'Missing alias order',
], $session['headers']);
$response
->assertStatus(500)
->assertEnvelope()
->assertSuccess(false)
->assertMessageContains('must not be accessed before initialization');
});
it('rejects unsupported legacy field-value updates on both update endpoints', function (): void {
api_test_covers('PUT /orders', 'failure');
$department = api_fixtures()->createDepartment(['name' => 'Unsupported Field Department']);
$customer = api_fixtures()->createUser(['display_name' => 'Unsupported Field Customer']);
$cashier = api_fixtures()->createUser(['display_name' => 'Unsupported Field Cashier']);
$order = api_fixtures()->createOrder([
'customer_id' => $customer['customer_number'],
'cashier_id' => $cashier['id'],
'department_id' => $department['id'],
'reference' => 'UNCHANGED-REF',
'notes' => 'Unchanged note',
'reg_1' => 'UNCH123',
]);
$session = api_fixtures()->createUserSession(['edit_order']);
foreach (['/orders', '/order'] as $endpoint) {
api_client()->put($endpoint, [
'id' => $order['id'],
'field' => 'cashier_id',
'value' => 999999,
], $session['headers'])
->assertStatus(400)
->assertEnvelope()
->assertSuccess(false)
->assertMessage('Unsupported legacy order field: cashier_id');
}
$row = api_fixtures()->fetchRowById('orders', (int)$order['id']);
expect($row)->not->toBeNull();
expect($row['reference'] ?? null)->toBe('UNCHANGED-REF');
expect($row['notes'] ?? null)->toBe('Unchanged note');
expect($row['reg_1'] ?? null)->toBe('UNCH123');
});
it('deletes orders through the real endpoint', function (): void {
api_test_covers('DELETE /orders', 'happy');
$department = api_fixtures()->createDepartment();
$customer = api_fixtures()->createUser();
$cashier = api_fixtures()->createUser(['display_name' => 'Delete Cashier']);
$order = api_fixtures()->createOrder([
'customer_id' => $customer['customer_number'],
'cashier_id' => $cashier['id'],
'department_id' => $department['id'],
]);
$session = api_fixtures()->createUserSession([
'delete_order',
'department_access_' . $department['id'],
]);
$response = api_client()->delete('/orders', [
'id' => $order['id'],
], $session['headers']);
$response
->assertStatus(200)
->assertEnvelope()
->assertSuccess()
->assertMessage('Order deleted successfully');
$row = api_fixtures()->fetchRowById('orders', (int)$order['id']);
expect($row)->not->toBeNull();
expect($row['deleted_at'] ?? null)->not->toBeNull();
});
it('rejects invalid order delete requests', function (): void {
api_test_covers('DELETE /orders', 'failure');
$department = api_fixtures()->createDepartment();
$session = api_fixtures()->createUserSession([
'delete_order',
'department_access_' . $department['id'],
]);
$missingId = api_client()->delete('/orders', [], $session['headers']);
$missingId
->assertStatus(400)
->assertEnvelope()
->assertSuccess(false)
->assertMessage('ID is required');
$missingOrder = api_client()->delete('/orders', [
'id' => 999999,
], $session['headers']);
$missingOrder
->assertStatus(500)
->assertEnvelope()
->assertSuccess(false)
->assertMessageContains('must not be accessed before initialization');
});