Bugzilla – Attachment 172594 Details for
Bug 10190
Overdue notice triggers based on item type
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 10190: Add components to list/add and edit circulation triggers
Bug-10190-Add-components-to-listadd-and-edit-circu.patch (text/plain), 42.46 KB, created by
Martin Renvoize (ashimema)
on 2024-10-10 14:39:32 UTC
(
hide
)
Description:
Bug 10190: Add components to list/add and edit circulation triggers
Filename:
MIME Type:
Creator:
Martin Renvoize (ashimema)
Created:
2024-10-10 14:39:32 UTC
Size:
42.46 KB
patch
obsolete
>From 336974d6e61979ba40711cace611952a5f2fc023 Mon Sep 17 00:00:00 2001 >From: Matt Blenkinsop <matt.blenkinsop@ptfs-europe.com> >Date: Tue, 13 Aug 2024 14:16:33 +0000 >Subject: [PATCH] Bug 10190: Add components to list/add and edit circulation > triggers > >These new components are used by the new 'Circulation triggers' >administration page to display existing trigger rules and add the >ability to add new rules and edit existing ones. > >Sponsored-by: Glasgow Colleges Library Group >--- > .../CirculationTriggersFormAdd.vue | 520 ++++++++++++++++++ > .../CirculationTriggersList.vue | 258 +++++++++ > .../CirculationTriggers/TriggerContext.vue | 50 ++ > .../CirculationTriggers/TriggersTable.vue | 266 +++++++++ > 4 files changed, 1094 insertions(+) > create mode 100644 koha-tmpl/intranet-tmpl/prog/js/vue/components/Admin/CirculationTriggers/CirculationTriggersFormAdd.vue > create mode 100644 koha-tmpl/intranet-tmpl/prog/js/vue/components/Admin/CirculationTriggers/CirculationTriggersList.vue > create mode 100644 koha-tmpl/intranet-tmpl/prog/js/vue/components/Admin/CirculationTriggers/TriggerContext.vue > create mode 100644 koha-tmpl/intranet-tmpl/prog/js/vue/components/Admin/CirculationTriggers/TriggersTable.vue > >diff --git a/koha-tmpl/intranet-tmpl/prog/js/vue/components/Admin/CirculationTriggers/CirculationTriggersFormAdd.vue b/koha-tmpl/intranet-tmpl/prog/js/vue/components/Admin/CirculationTriggers/CirculationTriggersFormAdd.vue >new file mode 100644 >index 00000000000..ac331a26c00 >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/js/vue/components/Admin/CirculationTriggers/CirculationTriggersFormAdd.vue >@@ -0,0 +1,520 @@ >+<template> >+ <router-link :to="{ name: 'CirculationTriggersList' }" class="close_modal"> >+ <i class="fa fa-fw fa-close"></i> >+ </router-link> >+ <div v-if="initialized" class="modal-content"> >+ <form @submit="addCircRule($event)"> >+ <div class="modal-header"> >+ <h1 v-if="!editMode">{{ $__("Add circulation trigger") }}</h1> >+ <h1 v-else>{{ $__("Edit circulation trigger") }}</h1> >+ </div> >+ <div class="modal-body"> >+ <div class="page-section bg-info" v-if="circRules.length"> >+ <h2>{{ $__("Trigger context") }}</h2> >+ <TriggerContext :ruleInfo="ruleInfo" /> >+ <h2>{{ $__("Existing rules") }}</h2> >+ <p>{{ $__("Notice") }} {{ " " + newTriggerNumber - 1 }}</p> >+ <TriggersTable >+ :circRules="circRules" >+ :triggerNumber="newTriggerNumber - 1" >+ :modal="true" >+ :ruleBeingEdited="ruleBeingEdited" >+ /> >+ </div> >+ <fieldset class="rows"> >+ <ol> >+ <li> >+ <label for="library_id" class="required" >+ >{{ $__("Library") }}:</label >+ > >+ <v-select >+ id="library_id" >+ v-model="circRuleTrigger.library_id" >+ label="name" >+ :reduce="lib => lib.library_id" >+ :options="libraries" >+ @update:modelValue="handleContextChange($event)" >+ :disabled="editMode ? true : false" >+ > >+ <template #search="{ attributes, events }"> >+ <input >+ :required="!circRuleTrigger.library_id" >+ class="vs__search" >+ v-bind="attributes" >+ v-on="events" >+ /> >+ </template> >+ </v-select> >+ <span class="required">{{ $__("Required") }}</span> >+ </li> >+ <li> >+ <label for="patron_category_id" class="required" >+ >{{ $__("Patron category") }}:</label >+ > >+ <v-select >+ id="patron_category_id" >+ v-model="circRuleTrigger.patron_category_id" >+ label="name" >+ :reduce="cat => cat.patron_category_id" >+ :options="categories" >+ @update:modelValue="handleContextChange($event)" >+ :disabled="editMode ? true : false" >+ > >+ <template #search="{ attributes, events }"> >+ <input >+ :required=" >+ !circRuleTrigger.patron_category_id >+ " >+ class="vs__search" >+ v-bind="attributes" >+ v-on="events" >+ /> >+ </template> >+ </v-select> >+ <span class="required">{{ $__("Required") }}</span> >+ </li> >+ <li> >+ <label for="item_type_id" class="required" >+ >{{ $__("Item type") }}:</label >+ > >+ <v-select >+ id="item_type_id" >+ v-model="circRuleTrigger.item_type_id" >+ label="description" >+ :reduce="type => type.item_type_id" >+ :options="itemTypes" >+ @update:modelValue="handleContextChange($event)" >+ :disabled="editMode ? true : false" >+ > >+ <template #search="{ attributes, events }"> >+ <input >+ :required=" >+ !circRuleTrigger.item_type_id >+ " >+ class="vs__search" >+ v-bind="attributes" >+ v-on="events" >+ /> >+ </template> >+ </v-select> >+ <span class="required">{{ $__("Required") }}</span> >+ </li> >+ <li> >+ <label for="overdue_delay" >+ >{{ $__("Delay") }}: >+ </label> >+ <input >+ id="overdue_delay" >+ v-model="circRuleTrigger.delay" >+ type="number" >+ /> >+ </li> >+ <li> >+ <label for="letter_code" >+ >{{ $__("Letter") }}:</label >+ > >+ <v-select >+ id="letter_code" >+ v-model="circRuleTrigger.notice" >+ label="name" >+ :reduce="type => type.code" >+ :options="letters" >+ > >+ <template #search="{ attributes, events }"> >+ <input >+ class="vs__search" >+ v-bind="attributes" >+ v-on="events" >+ /> >+ </template> >+ </v-select> >+ </li> >+ <li> >+ <label for="mtt" >+ >{{ $__("Transport type(s)") }}:</label >+ > >+ <v-select >+ id="mtt" >+ v-model="circRuleTrigger.mtt" >+ label="name" >+ :reduce="type => type.code" >+ :options="mtts" >+ multiple >+ > >+ <template #search="{ attributes, events }"> >+ <input >+ class="vs__search" >+ v-bind="attributes" >+ v-on="events" >+ /> >+ </template> >+ </v-select> >+ </li> >+ <li> >+ <label for="restricts" >+ >{{ $__("Restricts checkouts") }}:</label >+ > >+ <input >+ type="checkbox" >+ id="restricts" >+ :checked="false" >+ true-value="1" >+ false-value="0" >+ v-model="circRuleTrigger.restrict" >+ /> >+ </li> >+ </ol> >+ </fieldset> >+ </div> >+ <div class="modal-footer"> >+ <ButtonSubmit /> >+ <router-link >+ :to="{ >+ name: 'CirculationTriggersList', >+ }" >+ >Cancel</router-link >+ > >+ </div> >+ </form> >+ </div> >+ <div v-else> >+ <p>{{ $__("Loading...") }}</p> >+ </div> >+</template> >+ >+<script> >+import { APIClient } from "../../../fetch/api-client.js" >+import TriggersTable from "./TriggersTable.vue" >+import { inject } from "vue" >+import { storeToRefs } from "pinia" >+import ButtonSubmit from "../../ButtonSubmit.vue" >+import TriggerContext from "./TriggerContext.vue" >+ >+export default { >+ setup() { >+ const circRulesStore = inject("circRulesStore") >+ const { splitCircRulesByTriggerNumber } = circRulesStore >+ const { letters } = storeToRefs(circRulesStore) >+ >+ return { >+ splitCircRulesByTriggerNumber, >+ letters, >+ } >+ }, >+ data() { >+ return { >+ initialized: false, >+ libraries: null, >+ categories: null, >+ itemTypes: null, >+ circRules: [], >+ circRuleTrigger: { >+ item_type_id: "*", >+ library_id: "*", >+ patron_category_id: "*", >+ delay: null, >+ notice: null, >+ mtt: null, >+ restrict: "0", >+ }, >+ effectiveRule: { >+ item_type_id: "*", >+ library_id: "*", >+ patron_category_id: "*", >+ delay: null, >+ notice: null, >+ mtt: null, >+ restrict: "0", >+ }, >+ newTriggerNumber: 1, >+ mtts: [ >+ { code: "email", name: "Email" }, >+ { code: "sms", name: "SMS" }, >+ { code: "print", name: "Print" }, >+ ], >+ ruleInfo: { >+ issuelength: null, >+ decreaseloanholds: null, >+ fine: null, >+ chargeperiod: null, >+ lengthunit: null, >+ }, >+ editMode: false, >+ ruleBeingEdited: null, >+ } >+ }, >+ beforeRouteEnter(to, from, next) { >+ next(vm => { >+ vm.getLibraries().then(() => >+ vm.getCategories().then(() => >+ vm.getItemTypes().then(() => { >+ const { query } = to >+ vm.checkForExistingRules(query).then( >+ () => (vm.initialized = true) >+ ) >+ }) >+ ) >+ ) >+ }) >+ }, >+ methods: { >+ async addCircRule(e) { >+ e.preventDefault() >+ >+ const context = { >+ library_id: this.circRuleTrigger.library_id || "*", >+ item_type_id: this.circRuleTrigger.item_type_id || "*", >+ patron_category_id: >+ this.circRuleTrigger.patron_category_id || "*", >+ } >+ >+ const delay = >+ this.circRuleTrigger.delay === this.effectiveRule.delay >+ ? null >+ : this.circRuleTrigger.delay >+ const notice = >+ this.circRuleTrigger.notice === this.effectiveRule.notice >+ ? null >+ : this.circRuleTrigger.notice >+ const restrict = >+ this.circRuleTrigger.restrict === this.effectiveRule.restrict >+ ? null >+ : this.circRuleTrigger.restrict >+ const mtt = >+ this.circRuleTrigger.mtt.join(",") === >+ this.effectiveRule.mtt.join(",") >+ ? null >+ : this.circRuleTrigger.mtt.join(",") >+ >+ const circRule = { >+ context, >+ } >+ circRule[`overdue_${this.newTriggerNumber}_delay`] = delay >+ circRule[`overdue_${this.newTriggerNumber}_notice`] = notice >+ circRule[`overdue_${this.newTriggerNumber}_restrict`] = restrict >+ circRule[`overdue_${this.newTriggerNumber}_mtt`] = mtt >+ >+ if (this.editMode) { >+ Object.keys(circRule).forEach(key => { >+ if (key === "context") return >+ if (!circRule[key]) delete circRule[key] >+ }) >+ } >+ >+ const client = APIClient.circRule >+ await client.circRules.update(circRule).then( >+ () => { >+ this.$router >+ .push({ name: "CirculationTriggersList" }) >+ .then(() => this.$router.go(0)) >+ }, >+ error => {} >+ ) >+ }, >+ async getLibraries() { >+ const client = APIClient.library >+ await client.libraries.getAll().then( >+ libraries => { >+ libraries.unshift({ >+ library_id: "*", >+ name: "All libraries", >+ }) >+ this.libraries = libraries >+ }, >+ error => {} >+ ) >+ }, >+ async getCategories() { >+ const client = APIClient.patron >+ await client.patronCategories.getAll().then( >+ categories => { >+ categories.unshift({ >+ patron_category_id: "*", >+ name: "All categories", >+ }) >+ this.categories = categories >+ }, >+ error => {} >+ ) >+ }, >+ async getItemTypes() { >+ const client = APIClient.item >+ await client.itemTypes.getAll().then( >+ types => { >+ types.unshift({ >+ item_type_id: "*", >+ description: "All item types", >+ }) >+ this.itemTypes = types >+ }, >+ error => {} >+ ) >+ }, >+ async handleContextChange() { >+ await this.checkForExistingRules() >+ }, >+ async checkForExistingRules(routeParams) { >+ // We always pass library_id so we need to check for the existence of either item type or patron category >+ const editMode = routeParams && routeParams.triggerNumber >+ if (editMode) { >+ this.editMode = editMode >+ this.ruleBeingEdited = routeParams.triggerNumber >+ } >+ const library_id = >+ routeParams && routeParams.library_id >+ ? routeParams.library_id >+ : this.circRuleTrigger.library_id || "*" >+ const item_type_id = >+ routeParams && routeParams.item_type_id >+ ? routeParams.item_type_id >+ : this.circRuleTrigger.item_type_id || "*" >+ const patron_category_id = >+ routeParams && routeParams.patron_category_id >+ ? routeParams.patron_category_id >+ : this.circRuleTrigger.patron_category_id || "*" >+ const params = { >+ library_id, >+ item_type_id, >+ patron_category_id, >+ } >+ >+ const client = APIClient.circRule >+ await client.circRules.getAll({}, params).then( >+ rules => { >+ if (rules.length === 0) { >+ this.newTriggerNumber = 1 >+ } else { >+ const { rulesPerTrigger } = >+ this.splitCircRulesByTriggerNumber(rules) >+ this.circRules = rulesPerTrigger >+ this.ruleInfo = { >+ issuelength: rules[0].issuelength, >+ decreaseloanholds: rules[0].decreaseloanholds, >+ fine: rules[0].fine, >+ chargeperiod: rules[0].chargeperiod, >+ lengthunit: rules[0].lengthunit, >+ } >+ } >+ }, >+ error => {} >+ ) >+ >+ params.effective = false >+ await client.circRules.getAll({}, params).then( >+ rules => { >+ if (rules.length === 0) { >+ this.newTriggerNumber = 1 >+ >+ this.assignTriggerValues( >+ "circRuleTrigger", >+ this.circRules, >+ this.newTriggerNumber, >+ params >+ ) >+ this.assignTriggerValues( >+ "effectiveRule", >+ this.circRules, >+ this.newTriggerNumber, >+ params >+ ) >+ } else { >+ const regex = /overdue_(\d+)_delay/g >+ const numberOfTriggers = Object.keys(rules[0]).filter( >+ key => regex.test(key) && rules[0][key] !== null >+ ).length >+ this.newTriggerNumber = editMode >+ ? routeParams.triggerNumber >+ : numberOfTriggers + 1 >+ const { rulesPerTrigger } = >+ this.splitCircRulesByTriggerNumber(rules) >+ const triggerToDisplay = editMode >+ ? routeParams.triggerNumber >+ : numberOfTriggers >+ this.assignTriggerValues( >+ "circRuleTrigger", >+ rulesPerTrigger, >+ triggerToDisplay >+ ) >+ this.assignTriggerValues( >+ "effectiveRule", >+ rulesPerTrigger, >+ triggerToDisplay >+ ) >+ } >+ }, >+ error => {} >+ ) >+ }, >+ assignTriggerValues(prop, rules, triggerNumber, context = null) { >+ this[prop] = { >+ item_type_id: context >+ ? context.item_type_id >+ : rules[triggerNumber - 1].context.item_type_id || "*", >+ library_id: context >+ ? context.library_id >+ : rules[triggerNumber - 1].context.library_id || "*", >+ patron_category_id: context >+ ? context.patron_category_id >+ : rules[triggerNumber - 1].context.patron_category_id || >+ "*", >+ delay: rules[triggerNumber - 1][ >+ `overdue_${triggerNumber}_delay` >+ ], >+ notice: rules[triggerNumber - 1][ >+ `overdue_${triggerNumber}_notice` >+ ], >+ mtt: rules[triggerNumber - 1][`overdue_${triggerNumber}_mtt`] >+ ? rules[triggerNumber - 1][ >+ `overdue_${triggerNumber}_mtt` >+ ].split(",") >+ : [], >+ restrict: >+ rules[triggerNumber - 1][ >+ `overdue_${triggerNumber}_restrict` >+ ], >+ } >+ }, >+ }, >+ watch: { >+ $route: { >+ immediate: true, >+ handler: function (newVal, oldVal) { >+ if ( >+ oldVal && >+ oldVal.query.triggerNumber && >+ newVal.query.triggerNumber !== oldVal.query.triggerNumber >+ ) { >+ this.$router.go(0) >+ } >+ }, >+ }, >+ }, >+ components: { TriggersTable, ButtonSubmit, TriggerContext }, >+} >+</script> >+ >+<style scoped> >+.close_modal { >+ position: absolute; >+ top: 0.5rem; >+ right: 0.5rem; >+ font-size: 2rem; >+} >+ >+form li { >+ display: flex; >+ align-items: center; >+} >+ >+.dialog.alert >+ fieldset:not(.bg-danger):not(.bg-warning):not(.bg-info):not(.bg-success):not(.bg-primary):not(.action), >+.dialog.error >+ fieldset:not(.bg-danger):not(.bg-warning):not(.bg-info):not(.bg-success):not(.bg-primary):not(.action) { >+ margin: 0; >+ background-color: rgba(255, 255, 255, 1); >+} >+ >+.router-link-active { >+ margin-left: 10px; >+} >+</style> >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 >new file mode 100644 >index 00000000000..f464cbb8833 >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/js/vue/components/Admin/CirculationTriggers/CirculationTriggersList.vue >@@ -0,0 +1,258 @@ >+<template> >+ <Toolbar> >+ <ToolbarButton >+ :to="{ >+ name: 'CirculationTriggersFormAdd', >+ query: { library_id: selectedLibrary }, >+ }" >+ icon="plus" >+ :title="$__('Add new trigger')" >+ /> >+ </Toolbar> >+ <div v-if="initialized"> >+ <h1>Circulation triggers</h1> >+ <div class="page-section bg-info"> >+ <p> >+ Rules are applied from most specific to less specific, using the >+ first found in this order: >+ </p> >+ <ul> >+ <li>same library, same patron category, same item type</li> >+ <li>same library, same patron category, all item types</li> >+ <li>same library, all patron categories, same item type</li> >+ <li>same library, all patron categories, all item types</li> >+ <li> >+ default (all libraries), same patron category, same item >+ type >+ </li> >+ <li> >+ default (all libraries), same patron category, all item >+ types >+ </li> >+ <li> >+ default (all libraries), all patron categories, same item >+ type >+ </li> >+ <li> >+ default (all libraries), all patron categories, all item >+ types >+ </li> >+ </ul> >+ </div> >+ <div class="page-section" v-if="initialized"> >+ <label for="library_select">{{ $__("Select a library") }}:</label> >+ <v-select >+ id="library_select" >+ v-model="selectedLibrary" >+ label="name" >+ :reduce="lib => lib.library_id" >+ :options="libraries" >+ @update:modelValue="handleLibrarySelection($event)" >+ > >+ <template #search="{ attributes, events }"> >+ <input >+ :required="!selectedLibrary" >+ class="vs__search" >+ v-bind="attributes" >+ v-on="events" >+ /> >+ </template> >+ </v-select> >+ </div> >+ </div> >+ <div v-if="initialized"> >+ <div id="circ_triggers_tabs" class="toptabs numbered"> >+ <ul class="nav nav-tabs" role="tablist"> >+ <li >+ v-for="(number, i) in numberOfTabs" >+ role="presentation" >+ v-bind:class=" >+ tabSelected === `Notice ${number}` ? 'active' : '' >+ " >+ :key="`noticeTab${i}`" >+ > >+ <a >+ href="#" >+ role="tab" >+ @click="changeTabContent" >+ :data-content="`Notice ${number}`" >+ >{{ $__("Trigger") + " " + number }}</a >+ > >+ </li> >+ </ul> >+ </div> >+ <div class="tab_content"> >+ <template v-for="(number, i) in numberOfTabs"> >+ <div >+ v-if="tabSelected === `Notice ${number}`" >+ :key="`noticeTabContent${i}`" >+ > >+ <TriggersTable >+ :circRules="circRules" >+ :triggerNumber="number" >+ :categories="categories" >+ :itemTypes="itemTypes" >+ /> >+ </div> >+ </template> >+ </div> >+ </div> >+ <div v-if="showModal" class="modal_centered"> >+ <div >+ class="modal-dialog modal-dialog-centered modal-lg" >+ role="document" >+ > >+ <router-view></router-view> >+ </div> >+ </div> >+</template> >+ >+<script> >+import Toolbar from "../../Toolbar.vue" >+import ToolbarButton from "../../ToolbarButton.vue" >+import { APIClient } from "../../../fetch/api-client.js" >+import TriggersTable from "./TriggersTable.vue" >+import { inject } from "vue" >+ >+export default { >+ setup() { >+ const { splitCircRulesByTriggerNumber } = inject("circRulesStore") >+ >+ return { >+ splitCircRulesByTriggerNumber, >+ } >+ }, >+ data() { >+ return { >+ initialized: false, >+ libraries: null, >+ selectedLibrary: "*", >+ circRules: null, >+ numberOfTabs: [1], >+ tabSelected: "Notice 1", >+ showModal: false, >+ } >+ }, >+ beforeRouteEnter(to, from, next) { >+ next(vm => { >+ vm.getLibraries().then(() => >+ vm >+ .getCategories() >+ .then(() => >+ vm >+ .getItemTypes() >+ .then(() => >+ vm >+ .getCircRules({}, true) >+ .then(() => (vm.initialized = true)) >+ ) >+ ) >+ ) >+ }) >+ }, >+ methods: { >+ async getLibraries() { >+ const libClient = APIClient.library >+ await libClient.libraries.getAll().then( >+ libraries => { >+ libraries.unshift({ >+ library_id: "*", >+ name: "All libraries", >+ }) >+ this.libraries = libraries >+ }, >+ error => {} >+ ) >+ }, >+ async getCategories() { >+ const client = APIClient.patron >+ await client.patronCategories.getAll().then( >+ categories => { >+ categories.unshift({ >+ patron_category_id: "*", >+ name: "All", >+ }) >+ this.categories = categories >+ }, >+ error => {} >+ ) >+ }, >+ async getItemTypes() { >+ const client = APIClient.item >+ await client.itemTypes.getAll().then( >+ types => { >+ types.unshift({ >+ item_type_id: "*", >+ description: "All", >+ }) >+ this.itemTypes = types >+ }, >+ error => {} >+ ) >+ }, >+ async getCircRules(params = {}, pageLoad) { >+ params.effective = false >+ if (pageLoad) { >+ params.library_id = "*" >+ } >+ const client = APIClient.circRule >+ await client.circRules.getAll({}, params).then( >+ rules => { >+ const { numberOfTabs, rulesPerTrigger: circRules } = >+ this.splitCircRulesByTriggerNumber(rules) >+ this.numberOfTabs = numberOfTabs >+ this.circRules = circRules >+ }, >+ error => {} >+ ) >+ }, >+ async handleLibrarySelection(e) { >+ if (!e) e = "" >+ await this.getCircRules({ library_id: e }) >+ }, >+ changeTabContent(e) { >+ this.tabSelected = e.target.getAttribute("data-content") >+ }, >+ }, >+ watch: { >+ $route: { >+ immediate: true, >+ handler: function (newVal, oldVal) { >+ this.showModal = newVal.meta && newVal.meta.showModal >+ }, >+ }, >+ }, >+ components: { TriggersTable, Toolbar, ToolbarButton }, >+} >+</script> >+ >+<style scoped> >+.v-select { >+ display: inline-block; >+ background-color: white; >+ width: 30%; >+ margin-left: 10px; >+} >+.active { >+ cursor: pointer; >+} >+.toptabs { >+ margin-bottom: 0; >+} >+ >+.modal_centered { >+ position: fixed; >+ z-index: 9998; >+ display: table; >+ transition: opacity 0.3s ease; >+ left: 0px; >+ top: 0px; >+ width: 100%; >+ height: 100%; >+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.33); >+} >+.modal-dialog { >+ overflow: auto; >+ height: 90%; >+} >+</style> >diff --git a/koha-tmpl/intranet-tmpl/prog/js/vue/components/Admin/CirculationTriggers/TriggerContext.vue b/koha-tmpl/intranet-tmpl/prog/js/vue/components/Admin/CirculationTriggers/TriggerContext.vue >new file mode 100644 >index 00000000000..d6f81a5b333 >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/js/vue/components/Admin/CirculationTriggers/TriggerContext.vue >@@ -0,0 +1,50 @@ >+<template> >+ <div class="page-section"> >+ <table> >+ <thead> >+ <th> >+ {{ $__("Loan period") }} >+ </th> >+ <th> >+ {{ $__("Unit") }} >+ </th> >+ <th> >+ {{ $__("Decreased loan period for high holds") }} >+ </th> >+ <th> >+ {{ $__("Fine amount") }} >+ </th> >+ <th> >+ {{ $__("Fine charging interval") }} >+ </th> >+ </thead> >+ <tbody> >+ <tr> >+ <td> >+ {{ ruleInfo.issuelength }} >+ </td> >+ <td> >+ {{ ruleInfo.lengthunit }} >+ </td> >+ <td> >+ {{ ruleInfo.decreaseloanholds }} >+ </td> >+ <td> >+ {{ ruleInfo.fine }} >+ </td> >+ <td> >+ {{ ruleInfo.chargeperiod }} >+ </td> >+ </tr> >+ </tbody> >+ </table> >+ </div> >+</template> >+ >+<script> >+export default { >+ props: ["ruleInfo"], >+} >+</script> >+ >+<style></style> >diff --git a/koha-tmpl/intranet-tmpl/prog/js/vue/components/Admin/CirculationTriggers/TriggersTable.vue b/koha-tmpl/intranet-tmpl/prog/js/vue/components/Admin/CirculationTriggers/TriggersTable.vue >new file mode 100644 >index 00000000000..69f0df003a6 >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/js/vue/components/Admin/CirculationTriggers/TriggersTable.vue >@@ -0,0 +1,266 @@ >+<template> >+ <div class="page-section"> >+ <table> >+ <thead> >+ <th v-if="!modal"> >+ {{ $__("Patron category") }} >+ </th> >+ <th v-if="!modal"> >+ {{ $__("Item type") }} >+ </th> >+ <th v-if="modal"> >+ {{ $__("Notice") }} >+ </th> >+ <th> >+ {{ $__("Delay") }} >+ </th> >+ <th> >+ {{ $__("Notice") }} >+ </th> >+ <th> >+ {{ $__("Email") }} >+ </th> >+ <th> >+ {{ $__("Print") }} >+ </th> >+ <th> >+ {{ $__("SMS") }} >+ </th> >+ <th> >+ {{ $__("Restricts checkouts") }} >+ </th> >+ <th v-if="!modal"> >+ {{ $__("Actions") }} >+ </th> >+ </thead> >+ <tbody> >+ <tr >+ v-for="(rule, i) in filterCircRulesByTabNumber( >+ triggerNumber >+ )" >+ v-bind:key="'rule' + i" >+ > >+ <td >+ v-if="!modal" >+ :class=" >+ modal && i + 1 === parseInt(ruleBeingEdited) >+ ? 'selected_rule' >+ : '' >+ " >+ > >+ {{ >+ handleContext( >+ rule.context.patron_category_id, >+ categories, >+ "patron_category_id" >+ ) >+ }} >+ </td> >+ <td >+ v-if="!modal" >+ :class=" >+ modal && i + 1 === parseInt(ruleBeingEdited) >+ ? 'selected_rule' >+ : '' >+ " >+ > >+ {{ >+ handleContext( >+ rule.context.item_type_id, >+ itemTypes, >+ "item_type_id", >+ "description" >+ ) >+ }} >+ </td> >+ <td >+ v-if="modal" >+ :class=" >+ modal && i + 1 === parseInt(ruleBeingEdited) >+ ? 'selected_rule' >+ : '' >+ " >+ > >+ {{ i + 1 }} >+ </td> >+ <td >+ :class=" >+ modal && i + 1 === parseInt(ruleBeingEdited) >+ ? 'selected_rule' >+ : '' >+ " >+ > >+ {{ >+ rule[ >+ "overdue_" + >+ (modal ? i + 1 : triggerNumber) + >+ "_delay" >+ ] >+ ? rule[ >+ "overdue_" + >+ (modal ? i + 1 : triggerNumber) + >+ "_delay" >+ ] + >+ " " + >+ $__("days") >+ : 0 + " " + $__("days") >+ }} >+ </td> >+ <td >+ :class=" >+ modal && i + 1 === parseInt(ruleBeingEdited) >+ ? 'selected_rule' >+ : '' >+ " >+ > >+ {{ >+ rule[ >+ "overdue_" + >+ (modal ? i + 1 : triggerNumber) + >+ "_notice" >+ ] >+ }} >+ </td> >+ <td >+ :class=" >+ modal && i + 1 === parseInt(ruleBeingEdited) >+ ? 'selected_rule' >+ : '' >+ " >+ > >+ {{ >+ handleTransport( >+ rule[ >+ "overdue_" + >+ (modal ? i + 1 : triggerNumber) + >+ "_mtt" >+ ], >+ "email" >+ ) >+ }} >+ </td> >+ <td >+ :class=" >+ modal && i + 1 === parseInt(ruleBeingEdited) >+ ? 'selected_rule' >+ : '' >+ " >+ > >+ {{ >+ handleTransport( >+ rule[ >+ "overdue_" + >+ (modal ? i + 1 : triggerNumber) + >+ "_mtt" >+ ], >+ "print" >+ ) >+ }} >+ </td> >+ <td >+ :class=" >+ modal && i + 1 === parseInt(ruleBeingEdited) >+ ? 'selected_rule' >+ : '' >+ " >+ > >+ {{ >+ handleTransport( >+ rule[ >+ "overdue_" + >+ (modal ? i + 1 : triggerNumber) + >+ "_mtt" >+ ], >+ "sms" >+ ) >+ }} >+ </td> >+ <td >+ :class=" >+ modal && i + 1 === parseInt(ruleBeingEdited) >+ ? 'selected_rule' >+ : '' >+ " >+ > >+ {{ >+ handleRestrictions( >+ rule[ >+ "overdue_" + >+ (modal ? i + 1 : triggerNumber) + >+ "_restrict" >+ ] >+ ) >+ }} >+ </td> >+ <td v-if="!modal" class="actions"> >+ <router-link >+ :to="{ >+ name: 'CirculationTriggersFormEdit', >+ query: { >+ library_id: rule.context.library_id, >+ item_type_id: rule.context.item_type_id, >+ patron_category_id: >+ rule.context.patron_category_id, >+ triggerNumber, >+ }, >+ }" >+ class="btn btn-default btn-xs" >+ ><i class="fa-solid fa-pencil"></i> >+ Edit</router-link >+ > >+ </td> >+ </tr> >+ </tbody> >+ </table> >+ </div> >+</template> >+ >+<script> >+export default { >+ props: [ >+ "circRules", >+ "triggerNumber", >+ "modal", >+ "ruleBeingEdited", >+ "categories", >+ "itemTypes", >+ ], >+ methods: { >+ handleContext(value, data, type, displayProperty = "name") { >+ const item = data.find(item => item[type] === value) >+ return item[displayProperty] >+ }, >+ handleTransport(value, type) { >+ return value >+ ? value.includes(type) >+ ? this.$__("Yes") >+ : this.$__("No") >+ : this.$__("Default") >+ }, >+ handleRestrictions(value) { >+ return value === "1" ? this.$__("Yes") : this.$__("No") >+ }, >+ filterCircRulesByTabNumber(number) { >+ if (this.modal) return this.circRules >+ return this.circRules.filter( >+ rule => >+ rule.triggerNumber === number && >+ (rule[`overdue_${number}_delay`] || >+ rule[`overdue_${number}_notice`] || >+ rule[`overdue_${number}_mtt`] || >+ rule[`overdue_${number}_restrict`]) >+ ) >+ }, >+ }, >+} >+</script> >+ >+<style scoped> >+.selected_rule { >+ background-color: yellow !important; >+} >+ >+.actions a { >+ margin-right: 5px; >+} >+</style> >-- >2.47.0
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
|
Splinter Review
Attachments on
bug 10190
:
170357
|
170358
|
170359
|
170360
|
170361
|
170362
|
170363
|
170364
|
170365
|
170366
|
170367
|
170368
|
170369
|
170370
|
170371
|
170372
|
170373
|
170374
|
170375
|
170376
|
170380
|
170449
|
171727
|
171728
|
171729
|
171730
|
171731
|
171732
|
171733
|
171734
|
171735
|
171736
|
171737
|
171738
|
171739
|
171740
|
171741
|
171742
|
171743
|
171744
|
171745
|
171746
|
171747
|
171748
|
171749
|
171750
|
171751
|
171752
|
171753
|
171754
|
171755
|
172588
|
172589
|
172590
|
172591
|
172592
|
172593
|
172594
|
172595
|
172596
|
172597
|
172598
|
172599
|
172600
|
172601
|
172602
|
172603
|
172604
|
172605
|
172606
|
172607
|
172608
|
172609
|
172612
|
172614
|
172617
|
172619
|
172621
|
172622
|
172623
|
172624
|
172625
|
172626
|
172630
|
172631
|
172632
|
172633
|
172634
|
172635
|
172636
|
172637
|
172638
|
172639
|
172640
|
172641
|
172642
|
172643
|
172644
|
172645
|
172646
|
172647
|
172648
|
172649
|
172650
|
172651
|
172652
|
172653
|
172654
|
172655
|
172656
|
172657
|
172658
|
172659
|
172660
|
172661
|
172662
|
172663