From 27fdfafa229f6ca355de11bd9367ae26aba8f23e Mon Sep 17 00:00:00 2001 From: Chloe Zermatten Date: Fri, 30 Jan 2026 10:46:58 +0000 Subject: [PATCH] Bug 10190: feat: permissions - add a config API endpoint to Circulations Triggers so permissions can be fetched - update the permissions in the Vue App so that manage_circ_rules is required - update the permissions in the Circulation Rules API so that manage_circ_rules is required for PUT operations - update the circulation-rules store to so that it is compatible with recently intorduced compostables - implement a redirect into the route so that permission requirements are honoured --- Koha/REST/V1/CirculationRules.pm | 21 +++++++++ .../definitions/circulation_rules_config.yaml | 7 +++ .../paths/circulation_rules_config.yaml | 39 ++++++++++++++++ api/v1/swagger/swagger.yaml | 7 +++ .../prog/en/modules/admin/admin-home.tt | 2 +- .../js/fetch/circulation-rules-api-client.js | 9 ++++ .../CirculationTriggersList.vue | 1 - .../Admin/CirculationTriggers/Main.vue | 1 + .../vue/modules/admin/circulation_triggers.ts | 30 ++++++++++-- .../prog/js/vue/stores/circulation-rules.js | 46 ++++++++++++++----- 10 files changed, 146 insertions(+), 17 deletions(-) create mode 100644 api/v1/swagger/definitions/circulation_rules_config.yaml create mode 100644 api/v1/swagger/paths/circulation_rules_config.yaml diff --git a/Koha/REST/V1/CirculationRules.pm b/Koha/REST/V1/CirculationRules.pm index 7c2935cfb33..23f77a0681d 100644 --- a/Koha/REST/V1/CirculationRules.pm +++ b/Koha/REST/V1/CirculationRules.pm @@ -316,4 +316,25 @@ sub _all_kinds { return \@all_valid_kinds; } +=head3 config + +Return the configuration options needed for the Circulations Triggers Vue app + +=cut + +sub config { + my $c = shift->openapi->valid_input or return; + + my $patron = $c->stash('koha.user'); + my $userflags = C4::Auth::haspermission( $patron->userid ); + my $permissions = Koha::Auth::Permissions->get_authz_from_flags( { flags => $userflags } ); + + return $c->render( + status => 200, + openapi => { + permissions => $permissions, + }, + ); +} + 1; diff --git a/api/v1/swagger/definitions/circulation_rules_config.yaml b/api/v1/swagger/definitions/circulation_rules_config.yaml new file mode 100644 index 00000000000..f9e71160cd1 --- /dev/null +++ b/api/v1/swagger/definitions/circulation_rules_config.yaml @@ -0,0 +1,7 @@ +--- +type: object +properties: + permissions: + type: object + description: List of permissions for the user +additionalProperties: false diff --git a/api/v1/swagger/paths/circulation_rules_config.yaml b/api/v1/swagger/paths/circulation_rules_config.yaml new file mode 100644 index 00000000000..a4624ff8111 --- /dev/null +++ b/api/v1/swagger/paths/circulation_rules_config.yaml @@ -0,0 +1,39 @@ +--- +/circulation_rules/config: + get: + x-mojo-to: CirculationRules#config + operationId: getCirculationRulesConfig + description: This resource returns a list of options needed for the Circulation Triggers Vue app + summary: get the CirculationRules config + tags: + - circulation_rules_config + produces: + - application/json + responses: + 200: + description: The Circulation Rules config + schema: + $ref: "../swagger.yaml#/definitions/circulation_rules_config" + 400: + description: Bad request + schema: + $ref: "../swagger.yaml#/definitions/error" + 403: + description: Access forbidden + schema: + $ref: "../swagger.yaml#/definitions/error" + 500: + description: | + Internal server error. Possible `error_code` attribute values: + + * `internal_server_error` + schema: + $ref: "../swagger.yaml#/definitions/error" + 503: + description: Under maintenance + schema: + $ref: "../swagger.yaml#/definitions/error" + x-koha-authorization: + permissions: + - acquisition: '*' + - erm: 1 diff --git a/api/v1/swagger/swagger.yaml b/api/v1/swagger/swagger.yaml index 9a9b7af7565..cecd4b26b7b 100644 --- a/api/v1/swagger/swagger.yaml +++ b/api/v1/swagger/swagger.yaml @@ -16,6 +16,8 @@ definitions: $ref: ./definitions/authorised_value_category.yaml circulation_rules: $ref: ./definitions/circulation_rules.yaml + circulation_rules_config: + $ref: ./definitions/circulation_rules_config.yaml identity_provider: $ref: ./definitions/identity_provider.yaml identity_provider_domain: @@ -323,6 +325,8 @@ paths: $ref: ./paths/circulation_rules.yaml#/~1circulation_rules /circulation_rules/kinds: $ref: ./paths/circulation_rules.yaml#/~1circulation_rules~1kinds + /circulation_rules/config: + $ref: ./paths/circulation_rules_config.yaml#/~1circulation_rules~1config /cities: $ref: ./paths/cities.yaml#/~1cities "/cities/{city_id}": @@ -1239,6 +1243,9 @@ tags: - description: "Manage circulation rules\n" name: circulation_rules x-displayName: Circulation rules + - description: "Retrieve configuration relevant to circulation rules\n" + name: circulation_rules_config + x-displayName: Circulation rules config - description: "Manage cities\n" name: cities x-displayName: Cities diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/admin-home.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/admin-home.tt index b6dc0040f07..7dcb68d22f0 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/admin-home.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/admin-home.tt @@ -96,7 +96,7 @@
Circulation and fine rules
Define circulation and fine rules for combinations of libraries, patron categories, and item types
[% END %] - [% IF ( CAN_user_tools_edit_notice_status_triggers ) %] + [% IF ( CAN_user_parameters_manage_circ_rules ) %]
Circulation triggers
Define triggers for circulation notices
[% END %] diff --git a/koha-tmpl/intranet-tmpl/prog/js/fetch/circulation-rules-api-client.js b/koha-tmpl/intranet-tmpl/prog/js/fetch/circulation-rules-api-client.js index ecf033db6fd..17b85bfa361 100644 --- a/koha-tmpl/intranet-tmpl/prog/js/fetch/circulation-rules-api-client.js +++ b/koha-tmpl/intranet-tmpl/prog/js/fetch/circulation-rules-api-client.js @@ -5,6 +5,15 @@ export class CircRuleAPIClient { }); } + get config() { + return { + getAll: () => + this.httpClient.get({ + endpoint: "/config", + }), + }; + } + get circ_rules() { return { getAll: (query, params, headers) => diff --git a/koha-tmpl/intranet-tmpl/prog/js/vue/components/Admin/CirculationTriggers/CirculationTriggersList.vue b/koha-tmpl/intranet-tmpl/prog/js/vue/components/Admin/CirculationTriggers/CirculationTriggersList.vue index 3060373d563..4f52ad12663 100644 --- a/koha-tmpl/intranet-tmpl/prog/js/vue/components/Admin/CirculationTriggers/CirculationTriggersList.vue +++ b/koha-tmpl/intranet-tmpl/prog/js/vue/components/Admin/CirculationTriggers/CirculationTriggersList.vue @@ -288,7 +288,6 @@ import { storeToRefs } from "pinia"; export default { setup() { const circRulesStore = inject("circRulesStore"); - circRulesStore.init(); const { updateTriggerCount, setAllRawRuleSets, diff --git a/koha-tmpl/intranet-tmpl/prog/js/vue/components/Admin/CirculationTriggers/Main.vue b/koha-tmpl/intranet-tmpl/prog/js/vue/components/Admin/CirculationTriggers/Main.vue index 9a05c2d347c..8c1b12f4527 100644 --- a/koha-tmpl/intranet-tmpl/prog/js/vue/components/Admin/CirculationTriggers/Main.vue +++ b/koha-tmpl/intranet-tmpl/prog/js/vue/components/Admin/CirculationTriggers/Main.vue @@ -29,6 +29,7 @@ import { inject } from "vue"; export default { setup() { const circRulesStore = inject("circRulesStore"); + circRulesStore.init(); letters.unshift({ name: "No letter", code: "", diff --git a/koha-tmpl/intranet-tmpl/prog/js/vue/modules/admin/circulation_triggers.ts b/koha-tmpl/intranet-tmpl/prog/js/vue/modules/admin/circulation_triggers.ts index f5e6a50c50b..10f31c85a23 100644 --- a/koha-tmpl/intranet-tmpl/prog/js/vue/modules/admin/circulation_triggers.ts +++ b/koha-tmpl/intranet-tmpl/prog/js/vue/modules/admin/circulation_triggers.ts @@ -51,7 +51,31 @@ app.provide("navigationStore", navigationStore); app.provide("circRulesStore", circRulesStore); app.mount("#__app"); -router.beforeEach(to => { - navigationStore.$patch({ current: to.matched, params: to.params || {} }); - removeMessages(); // This will actually flag the messages as displayed already +router.beforeEach((to, from, next) => { + circRulesStore + .loadUserPermissions() + .then(() => { + if ( + !circRulesStore.isUserPermitted( + "CAN_user_parameters_manage_circ_rules" + ) + ) { + navigationStore.$patch({ + navigationBlocked: true, + currentPermission: null, + }); + removeMessages(); // This will actually flag the messages as displayed already + window.location.href = "/cgi-bin/koha/mainpage.pl"; + return; + } + navigationStore.$patch({ + current: to.matched, + params: to.params || {}, + }); + removeMessages(); // This will actually flag the messages as displayed already + next(); + }) + .catch(() => { + window.location.href = "/cgi-bin/koha/mainpage.pl"; + }); }); diff --git a/koha-tmpl/intranet-tmpl/prog/js/vue/stores/circulation-rules.js b/koha-tmpl/intranet-tmpl/prog/js/vue/stores/circulation-rules.js index cf1004f2e35..56f03894135 100644 --- a/koha-tmpl/intranet-tmpl/prog/js/vue/stores/circulation-rules.js +++ b/koha-tmpl/intranet-tmpl/prog/js/vue/stores/circulation-rules.js @@ -2,16 +2,19 @@ import { defineStore } from "pinia"; import { $__ } from "../i18n"; import { APIClient } from "../fetch/api-client.js"; import { isEqual } from "lodash"; +import { permissionsActions } from "../composables/permissions"; +import { reactive, toRefs } from "vue"; -export const useCircRulesStore = defineStore("circRules", { - // NOTES ON RULE SETS TYPES - // exhaustive: includes 'pure fallback' rules sets for contexts that no rules match. - // Format: [{overdue_X_: {value: mixed: isFallback: bool}}] - // effective: includes sets only for contexts for which one or more rule exists in the db. These sets will include fallbacks. - // Format: [{overdue_X_: }} - // raw: includes only the exact sets as they are found in the db - // Format: [{overdue_X_: }}] - state: () => ({ +// NOTES ON RULE SETS TYPES +// exhaustive: includes 'pure fallback' rules sets for contexts that no rules match. +// Format: [{overdue_X_: {value: mixed: isFallback: bool}}] +// effective: includes sets only for contexts for which one or more rule exists in the db. These sets will include fallbacks. +// Format: [{overdue_X_: }} +// raw: includes only the exact sets as they are found in the db +// Format: [{overdue_X_: }}] + +export const useCircRulesStore = defineStore("circRules", () => { + const store = reactive({ // context currentLibraryId: "*", currentPatronCategoryId: null, @@ -21,6 +24,7 @@ export const useCircRulesStore = defineStore("circRules", { itemTypes: [], libraries: [], patronCategories: [], + userPermissions: null, letters: [], ruleSuffixes: ["delay", "notice", "mtt", "restrict", "has_rules"], transportTypes: [ @@ -36,14 +40,21 @@ export const useCircRulesStore = defineStore("circRules", { allExhaustiveEffectiveRuleSets: [], // main data set for display all applied rules for current library currentAndDefaultRawRuleSets: [], // data set to identify effective rules from (combines allDefaultLibraryRawRuleSets and allCurrentLibraryRawRuleSets) storeInitialized: false, - }), - actions: { + }); + + const actions = { // controllers async init() { await this.getItemTypes(); await this.getLibraries(); await this.getPatronCategories(); }, + async loadUserPermissions() { + if (this.userPermissions !== null) { + return; + } + await this.getConfigurationOptions(); + }, // utilities formatTriggerSpecificRuleSetForDisplay( context, @@ -403,6 +414,11 @@ export const useCircRulesStore = defineStore("circRules", { throw e; } }, + async getConfigurationOptions() { + const client = APIClient.circRule; + const { permissions } = await client.config.getAll(); + this.userPermissions = permissions; + }, async getItemTypes() { const client = APIClient.item; let itemTypes = []; @@ -485,5 +501,11 @@ export const useCircRulesStore = defineStore("circRules", { //TODO: handle e } }, - }, + ...permissionsActions(store), + }; + + return { + ...toRefs(store), + ...actions, + }; }); -- 2.39.5