Line 0
Link Here
|
|
|
1 |
<template> |
2 |
<div> |
3 |
<div class="group" v-for="group in groups" :key="group.key"> |
4 |
<div class="row"> |
5 |
<h2 class="col-md-9">{{group.title}}</h2> |
6 |
<div class="col-md-3"> |
7 |
<button @click="group.expand = !group.expand">{{group.expand ? collapseText : expandText}}</button> |
8 |
</div> |
9 |
</div> |
10 |
<div class="text-muted"> |
11 |
{{group.description}} |
12 |
</div> |
13 |
<form v-if="group.expand" class="form-horizontal"> |
14 |
<template v-for="panel in group.panels" :key="panel.key"> |
15 |
<div class="panel panel-default" v-if="has_kinds(group.key, panel.key)"> |
16 |
<div class="panel-heading"> |
17 |
<div class="row"> |
18 |
<div class="col-md-11">{{self[panel.key+'_panel_heading']}}</div> |
19 |
<div class="col-md-1"> |
20 |
<span class="fa fa-pencil" @click="panel.edit = !panel.edit"></span> |
21 |
</div> |
22 |
</div> |
23 |
</div> |
24 |
<div class="panel-body"> |
25 |
<div v-for="(rule, key) in effective_rules[group.key][panel.key]" :key="key" class="form-group"> |
26 |
<label for="{{key}}" class="col-md-3 control-label">{{rule.kind.description}}</label> |
27 |
<div class="col-md-9" v-if="!panel.edit"> |
28 |
<div class="form-control-static">{{get_rule_value(rule)}}</div> |
29 |
<div class="text-muted" v-if="rule.rule._use_default && !rule.is_default">Using default value</div> |
30 |
</div> |
31 |
<div class="col-md-9" v-if="panel.edit"> |
32 |
<template v-if="!rule.is_default"> |
33 |
<div> |
34 |
<input type="checkbox" :id="key+'_default'" v-model="rule.rule._use_default" />Use default value ({{get_default_value(rule)}}) |
35 |
</div> |
36 |
<div> |
37 |
<input v-if="!rule.kind.choices && rule.kind.type != 'date'" :readonly="rule.rule._use_default" :disabled="rule.rule._use_default" type="text" :id="key" v-model="rule.rule.rule_value" @change="edit(rule.rule)" class="form-control" :class="{datepicker: rule.kind.type == 'date'}"/> |
38 |
<date-picker v-if="!rule.kind.choices && rule.kind.type == 'date'" :readonly="rule.rule._use_default" :disabled="rule.rule._use_default" :id="key" v-model:date="rule.rule.rule_value" @change="edit(rule.rule)" class="form-control"/> |
39 |
<select v-if="rule.kind.choices" :readonly="rule.rule._use_default" :disabled="rule.rule._use_default" :id="key" v-model="rule.rule.rule_value" @change="edit(rule.rule)" class="form-control"> |
40 |
<option v-for="choice in rule.kind.choices" :key="choice[0]" :value="choice[0]">{{choice[1]}}</option> |
41 |
</select> |
42 |
</div> |
43 |
</template> |
44 |
<template v-if="rule.is_default"> |
45 |
<input v-if="!rule.kind.choices && rule.kind.type != 'date'" type="text" :id="key" v-model="rule.default.rule_value" @change="edit(rule.default)" class="form-control" :class="{datepicker: rule.kind.type == 'date'}"/> |
46 |
<date-picker v-if="!rule.kind.choices && rule.kind.type == 'date'" :id="key" v-model:date="rule.default.rule_value" @change="edit(rule.default)" class="form-control"/> |
47 |
<select v-if="rule.kind.choices" :id="key" v-model="rule.default.rule_value" @change="edit(rule.default)" class="form-control"> |
48 |
<option v-for="choice in rule.kind.choices" :key="choice[0]" :value="choice[0]">{{choice[1]}}</option> |
49 |
</select> |
50 |
</template> |
51 |
|
52 |
</div> |
53 |
</div> |
54 |
</div> |
55 |
</div> |
56 |
</template> |
57 |
</form> |
58 |
</div> |
59 |
</div> |
60 |
</template> |
61 |
<script lang="ts"> |
62 |
import { defineComponent, readonly, reactive } from 'vue' |
63 |
import DatePicker from '../../components/datepicker.vue' |
64 |
|
65 |
export default defineComponent({ |
66 |
name: 'PolicyMain', |
67 |
components: { |
68 |
DatePicker |
69 |
}, |
70 |
props: [ |
71 |
'current', |
72 |
'libraries', |
73 |
'categories', |
74 |
'itemtypes', |
75 |
'doSave', |
76 |
'doClear' |
77 |
], |
78 |
data() { |
79 |
return { |
80 |
self: this, |
81 |
expandText: _('Expand'), |
82 |
collapseText: _('Collapse'), |
83 |
kinds: {}, |
84 |
origRules: {}, |
85 |
rules: {}, |
86 |
groups: [ |
87 |
{ |
88 |
key: 'circulation', |
89 |
title: _("Circulation rules"), |
90 |
description: _("A brief and tiny text that explains how awesome and great cirulation rules are"), |
91 |
expanded: false, |
92 |
panels: [ |
93 |
{ |
94 |
scope: ['branchcode'], |
95 |
key: 'lib' |
96 |
}, |
97 |
{ |
98 |
scope: ['branchcode', 'categorycode'], |
99 |
key: 'cat' |
100 |
}, |
101 |
{ |
102 |
scope: ['branchcode', 'itemtype'], |
103 |
key: 'itype' |
104 |
}, |
105 |
{ |
106 |
scope: ['branchcode', 'categorycode', 'itemtype'], |
107 |
key: 'all' |
108 |
} |
109 |
] |
110 |
}, |
111 |
{ |
112 |
key: 'fines', |
113 |
title: _("Fines rules"), |
114 |
description: _("A long and tedious text that explains how deadly serious fines rules are"), |
115 |
expanded: false, |
116 |
panels: [ |
117 |
{ |
118 |
scope: ['branchcode'], |
119 |
key: 'lib' |
120 |
}, |
121 |
{ |
122 |
scope: ['branchcode', 'categorycode'], |
123 |
key: 'cat' |
124 |
}, |
125 |
{ |
126 |
scope: ['branchcode', 'itemtype'], |
127 |
key: 'itype' |
128 |
}, |
129 |
{ |
130 |
scope: ['branchcode', 'categorycode', 'itemtype'], |
131 |
key: 'all' |
132 |
} |
133 |
] |
134 |
}, |
135 |
{ |
136 |
key: 'holds', |
137 |
title: _("Holds rules"), |
138 |
description: _("A light text that explains why you cannot live without holds rules"), |
139 |
expanded: false, |
140 |
panels: [ |
141 |
{ |
142 |
scope: ['branchcode'], |
143 |
key: 'lib' |
144 |
}, |
145 |
{ |
146 |
scope: ['branchcode', 'categorycode'], |
147 |
key: 'cat' |
148 |
}, |
149 |
{ |
150 |
scope: ['branchcode', 'itemtype'], |
151 |
key: 'itype' |
152 |
}, |
153 |
{ |
154 |
scope: ['branchcode', 'categorycode', 'itemtype'], |
155 |
key: 'all' |
156 |
} |
157 |
] |
158 |
}, |
159 |
], |
160 |
edited: [] |
161 |
} |
162 |
}, |
163 |
async created() { |
164 |
this.kinds = await fetch( "/api/v1/circulation-rules/kinds" ).then( result => result.json() ) |
165 |
this.rules = await fetch( "/api/v1/circulation-rules" ).then( result => result.json() ) |
166 |
this.origRules = readonly(JSON.parse(JSON.stringify(this.rules))); |
167 |
}, |
168 |
computed: { |
169 |
lib_panel_heading() { |
170 |
return 'For '+this.get_current('lib') |
171 |
}, |
172 |
cat_panel_heading() { |
173 |
return 'For '+this.get_current('lib')+' and '+this.get_current('cat') |
174 |
}, |
175 |
itype_panel_heading() { |
176 |
return 'For '+this.get_current('lib')+' and '+this.get_current('itype') |
177 |
}, |
178 |
all_panel_heading() { |
179 |
return 'For '+this.get_current('lib')+', '+this.get_current('cat')+' and '+this.get_current('itype') |
180 |
}, |
181 |
effective_rules() { |
182 |
const groups = {} |
183 |
const missing_rules = []; |
184 |
this.groups.forEach(group => { |
185 |
groups[group.key] = {}, |
186 |
group.panels.forEach(panel => { |
187 |
groups[group.key][panel.key] = {} |
188 |
Object.keys(this.kinds).filter(key => { |
189 |
const kind = this.kinds[key] |
190 |
if(!kind.description) return; |
191 |
if(kind.group == group.key && !this.simetric_diff(kind.scope, panel.scope).length) { |
192 |
groups[group.key][panel.key][key] = {kind} |
193 |
const rules = this.rules |
194 |
.filter(rule => rule.rule_name == key) |
195 |
rules.forEach(rule => { |
196 |
if(rule.branchcode == null) rule.branchcode = ''; |
197 |
if(rule.categorycode == null) rule.categorycode = ''; |
198 |
if(rule.itemtype == null) rule.itemtype = ''; |
199 |
}) |
200 |
groups[group.key][panel.key][key].default = rules.find(rule => rule.branchcode == '' && rule.categorycode == '' && rule.itemtype == '') |
201 |
if(!groups[group.key][panel.key][key].default) { |
202 |
missing_rules.push({ |
203 |
rule_value: '', |
204 |
rule_name: key, |
205 |
branchcode: '', |
206 |
categorycode: '', |
207 |
itemtype: '' |
208 |
}) |
209 |
} |
210 |
if(panel.key == 'lib' && this.current.lib == '') { |
211 |
groups[group.key][panel.key][key].is_default = true |
212 |
} else if(panel.key == 'cat' && this.current.lib == '' && this.current.cat == '') { |
213 |
groups[group.key][panel.key][key].is_default = true |
214 |
} else if(panel.key == 'itype' && this.current.lib == '' && this.current.itype == '') { |
215 |
groups[group.key][panel.key][key].is_default = true |
216 |
} else if(panel.key == 'all' && this.current.lib == '' && this.current.cat == '' && this.current.itype == '') { |
217 |
groups[group.key][panel.key][key].is_default = true |
218 |
} else { |
219 |
groups[group.key][panel.key][key].is_default = false |
220 |
} |
221 |
|
222 |
groups[group.key][panel.key][key].rule = rules.find(rule => rule.branchcode == this.current.lib && rule.categorycode == this.current.cat && rule.itemtype == this.current.itype) |
223 |
if(!groups[group.key][panel.key][key].rule) { |
224 |
missing_rules.push({ |
225 |
rule_name: key, |
226 |
rule_value: null, |
227 |
branchcode: this.current.lib, |
228 |
categorycode: this.current.cat, |
229 |
itemtype: this.current.itype, |
230 |
_use_default: true |
231 |
}); |
232 |
} |
233 |
} |
234 |
}) |
235 |
}) |
236 |
}) |
237 |
if(missing_rules.length) { |
238 |
this.$nextTick(()=>{ |
239 |
this.rules = this.rules.concat(missing_rules) |
240 |
}) |
241 |
return null |
242 |
} |
243 |
return groups |
244 |
} |
245 |
}, |
246 |
methods: { |
247 |
simetric_diff(left, right) { |
248 |
const rightSet = new Set(right) |
249 |
const leftSet = new Set(left) |
250 |
return left.filter(item => !rightSet.has(item)).concat(right.filter(item => !leftSet.has(item))) |
251 |
}, |
252 |
has_kinds(group, panel) { |
253 |
return this.effective_rules && this.effective_rules[group] && this.effective_rules[group][panel] && Object.keys(this.effective_rules[group][panel]).length |
254 |
}, |
255 |
get_default_value(rule) { |
256 |
if(!rule.default) { |
257 |
return '' |
258 |
} |
259 |
if(rule.kind.choices && rule.kind.choices) { |
260 |
const choice = rule.kind.choices.find(choice => choice[0]==rule.default.rule_value) |
261 |
if(choice) return choice[1] |
262 |
} |
263 |
return rule.default.rule_value |
264 |
}, |
265 |
get_rule_value(rule) { |
266 |
if(rule.rule._use_default) return this.get_default_value(rule); |
267 |
if(rule.kind.choices && rule.kind.choices) { |
268 |
const choice = rule.kind.choices.find(choice => choice[0]==rule.rule.rule_value) |
269 |
if(choice) return choice[1] |
270 |
} |
271 |
return rule.rule.rule_value |
272 |
}, |
273 |
get_current(type) { |
274 |
if(type == 'lib') { |
275 |
let curr_name = this.libraries.find(lib => lib.code == this.current.lib).name |
276 |
return this.current[type]==''?curr_name.toLowerCase():curr_name |
277 |
} |
278 |
if(type == 'cat') { |
279 |
let curr_name = this.categories.find(cat => cat.code == this.current.cat).name |
280 |
return this.current[type]==''?curr_name.toLowerCase():_('%s category').format(curr_name) |
281 |
} |
282 |
if(type == 'itype') { |
283 |
let curr_name = this.itemtypes.find(itype => itype.code == this.current.itype).name |
284 |
return this.current[type]==''?curr_name.toLowerCase():_('%s item type').format(curr_name) |
285 |
} |
286 |
}, |
287 |
edit(rule) { |
288 |
if(!this.edited.includes(rule)) this.edited.push(rule) |
289 |
}, |
290 |
clear() { |
291 |
this.rules = reactive(JSON.parse(JSON.stringify(this.origRules))) |
292 |
this.edited = [] |
293 |
}, |
294 |
save() { |
295 |
const payload = this.edited |
296 |
.filter(rule => { |
297 |
return rule.id || !rule._use_default |
298 |
}) |
299 |
.map(origRule => { |
300 |
const rule = { |
301 |
rule_name: origRule.rule_name, |
302 |
rule_value: origRule.id && origRule._use_default?null:origRule.rule_value, |
303 |
branchcode: origRule.branchcode == ''?null:origRule.branchcode, |
304 |
categorycode: origRule.categorycode == ''?null:origRule.categorycode, |
305 |
itemtype: origRule.itemtype == ''?null:origRule.itemtype |
306 |
}; |
307 |
if(origRule.id) rule.id = origRule.id |
308 |
|
309 |
return rule |
310 |
}) |
311 |
fetch( "/api/v1/circulation-rules", { |
312 |
method: "POST", |
313 |
credentials: "include", |
314 |
body: JSON.stringify(payload), |
315 |
} ).then( response => { |
316 |
switch( response.status ) { |
317 |
case 200: |
318 |
this.edited = [] |
319 |
this.origRules = readonly(JSON.parse(JSON.stringify(this.rules))) |
320 |
humanMsg.displayMsg( '<h3>'+_("Rules saved.")+'</h3>', { className: "humanSuccess" } ) |
321 |
break |
322 |
case 401: |
323 |
humanMsg.displayAlert( '<h3>'+_("Rules not saved")+'</h3><p>'+_("Please reload the page to log in again")+'</p>', { className: "humanError" } ) |
324 |
break |
325 |
default: |
326 |
humanMsg.displayAlert( '<h3>'+_("Rules not saved")+'</h3><p>'+_("Internal error")+'</p>', { className: "humanError" } ) |
327 |
break |
328 |
} |
329 |
} ) |
330 |
} |
331 |
}, |
332 |
watch: { |
333 |
doSave(val, oldVal) { |
334 |
if(val) { |
335 |
this.save() |
336 |
} |
337 |
}, |
338 |
doClear(val, oldVal) { |
339 |
if(val) { |
340 |
this.clear() |
341 |
} |
342 |
}, |
343 |
edited: { |
344 |
handler(val, oldVal) { |
345 |
this.$emit('edited', val) |
346 |
}, |
347 |
deep: true |
348 |
} |
349 |
} |
350 |
}) |
351 |
</script> |