From 94e44b688cce4ed1cce60c2a4358d22af063c024 Mon Sep 17 00:00:00 2001 From: Matt Blenkinsop Date: Tue, 13 Aug 2024 14:15:44 +0000 Subject: [PATCH] Bug 10190: Add a vue app for circulation triggers This patch adds a new vue based page for administering circulation triggers. This aims to replace the 'Overdue notice/status triggers' page available from Tools. The new page is available from Koha administration > Circulation triggers Sponsored-by: Glasgow Colleges Library Group --- admin/circulation_triggers.pl | 45 +++++++++++++ debian/templates/apache-shared-intranet.conf | 1 + .../prog/en/modules/admin/admin-home.tt | 4 ++ .../en/modules/admin/circulation_triggers.tt | 42 ++++++++++++ .../Admin/CirculationTriggers/Main.vue | 65 +++++++++++++++++++ .../vue/fetch/circulation-rules-api-client.js | 38 +++++++++++ .../vue/modules/admin/circulation_triggers.ts | 57 ++++++++++++++++ .../vue/routes/admin/circulation_triggers.js | 47 ++++++++++++++ .../prog/js/vue/stores/circulation-rules.js | 49 ++++++++++++++ webpack.config.js | 1 + 10 files changed, 349 insertions(+) create mode 100755 admin/circulation_triggers.pl create mode 100644 koha-tmpl/intranet-tmpl/prog/en/modules/admin/circulation_triggers.tt create mode 100644 koha-tmpl/intranet-tmpl/prog/js/vue/components/Admin/CirculationTriggers/Main.vue create mode 100644 koha-tmpl/intranet-tmpl/prog/js/vue/fetch/circulation-rules-api-client.js create mode 100644 koha-tmpl/intranet-tmpl/prog/js/vue/modules/admin/circulation_triggers.ts create mode 100644 koha-tmpl/intranet-tmpl/prog/js/vue/routes/admin/circulation_triggers.js create mode 100644 koha-tmpl/intranet-tmpl/prog/js/vue/stores/circulation-rules.js diff --git a/admin/circulation_triggers.pl b/admin/circulation_triggers.pl new file mode 100755 index 00000000000..a8db14f9981 --- /dev/null +++ b/admin/circulation_triggers.pl @@ -0,0 +1,45 @@ +#!/usr/bin/perl + +# Copyright 2024 PTFS Europe Ltd +# +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Koha is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Koha; if not, see . + +use Modern::Perl; + +use CGI qw ( -utf8 ); +use C4::Auth qw( get_template_and_user ); +use C4::Output qw( output_html_with_http_headers ); + +my $query = CGI->new; + +my ( $template, $loggedinuser, $cookie ) = get_template_and_user( + { + template_name => "admin/circulation_triggers.tt", + query => $query, + type => "intranet", + } +); + +my $letters = C4::Letters::GetLettersAvailableForALibrary( + { + branchcode => undef, + module => "circulation", + } +); + +$template->param( letters => $letters ); + +output_html_with_http_headers $query, $cookie, $template->output; diff --git a/debian/templates/apache-shared-intranet.conf b/debian/templates/apache-shared-intranet.conf index 49b742f868a..86dcb7bf0c6 100644 --- a/debian/templates/apache-shared-intranet.conf +++ b/debian/templates/apache-shared-intranet.conf @@ -24,6 +24,7 @@ RewriteRule ^/cgi-bin/koha/erm/.*$ /cgi-bin/koha/erm/erm.pl [PT] RewriteCond %{REQUEST_URI} !^/cgi-bin/koha/preservation/.*.pl$ RewriteRule ^/cgi-bin/koha/preservation/.*$ /cgi-bin/koha/preservation/home.pl [PT] RewriteRule ^/cgi-bin/koha/admin/record_sources(.*)?$ /cgi-bin/koha/admin/record_sources.pl$1 [PT] +RewriteRule ^/cgi-bin/koha/admin/circulation_triggers(.*)?$ /cgi-bin/koha/admin/circulation_triggers.pl$1 [PT] Alias "/api" "/usr/share/koha/api" 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 000f4f0956a..458b49a844d 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 @@ -99,6 +99,10 @@
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 ) %] +
Circulation triggers
+
Define triggers for circulation notices
+ [% END %] [% IF ( CAN_user_parameters_manage_patron_attributes ) %]
Patron attribute types
Define extended attributes (identifiers and statistical categories) for patron records
diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/circulation_triggers.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/circulation_triggers.tt new file mode 100644 index 00000000000..57ba3129856 --- /dev/null +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/circulation_triggers.tt @@ -0,0 +1,42 @@ +[% USE raw %] +[% USE To %] +[% USE Asset %] +[% USE KohaDates %] +[% USE TablesSettings %] +[% USE AuthorisedValues %] +[% SET footerjs = 1 %] +[% PROCESS 'i18n.inc' %] +[% INCLUDE 'doc-head-open.inc' %] +[% FILTER collapse %] + [% t("Circulation triggers") | html %] › + [% t("Administration") | html %] › + [% t("Koha") | html %] +[% END %] +[% INCLUDE 'doc-head-close.inc' %] + + + +[% WRAPPER 'header.inc' %] + [% INCLUDE 'prefs-admin-search.inc' %] +[% END %] + +
+ +[% MACRO jsinclude BLOCK %] + [% INCLUDE 'calendar.inc' %] + [% INCLUDE 'datatables.inc' %] + [% INCLUDE 'columns_settings.inc' %] + [% INCLUDE 'js-patron-format.inc' %] + [% INCLUDE 'js-date-format.inc' %] + + + + [% Asset.js("js/vue/dist/admin/circulation_triggers.js") | $raw %] + +[% END %] + +[% INCLUDE 'intranet-bottom.inc' %] 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 new file mode 100644 index 00000000000..2010cedbc62 --- /dev/null +++ b/koha-tmpl/intranet-tmpl/prog/js/vue/components/Admin/CirculationTriggers/Main.vue @@ -0,0 +1,65 @@ + + + + + diff --git a/koha-tmpl/intranet-tmpl/prog/js/vue/fetch/circulation-rules-api-client.js b/koha-tmpl/intranet-tmpl/prog/js/vue/fetch/circulation-rules-api-client.js new file mode 100644 index 00000000000..e6e749ffcea --- /dev/null +++ b/koha-tmpl/intranet-tmpl/prog/js/vue/fetch/circulation-rules-api-client.js @@ -0,0 +1,38 @@ +import HttpClient from "./http-client"; + +export class CircRuleAPIClient extends HttpClient { + constructor() { + super({ + baseURL: "/api/v1/", + }); + } + + get circRules() { + return { + getAll: (query, params, headers) => + this.getAll({ + endpoint: "circulation_rules", + query, + params, + headers, + }), + update: rule => + this.put({ + endpoint: "circulation_rules", + body: rule, + }), + count: (query = {}) => + this.count({ + endpoint: + "circulation_rules?" + + new URLSearchParams({ + _page: 1, + _per_page: 1, + ...(query && { q: JSON.stringify(query) }), + }), + }), + }; + } +} + +export default CircRuleAPIClient; 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 new file mode 100644 index 00000000000..f5e6a50c50b --- /dev/null +++ b/koha-tmpl/intranet-tmpl/prog/js/vue/modules/admin/circulation_triggers.ts @@ -0,0 +1,57 @@ +import { createApp } from "vue"; +import { createPinia } from "pinia"; +import { createWebHistory, createRouter } from "vue-router"; + +import { library } from "@fortawesome/fontawesome-svg-core"; +import { + faPlus, + faMinus, + faPencil, + faTrash, + faSpinner, +} from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome"; +import vSelect from "vue-select"; +import { useNavigationStore } from "../../stores/navigation"; +import { useMainStore } from "../../stores/main"; +import { useCircRulesStore } from "../../stores/circulation-rules"; +import routesDef from "../../routes/admin/circulation_triggers"; + +library.add(faPlus, faMinus, faPencil, faTrash, faSpinner); + +const pinia = createPinia(); +const navigationStore = useNavigationStore(pinia); +const mainStore = useMainStore(pinia); +const circRulesStore = useCircRulesStore(pinia); +const { removeMessages } = mainStore; +const { setRoutes } = navigationStore; +const routes = setRoutes(routesDef); + +const router = createRouter({ + history: createWebHistory(), + linkExactActiveClass: "current", + routes, +}); + +import App from "../../components/Admin/CirculationTriggers/Main.vue"; +import i18n from "../../i18n"; + +const app = createApp(App); + +const rootComponent = app + .use(i18n) + .use(pinia) + .use(router) + .component("font-awesome-icon", FontAwesomeIcon) + .component("v-select", vSelect); + +app.config.unwrapInjectedRef = true; +app.provide("mainStore", mainStore); +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 +}); diff --git a/koha-tmpl/intranet-tmpl/prog/js/vue/routes/admin/circulation_triggers.js b/koha-tmpl/intranet-tmpl/prog/js/vue/routes/admin/circulation_triggers.js new file mode 100644 index 00000000000..9619c176ac5 --- /dev/null +++ b/koha-tmpl/intranet-tmpl/prog/js/vue/routes/admin/circulation_triggers.js @@ -0,0 +1,47 @@ +import { markRaw } from "vue"; +import { $__ } from "../../i18n"; + +import CirculationTriggersList from "../../components/Admin/CirculationTriggers/CirculationTriggersList.vue"; +import CirculationTriggersFormAdd from "../../components/Admin/CirculationTriggers/CirculationTriggersFormAdd.vue"; + +export default { + title: $__("Administration"), + path: "", + href: "/cgi-bin/koha/admin/admin-home.pl", + is_base: true, + is_default: true, + children: [ + { + title: $__("Circulation triggers"), + path: "/cgi-bin/koha/admin/circulation_triggers", + children: [ + { + path: "", + name: "CirculationTriggersList", + component: markRaw(CirculationTriggersList), + title: $__("Home"), + children: [ + { + path: "add", + name: "CirculationTriggersFormAdd", + component: markRaw(CirculationTriggersFormAdd), + title: $__("Add new trigger"), + meta: { + showModal: true, + }, + }, + { + path: "edit", + name: "CirculationTriggersFormEdit", + component: markRaw(CirculationTriggersFormAdd), + title: $__("Edit trigger"), + meta: { + showModal: true, + }, + }, + ], + }, + ], + }, + ], +}; 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 new file mode 100644 index 00000000000..da89e140cb8 --- /dev/null +++ b/koha-tmpl/intranet-tmpl/prog/js/vue/stores/circulation-rules.js @@ -0,0 +1,49 @@ +import { defineStore } from "pinia"; + +export const useCircRulesStore = defineStore("circRules", { + state: () => ({ + letters: [], + }), + actions: { + splitCircRulesByTriggerNumber(rules) { + const ruleSuffixes = ["delay", "notice", "mtt", "restrict"]; + let numberOfTabs = 1; + const rulesPerTrigger = rules.reduce((acc, rule) => { + const regex = /overdue_(\d+)_delay/g; + const numberOfTriggers = Object.keys(rule).filter(key => + regex.test(key) + ).length; + numberOfTabs = this.setNumberOfTabs( + numberOfTriggers, + numberOfTabs + ); + const triggerNumbers = Array.from( + { length: numberOfTriggers }, + (_, i) => i + 1 + ); + triggerNumbers.forEach(i => { + const ruleCopy = JSON.parse(JSON.stringify(rule)); + const rulesToDelete = triggerNumbers.filter( + num => num !== i + ); + ruleSuffixes.forEach(suffix => { + rulesToDelete.forEach(number => { + delete ruleCopy[`overdue_${number}_${suffix}`]; + }); + }); + ruleCopy.triggerNumber = i; + acc.push(ruleCopy); + }); + return acc; + }, []); + + return { numberOfTabs, rulesPerTrigger }; + }, + setNumberOfTabs(triggerCount, tabCount) { + if (triggerCount > tabCount) { + return Array.from({ length: triggerCount }, (_, i) => i + 1); + } + return tabCount; + }, + }, +}); diff --git a/webpack.config.js b/webpack.config.js index ae87653c244..1b52cbae448 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -8,6 +8,7 @@ module.exports = { erm: "./koha-tmpl/intranet-tmpl/prog/js/vue/modules/erm.ts", preservation: "./koha-tmpl/intranet-tmpl/prog/js/vue/modules/preservation.ts", "admin/record_sources": "./koha-tmpl/intranet-tmpl/prog/js/vue/modules/admin/record_sources.ts", + "admin/circulation_triggers": "./koha-tmpl/intranet-tmpl/prog/js/vue/modules/admin/circulation_triggers.ts", }, output: { filename: "[name].js", -- 2.46.0