diff --git a/openapi.yaml b/openapi.yaml index 7d04402a..468eb75b 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -12672,7 +12672,15 @@ paths: minimum: 1 - name: user_id in: query - required: true + required: false + description: Internal user id. Provide either user_id or customer_number. + schema: + type: integer + minimum: 1 + - name: customer_number + in: query + required: false + description: Customer number. Provide either user_id or customer_number. schema: type: integer minimum: 1 @@ -12735,7 +12743,15 @@ paths: minimum: 1 - name: user_id in: query - required: true + required: false + description: Internal user id. Provide either user_id or customer_number. + schema: + type: integer + minimum: 1 + - name: customer_number + in: query + required: false + description: Customer number. Provide either user_id or customer_number. schema: type: integer minimum: 1 @@ -13608,7 +13624,6 @@ components: type: object required: - department_id - - user_id - overrides properties: department_id: @@ -13617,6 +13632,9 @@ components: user_id: type: integer minimum: 1 + customer_number: + type: integer + minimum: 1 overrides: type: array items: @@ -13625,12 +13643,14 @@ components: DepartmentCustomerPricingLimitedUpdateRequest: type: object required: - - user_id - overrides properties: user_id: type: integer minimum: 1 + customer_number: + type: integer + minimum: 1 overrides: type: array items: diff --git a/services/nginx/app/classes/limited_backoffice_service.php b/services/nginx/app/classes/limited_backoffice_service.php index 35c56cac..ec8244f1 100644 --- a/services/nginx/app/classes/limited_backoffice_service.php +++ b/services/nginx/app/classes/limited_backoffice_service.php @@ -11,6 +11,8 @@ class limited_backoffice_service { public const PERMISSION_ACCESS = 'limited_backoffice_access'; public const PERMISSION_MANAGE_PRICES = 'limited_backoffice_prices_manage'; + public const PERMISSION_VIEW_CUSTOMER_PRICING = 'limited_backoffice_customer_pricing_view'; + public const PERMISSION_MANAGE_CUSTOMER_PRICING = 'limited_backoffice_customer_pricing_manage'; public const PERMISSION_MANAGE_EMPLOYEES = 'limited_backoffice_employees_manage'; private const PERMISSION_PUBLIC_EMPLOYEE_DATA = 'employee_public_data'; @@ -251,6 +253,8 @@ class limited_backoffice_service 'statistics_bookings_new', self::PERMISSION_ACCESS, self::PERMISSION_MANAGE_PRICES, + self::PERMISSION_VIEW_CUSTOMER_PRICING, + self::PERMISSION_MANAGE_CUSTOMER_PRICING, self::PERMISSION_MANAGE_EMPLOYEES, ], ], @@ -484,6 +488,14 @@ class limited_backoffice_service 'group' => 'limited_backoffice', 'capability' => 'manage_department_prices', ], + self::PERMISSION_VIEW_CUSTOMER_PRICING => [ + 'group' => 'limited_backoffice', + 'capability' => 'view_customer_pricing', + ], + self::PERMISSION_MANAGE_CUSTOMER_PRICING => [ + 'group' => 'limited_backoffice', + 'capability' => 'manage_customer_pricing', + ], self::PERMISSION_MANAGE_EMPLOYEES => [ 'group' => 'limited_backoffice', 'capability' => 'manage_employee_access', diff --git a/services/nginx/app/openapi.yaml b/services/nginx/app/openapi.yaml index a43abc1d..41b3cbf1 100644 --- a/services/nginx/app/openapi.yaml +++ b/services/nginx/app/openapi.yaml @@ -12683,7 +12683,15 @@ paths: minimum: 1 - name: user_id in: query - required: true + required: false + description: Internal user id. Provide either user_id or customer_number. + schema: + type: integer + minimum: 1 + - name: customer_number + in: query + required: false + description: Customer number. Provide either user_id or customer_number. schema: type: integer minimum: 1 @@ -12746,7 +12754,15 @@ paths: minimum: 1 - name: user_id in: query - required: true + required: false + description: Internal user id. Provide either user_id or customer_number. + schema: + type: integer + minimum: 1 + - name: customer_number + in: query + required: false + description: Customer number. Provide either user_id or customer_number. schema: type: integer minimum: 1 @@ -13619,7 +13635,6 @@ components: type: object required: - department_id - - user_id - overrides properties: department_id: @@ -13628,6 +13643,9 @@ components: user_id: type: integer minimum: 1 + customer_number: + type: integer + minimum: 1 overrides: type: array items: @@ -13636,12 +13654,14 @@ components: DepartmentCustomerPricingLimitedUpdateRequest: type: object required: - - user_id - overrides properties: user_id: type: integer minimum: 1 + customer_number: + type: integer + minimum: 1 overrides: type: array items: diff --git a/services/nginx/app/routes/limitedBackofficeRoute.php b/services/nginx/app/routes/limitedBackofficeRoute.php index a4d2c9d6..d7cc64e0 100644 --- a/services/nginx/app/routes/limitedBackofficeRoute.php +++ b/services/nginx/app/routes/limitedBackofficeRoute.php @@ -47,18 +47,20 @@ class limitedBackofficeRoute $this->get('/limited-backoffice/departments/{departmentId}/customer-pricing', function () { $this->withLimitedBackoffice(function (limited_backoffice_service $service, $user): array { $this->requirePermission(limited_backoffice_service::PERMISSION_ACCESS); + $this->requirePermission(limited_backoffice_service::PERMISSION_VIEW_CUSTOMER_PRICING); $departmentId = $this->routePositiveInt('departmentId'); $customerUserId = $this->queryCustomerUserId(); return $service->getDepartmentCustomerPricing($user, $departmentId, $customerUserId); }); }, [ limited_backoffice_service::PERMISSION_ACCESS => 'Access the limited backoffice', + limited_backoffice_service::PERMISSION_VIEW_CUSTOMER_PRICING => 'View limited backoffice department customer pricing', ]); $this->put('/limited-backoffice/departments/{departmentId}/customer-pricing', function () { $this->withLimitedBackoffice(function (limited_backoffice_service $service, $user): array { $this->requirePermission(limited_backoffice_service::PERMISSION_ACCESS); - $this->requirePermission(limited_backoffice_service::PERMISSION_MANAGE_PRICES); + $this->requirePermission(limited_backoffice_service::PERMISSION_MANAGE_CUSTOMER_PRICING); $departmentId = $this->routePositiveInt('departmentId'); $payload = $this->requestPayload(); $customerUserId = $this->payloadCustomerUserId($payload); @@ -66,7 +68,7 @@ class limitedBackofficeRoute }); }, [ limited_backoffice_service::PERMISSION_ACCESS => 'Access the limited backoffice', - limited_backoffice_service::PERMISSION_MANAGE_PRICES => 'Manage limited backoffice department prices', + limited_backoffice_service::PERMISSION_MANAGE_CUSTOMER_PRICING => 'Manage limited backoffice department customer pricing', ]); $this->get('/limited-backoffice/roles', function () { diff --git a/services/nginx/app/tests/Api/DepartmentCustomerPricingApiTest.php b/services/nginx/app/tests/Api/DepartmentCustomerPricingApiTest.php index e541ebcf..e684a73e 100644 --- a/services/nginx/app/tests/Api/DepartmentCustomerPricingApiTest.php +++ b/services/nginx/app/tests/Api/DepartmentCustomerPricingApiTest.php @@ -144,10 +144,25 @@ it('limits department customer pricing to assigned limited-backoffice department $otherFixture = department_customer_pricing_setup(['name' => 'Denied Scoped Customer Pricing Department']); $session = api_fixtures()->createUserSession([ limited_backoffice_service::PERMISSION_ACCESS, - limited_backoffice_service::PERMISSION_MANAGE_PRICES, + limited_backoffice_service::PERMISSION_VIEW_CUSTOMER_PRICING, + limited_backoffice_service::PERMISSION_MANAGE_CUSTOMER_PRICING, 'department_access_' . (int)$fixture['department']['id'], ]); + api_client() + ->get( + '/limited-backoffice/departments/' . (int)$fixture['department']['id'] . + '/customer-pricing?user_id=' . (int)$fixture['customer']['id'], + api_fixtures()->createUserSession([ + limited_backoffice_service::PERMISSION_ACCESS, + 'department_access_' . (int)$fixture['department']['id'], + ])['headers'] + ) + ->assertStatus(403) + ->assertEnvelope() + ->assertSuccess(false) + ->assertMissingPermissions([limited_backoffice_service::PERMISSION_VIEW_CUSTOMER_PRICING]); + api_client() ->get( '/limited-backoffice/departments/' . (int)$otherFixture['department']['id'] . @@ -159,6 +174,24 @@ it('limits department customer pricing to assigned limited-backoffice department ->assertSuccess(false) ->assertMissingPermissions(['department_access_' . (int)$otherFixture['department']['id']]); + api_client() + ->put( + '/limited-backoffice/departments/' . (int)$fixture['department']['id'] . '/customer-pricing', + [ + 'user_id' => $fixture['customer']['id'], + 'overrides' => [], + ], + api_fixtures()->createUserSession([ + limited_backoffice_service::PERMISSION_ACCESS, + limited_backoffice_service::PERMISSION_VIEW_CUSTOMER_PRICING, + 'department_access_' . (int)$fixture['department']['id'], + ])['headers'] + ) + ->assertStatus(403) + ->assertEnvelope() + ->assertSuccess(false) + ->assertMissingPermissions([limited_backoffice_service::PERMISSION_MANAGE_CUSTOMER_PRICING]); + $updated = api_client()->put( '/limited-backoffice/departments/' . (int)$fixture['department']['id'] . '/customer-pricing', [ diff --git a/services/nginx/app/tests/Api/LimitedBackofficeApiTest.php b/services/nginx/app/tests/Api/LimitedBackofficeApiTest.php index 01ed883a..db274849 100644 --- a/services/nginx/app/tests/Api/LimitedBackofficeApiTest.php +++ b/services/nginx/app/tests/Api/LimitedBackofficeApiTest.php @@ -11,6 +11,8 @@ function limited_backoffice_manager_session(array $departmentIds, array $extraPe $permissions = [ limited_backoffice_service::PERMISSION_ACCESS, limited_backoffice_service::PERMISSION_MANAGE_PRICES, + limited_backoffice_service::PERMISSION_VIEW_CUSTOMER_PRICING, + limited_backoffice_service::PERMISSION_MANAGE_CUSTOMER_PRICING, limited_backoffice_service::PERMISSION_MANAGE_EMPLOYEES, ]; foreach ($departmentIds as $departmentId) { @@ -77,6 +79,8 @@ function limited_backoffice_all_role_permissions(): array 'list_own_notifications', 'statistics_orders_new', 'statistics_bookings_new', + 'limited_backoffice_customer_pricing_view', + 'limited_backoffice_customer_pricing_manage', ]; } @@ -879,6 +883,8 @@ it('creates updates lists and deactivates scoped employees without exposing raw expect($departmentAdminGroups['limited_backoffice'] ?? null)->toBe([ 'open_limited_backoffice', 'manage_department_prices', + 'view_customer_pricing', + 'manage_customer_pricing', 'manage_employee_access', ]); expect($roles->body)->not->toContain('department_access_'); @@ -940,6 +946,8 @@ it('creates updates lists and deactivates scoped employees without exposing raw 'statistics_bookings_new', 'limited_backoffice_access', 'limited_backoffice_prices_manage', + 'limited_backoffice_customer_pricing_view', + 'limited_backoffice_customer_pricing_manage', 'limited_backoffice_employees_manage', ] as $rawPermission) { expect($rolePayloadStrings)->not->toContain($rawPermission); @@ -1654,6 +1662,13 @@ it('includes statistics_orders_new and statistics_bookings_new (Overblik) in all } }); +it('includes customer pricing permissions in the department admin limited backoffice role preset', function (): void { + $permissions = limited_backoffice_role_preset_permissions('department_admin'); + expect($permissions) + ->toContain(limited_backoffice_service::PERMISSION_VIEW_CUSTOMER_PRICING) + ->toContain(limited_backoffice_service::PERMISSION_MANAGE_CUSTOMER_PRICING); +}); + it('assigns list_departments to managed employees and enforces list_departments permission on GET /departments', function (): void { api_test_covers('GET /departments', 'limited backoffice employee'); diff --git a/services/nginx/app/tests/Api/RolesApiTest.php b/services/nginx/app/tests/Api/RolesApiTest.php index adf4365d..b588bd5c 100644 --- a/services/nginx/app/tests/Api/RolesApiTest.php +++ b/services/nginx/app/tests/Api/RolesApiTest.php @@ -34,6 +34,8 @@ it('lists limited backoffice permission templates for superuser role maintenance expect($templatesByKey['cashier']['permissions'] ?? [])->toContain('statistics_orders_new'); expect($templatesByKey['department_admin']['permissions'] ?? [])->toContain('limited_backoffice_access'); expect($templatesByKey['department_admin']['permissions'] ?? [])->toContain('limited_backoffice_prices_manage'); + expect($templatesByKey['department_admin']['permissions'] ?? [])->toContain('limited_backoffice_customer_pricing_view'); + expect($templatesByKey['department_admin']['permissions'] ?? [])->toContain('limited_backoffice_customer_pricing_manage'); expect($templatesByKey['department_admin']['permissions'] ?? [])->toContain('limited_backoffice_employees_manage'); });