Harden Sæby demo registration and department scope (#335)
Complete and secure public customer/driver registration, authoritative limited-backoffice department scope, one-time employee QR login, and pricing concurrency for the Sæby demo.
This commit is contained in:
@@ -93,6 +93,16 @@ it('sets and applies department-specific customer discounts without legacy fallb
|
||||
'department_id' => $fixture['department']['id'],
|
||||
'user_id' => $fixture['customer']['id'],
|
||||
'overrides' => [
|
||||
[
|
||||
'is_category' => true,
|
||||
'product_or_category_id' => (string)$fixture['category']['id'],
|
||||
'discount' => 60,
|
||||
],
|
||||
[
|
||||
'is_category' => true,
|
||||
'product_or_category_id' => 'global',
|
||||
'discount' => 80,
|
||||
],
|
||||
[
|
||||
'is_category' => false,
|
||||
'product_or_category_id' => $fixture['product']['id'],
|
||||
@@ -107,7 +117,12 @@ it('sets and applies department-specific customer discounts without legacy fallb
|
||||
->assertEnvelope()
|
||||
->assertSuccess();
|
||||
|
||||
expect($updated->data()['overrides'][0]['percentage'] ?? null)->toBe(25);
|
||||
$productOverrides = array_values(array_filter(
|
||||
$updated->data()['overrides'],
|
||||
static fn(array $override): bool => $override['is_category'] === false
|
||||
));
|
||||
expect($productOverrides)->toHaveCount(1);
|
||||
expect($productOverrides[0]['percentage'] ?? null)->toBe(25);
|
||||
expect($updated->data()['categories'][0]['products'][0]['effective_price'] ?? null)->toBe(750);
|
||||
|
||||
$byCustomerNumber = api_client()->get(
|
||||
@@ -136,6 +151,124 @@ it('sets and applies department-specific customer discounts without legacy fallb
|
||||
expect((int)($productResponse->data()['price'] ?? 0))->toBe(750);
|
||||
});
|
||||
|
||||
it('lets only the first writer replace customer pricing for a shared revision', function (): void {
|
||||
api_test_covers('GET /limited-backoffice/departments/{departmentId}/customer-pricing', 'revision');
|
||||
api_test_covers('PUT /limited-backoffice/departments/{departmentId}/customer-pricing', 'revision conflict');
|
||||
|
||||
$fixture = department_customer_pricing_setup();
|
||||
$session = api_fixtures()->createUserSession([
|
||||
limited_backoffice_service::PERMISSION_ACCESS,
|
||||
limited_backoffice_service::PERMISSION_VIEW_CUSTOMER_PRICING,
|
||||
limited_backoffice_service::PERMISSION_MANAGE_CUSTOMER_PRICING,
|
||||
'department_access_' . (int)$fixture['department']['id'],
|
||||
]);
|
||||
$path = '/limited-backoffice/departments/' . (int)$fixture['department']['id'] . '/customer-pricing';
|
||||
$query = '?user_id=' . (int)$fixture['customer']['id'];
|
||||
|
||||
$initial = api_client()->get($path . $query, $session['headers']);
|
||||
$initial->assertStatus(200)->assertEnvelope()->assertSuccess();
|
||||
$sharedRevision = $initial->data()['revision'] ?? null;
|
||||
expect($sharedRevision)->toBeString()->toMatch('/^[a-f0-9]{64}$/');
|
||||
|
||||
$winner = api_client()->put($path, [
|
||||
'user_id' => (int)$fixture['customer']['id'],
|
||||
'expected_revision' => $sharedRevision,
|
||||
'overrides' => [[
|
||||
'is_category' => false,
|
||||
'product_or_category_id' => (int)$fixture['product']['id'],
|
||||
'discount' => 20,
|
||||
]],
|
||||
], $session['headers']);
|
||||
$winner->assertStatus(200)->assertEnvelope()->assertSuccess();
|
||||
$winningRevision = $winner->data()['revision'] ?? null;
|
||||
expect($winningRevision)->toBeString()->not->toBe($sharedRevision);
|
||||
|
||||
$stale = api_client()->put($path, [
|
||||
'user_id' => (int)$fixture['customer']['id'],
|
||||
'expected_revision' => $sharedRevision,
|
||||
'overrides' => [[
|
||||
'is_category' => false,
|
||||
'product_or_category_id' => (int)$fixture['product']['id'],
|
||||
'discount' => 70,
|
||||
]],
|
||||
], $session['headers']);
|
||||
$stale
|
||||
->assertStatus(409)
|
||||
->assertEnvelope()
|
||||
->assertSuccess(false)
|
||||
->assertMessage('Pricing has changed. Reload and try again.');
|
||||
expect($stale->data()['code'] ?? null)->toBe('pricing_revision_conflict');
|
||||
expect($stale->data()['current_revision'] ?? null)->toBe($winningRevision);
|
||||
|
||||
$reloaded = api_client()->get($path . $query, $session['headers']);
|
||||
$reloaded->assertStatus(200)->assertEnvelope()->assertSuccess();
|
||||
expect($reloaded->data()['revision'] ?? null)->toBe($winningRevision);
|
||||
expect($reloaded->data()['overrides'])->toHaveCount(1);
|
||||
expect($reloaded->data()['overrides'][0]['percentage'] ?? null)->toBe(20);
|
||||
expect($reloaded->data()['categories'][0]['products'][0]['effective_price'] ?? null)->toBe(800);
|
||||
});
|
||||
|
||||
it('rejects ambiguous and duplicate customer price overrides without changing saved pricing', function (): void {
|
||||
api_test_covers('PUT /limited-backoffice/departments/{departmentId}/customer-pricing', 'validation');
|
||||
|
||||
$fixture = department_customer_pricing_setup();
|
||||
$session = api_fixtures()->createUserSession([
|
||||
limited_backoffice_service::PERMISSION_ACCESS,
|
||||
limited_backoffice_service::PERMISSION_VIEW_CUSTOMER_PRICING,
|
||||
limited_backoffice_service::PERMISSION_MANAGE_CUSTOMER_PRICING,
|
||||
'department_access_' . (int)$fixture['department']['id'],
|
||||
]);
|
||||
$path = '/limited-backoffice/departments/' . (int)$fixture['department']['id'] . '/customer-pricing';
|
||||
$initial = api_client()->get(
|
||||
$path . '?user_id=' . (int)$fixture['customer']['id'],
|
||||
$session['headers']
|
||||
);
|
||||
$revision = $initial->data()['revision'];
|
||||
|
||||
foreach ([
|
||||
[[
|
||||
'is_category' => false,
|
||||
'product_or_category_id' => (int)$fixture['product']['id'],
|
||||
'discount' => 10,
|
||||
'fixed_price' => 500,
|
||||
]],
|
||||
[[
|
||||
'is_category' => true,
|
||||
'product_or_category_id' => (string)$fixture['category']['id'],
|
||||
'discount' => 10,
|
||||
'fixed_price' => 500,
|
||||
]],
|
||||
[
|
||||
[
|
||||
'is_category' => false,
|
||||
'product_or_category_id' => (int)$fixture['product']['id'],
|
||||
'discount' => 10,
|
||||
],
|
||||
[
|
||||
'is_category' => false,
|
||||
'product_or_category_id' => (int)$fixture['product']['id'],
|
||||
'discount' => 20,
|
||||
],
|
||||
],
|
||||
] as $overrides) {
|
||||
api_client()->put($path, [
|
||||
'user_id' => (int)$fixture['customer']['id'],
|
||||
'expected_revision' => $revision,
|
||||
'overrides' => $overrides,
|
||||
], $session['headers'])
|
||||
->assertStatus(400)
|
||||
->assertEnvelope()
|
||||
->assertSuccess(false);
|
||||
}
|
||||
|
||||
$unchanged = api_client()->get(
|
||||
$path . '?user_id=' . (int)$fixture['customer']['id'],
|
||||
$session['headers']
|
||||
);
|
||||
expect($unchanged->data()['revision'] ?? null)->toBe($revision);
|
||||
expect($unchanged->data()['overrides'])->toBe([]);
|
||||
});
|
||||
|
||||
it('limits department customer pricing to assigned limited-backoffice departments', function (): void {
|
||||
api_test_covers('GET /limited-backoffice/departments/{departmentId}/customer-pricing', 'auth');
|
||||
api_test_covers('PUT /limited-backoffice/departments/{departmentId}/customer-pricing', 'auth');
|
||||
|
||||
Reference in New Issue
Block a user