nodebb-plugin-calendar-onekite 11.1.38 → 11.1.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/admin.js +17 -29
- package/lib/api.js +47 -15
- package/lib/helloasso.js +4 -4
- package/package.json +1 -1
- package/public/admin.js +89 -15
- package/public/client.js +20 -2
- package/templates/emails/calendar-onekite_approved.tpl +4 -3
- package/templates/emails/calendar-onekite_pending.tpl +0 -1
package/lib/admin.js
CHANGED
|
@@ -4,6 +4,14 @@ const meta = require.main.require('./src/meta');
|
|
|
4
4
|
const user = require.main.require('./src/user');
|
|
5
5
|
const emailer = require.main.require('./src/emailer');
|
|
6
6
|
|
|
7
|
+
function formatFR(tsOrIso) {
|
|
8
|
+
const d = new Date(typeof tsOrIso === 'string' && /^[0-9]+$/.test(tsOrIso) ? parseInt(tsOrIso, 10) : tsOrIso);
|
|
9
|
+
const dd = String(d.getDate()).padStart(2, '0');
|
|
10
|
+
const mm = String(d.getMonth() + 1).padStart(2, '0');
|
|
11
|
+
const yyyy = d.getFullYear();
|
|
12
|
+
return `${dd}/${mm}/${yyyy}`;
|
|
13
|
+
}
|
|
14
|
+
|
|
7
15
|
async function sendEmail(template, toEmail, subject, data) {
|
|
8
16
|
if (!toEmail) return;
|
|
9
17
|
try {
|
|
@@ -68,7 +76,7 @@ admin.approveReservation = async function (req, res) {
|
|
|
68
76
|
const r = await dbLayer.getReservation(rid);
|
|
69
77
|
if (!r) return res.status(404).json({ error: 'not-found' });
|
|
70
78
|
|
|
71
|
-
r.status = '
|
|
79
|
+
r.status = 'awaiting_payment';
|
|
72
80
|
r.adminNote = String((req.body && (req.body.adminNote || req.body.note)) || '').trim();
|
|
73
81
|
r.pickupTime = String((req.body && (req.body.pickupTime || req.body.pickup)) || '').trim();
|
|
74
82
|
r.approvedAt = Date.now();
|
|
@@ -85,29 +93,8 @@ admin.approveReservation = async function (req, res) {
|
|
|
85
93
|
let paymentUrl = null;
|
|
86
94
|
if (token) {
|
|
87
95
|
const requester = await user.getUserFields(r.uid, ['username', 'email']);
|
|
88
|
-
//
|
|
89
|
-
|
|
90
|
-
try {
|
|
91
|
-
const items = await helloasso.listItems({
|
|
92
|
-
env,
|
|
93
|
-
token,
|
|
94
|
-
organizationSlug: settings.helloassoOrganizationSlug,
|
|
95
|
-
formType: settings.helloassoFormType,
|
|
96
|
-
formSlug: settings.helloassoFormSlug,
|
|
97
|
-
});
|
|
98
|
-
const normalized = (items || []).map((it) => ({
|
|
99
|
-
id: String(it.id || it.itemId || it.reference || it.name),
|
|
100
|
-
price: it.price || it.amount || it.unitPrice || 0,
|
|
101
|
-
})).filter(it => it.id);
|
|
102
|
-
const match = normalized.find(it => it.id === String(r.itemId));
|
|
103
|
-
totalAmount = match ? parseInt(match.price, 10) || 0 : 0;
|
|
104
|
-
} catch (e) {
|
|
105
|
-
totalAmount = 0;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
if (!totalAmount) {
|
|
109
|
-
return res.status(400).json({ error: 'item-price-not-found' });
|
|
110
|
-
}
|
|
96
|
+
// r.total is stored as an estimated total in euros; HelloAsso expects cents.
|
|
97
|
+
const totalAmount = Math.max(0, Math.round((Number(r.total) || 0) * 100));
|
|
111
98
|
paymentUrl = await helloasso.createCheckoutIntent({
|
|
112
99
|
env,
|
|
113
100
|
token,
|
|
@@ -116,6 +103,7 @@ admin.approveReservation = async function (req, res) {
|
|
|
116
103
|
formSlug: settings.helloassoFormSlug,
|
|
117
104
|
totalAmount,
|
|
118
105
|
payerEmail: requester && requester.email,
|
|
106
|
+
callbackUrl: 'https://api.onekite.com/helloasso',
|
|
119
107
|
});
|
|
120
108
|
}
|
|
121
109
|
|
|
@@ -129,11 +117,11 @@ admin.approveReservation = async function (req, res) {
|
|
|
129
117
|
try {
|
|
130
118
|
const requester = await user.getUserFields(r.uid, ['username', 'email']);
|
|
131
119
|
if (requester && requester.email) {
|
|
132
|
-
await sendEmail('calendar-onekite_approved', requester.email, 'Demande de réservation matériel
|
|
120
|
+
await sendEmail('calendar-onekite_approved', requester.email, 'Demande de réservation de matériel', {
|
|
133
121
|
username: requester.username,
|
|
134
122
|
itemName: (Array.isArray(r.itemNames) ? r.itemNames.join(', ') : (r.itemName || '')),
|
|
135
|
-
start:
|
|
136
|
-
end:
|
|
123
|
+
start: formatFR(r.start),
|
|
124
|
+
end: formatFR(r.end),
|
|
137
125
|
paymentUrl: paymentUrl || '',
|
|
138
126
|
adminNote: r.adminNote || '',
|
|
139
127
|
pickupTime: r.pickupTime || '',
|
|
@@ -158,8 +146,8 @@ admin.refuseReservation = async function (req, res) {
|
|
|
158
146
|
await sendEmail('calendar-onekite_refused', requester.email, 'Demande de réservation matériel - Refusée', {
|
|
159
147
|
username: requester.username,
|
|
160
148
|
itemName: r.itemName,
|
|
161
|
-
start:
|
|
162
|
-
end:
|
|
149
|
+
start: formatFR(r.start),
|
|
150
|
+
end: formatFR(r.end),
|
|
163
151
|
});
|
|
164
152
|
}
|
|
165
153
|
} catch (e) {}
|
package/lib/api.js
CHANGED
|
@@ -45,6 +45,15 @@ function overlap(aStart, aEnd, bStart, bEnd) {
|
|
|
45
45
|
return aStart < bEnd && bStart < aEnd;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
|
|
49
|
+
function formatFR(tsOrIso) {
|
|
50
|
+
const d = new Date(typeof tsOrIso === 'string' && /^[0-9]+$/.test(tsOrIso) ? parseInt(tsOrIso, 10) : tsOrIso);
|
|
51
|
+
const dd = String(d.getDate()).padStart(2, '0');
|
|
52
|
+
const mm = String(d.getMonth() + 1).padStart(2, '0');
|
|
53
|
+
const yyyy = d.getFullYear();
|
|
54
|
+
return `${dd}/${mm}/${yyyy}`;
|
|
55
|
+
}
|
|
56
|
+
|
|
48
57
|
function toTs(v) {
|
|
49
58
|
if (!v) return NaN;
|
|
50
59
|
const d = new Date(v);
|
|
@@ -73,7 +82,7 @@ async function canValidate(uid, settings) {
|
|
|
73
82
|
|
|
74
83
|
function eventsFor(resv) {
|
|
75
84
|
const status = resv.status;
|
|
76
|
-
const icons = { pending: '⏳', awaiting_payment: '
|
|
85
|
+
const icons = { pending: '⏳', awaiting_payment: '💳', paid: '✅' };
|
|
77
86
|
const startIsoDate = new Date(parseInt(resv.start, 10)).toISOString().slice(0, 10);
|
|
78
87
|
const endIsoDate = new Date(parseInt(resv.end, 10)).toISOString().slice(0, 10);
|
|
79
88
|
|
|
@@ -97,8 +106,10 @@ function eventsFor(resv) {
|
|
|
97
106
|
rid: resv.rid,
|
|
98
107
|
status,
|
|
99
108
|
uid: resv.uid,
|
|
100
|
-
itemIds:
|
|
101
|
-
itemNames:
|
|
109
|
+
itemIds: itemIds.filter(Boolean),
|
|
110
|
+
itemNames: itemNames.filter(Boolean),
|
|
111
|
+
itemIdLine: itemId,
|
|
112
|
+
itemNameLine: itemName,
|
|
102
113
|
},
|
|
103
114
|
});
|
|
104
115
|
}
|
|
@@ -123,7 +134,7 @@ api.getEvents = async function (req, res) {
|
|
|
123
134
|
const r = await dbLayer.getReservation(rid);
|
|
124
135
|
if (!r) continue;
|
|
125
136
|
// Only show active statuses
|
|
126
|
-
if (!['pending', 'awaiting_payment', '
|
|
137
|
+
if (!['pending', 'awaiting_payment', 'paid'].includes(r.status)) continue;
|
|
127
138
|
const rStart = parseInt(r.start, 10);
|
|
128
139
|
const rEnd = parseInt(r.end, 10);
|
|
129
140
|
if (!(rStart < endTs && startTs < rEnd)) continue; // overlap check
|
|
@@ -218,7 +229,6 @@ api.createReservation = async function (req, res) {
|
|
|
218
229
|
const rid = crypto.randomUUID();
|
|
219
230
|
|
|
220
231
|
const resv = {
|
|
221
|
-
rid,
|
|
222
232
|
uid,
|
|
223
233
|
itemIds,
|
|
224
234
|
itemNames: itemNames.length ? itemNames : itemIds,
|
|
@@ -249,14 +259,13 @@ api.createReservation = async function (req, res) {
|
|
|
249
259
|
await sendEmail(
|
|
250
260
|
'calendar-onekite_pending',
|
|
251
261
|
md.email,
|
|
252
|
-
'Demande de réservation matériel
|
|
262
|
+
'Demande de réservation de matériel',
|
|
253
263
|
{
|
|
254
264
|
username: md.username,
|
|
255
265
|
requester: requester.username,
|
|
256
266
|
itemName: itemsLabel,
|
|
257
|
-
start:
|
|
258
|
-
end:
|
|
259
|
-
rid,
|
|
267
|
+
start: formatFR(start),
|
|
268
|
+
end: formatFR(end),
|
|
260
269
|
total: resv.total || 0,
|
|
261
270
|
}
|
|
262
271
|
);
|
|
@@ -284,20 +293,43 @@ api.approveReservation = async function (req, res) {
|
|
|
284
293
|
if (!r) return res.status(404).json({ error: 'not-found' });
|
|
285
294
|
if (r.status !== 'pending') return res.status(400).json({ error: 'bad-status' });
|
|
286
295
|
|
|
287
|
-
r.status = '
|
|
296
|
+
r.status = 'awaiting_payment';
|
|
288
297
|
r.adminNote = String((req.body && req.body.adminNote) || '').trim();
|
|
289
298
|
r.pickupTime = String((req.body && req.body.pickupTime) || '').trim();
|
|
290
299
|
r.approvedAt = Date.now();
|
|
300
|
+
// Create HelloAsso payment link on validation
|
|
301
|
+
try {
|
|
302
|
+
const settings2 = await meta.settings.get('calendar-onekite');
|
|
303
|
+
const token = await helloasso.getAccessToken(settings2);
|
|
304
|
+
const payer = await user.getUserFields(r.uid, ['email']);
|
|
305
|
+
const paymentUrl = await helloasso.createCheckoutIntent({
|
|
306
|
+
env: settings2.helloassoEnv,
|
|
307
|
+
token,
|
|
308
|
+
organizationSlug: settings2.helloassoOrganizationSlug,
|
|
309
|
+
formType: settings2.helloassoFormType,
|
|
310
|
+
formSlug: settings2.helloassoFormSlug,
|
|
311
|
+
// r.total is stored as an estimated total in euros; HelloAsso expects cents.
|
|
312
|
+
totalAmount: Math.max(0, Math.round((Number(r.total) || 0) * 100)),
|
|
313
|
+
payerEmail: payer && payer.email ? payer.email : '',
|
|
314
|
+
callbackUrl: 'https://api.onekite.com/helloasso',
|
|
315
|
+
});
|
|
316
|
+
if (paymentUrl) {
|
|
317
|
+
r.paymentUrl = paymentUrl;
|
|
318
|
+
}
|
|
319
|
+
} catch (e) {
|
|
320
|
+
// ignore payment link errors, admin can retry
|
|
321
|
+
}
|
|
322
|
+
|
|
291
323
|
await dbLayer.saveReservation(r);
|
|
292
324
|
|
|
293
325
|
// Email requester
|
|
294
326
|
const requester = await user.getUserFields(r.uid, ['username', 'email']);
|
|
295
327
|
if (requester && requester.email) {
|
|
296
|
-
await sendEmail('calendar-onekite_approved', requester.email, 'Demande de réservation matériel
|
|
328
|
+
await sendEmail('calendar-onekite_approved', requester.email, 'Demande de réservation de matériel', {
|
|
297
329
|
username: requester.username,
|
|
298
330
|
itemName: (Array.isArray(r.itemNames) ? r.itemNames.join(', ') : (r.itemName || '')),
|
|
299
|
-
start:
|
|
300
|
-
end:
|
|
331
|
+
start: formatFR(r.start),
|
|
332
|
+
end: formatFR(r.end),
|
|
301
333
|
adminNote: r.adminNote || '',
|
|
302
334
|
pickupTime: r.pickupTime || '',
|
|
303
335
|
paymentUrl: r.paymentUrl || '',
|
|
@@ -326,8 +358,8 @@ api.refuseReservation = async function (req, res) {
|
|
|
326
358
|
await sendEmail('calendar-onekite_refused', requester.email, 'Demande de réservation matériel - Supprimée', {
|
|
327
359
|
username: requester.username,
|
|
328
360
|
itemName: (Array.isArray(r.itemNames) ? r.itemNames.join(', ') : (r.itemName || '')),
|
|
329
|
-
start:
|
|
330
|
-
end:
|
|
361
|
+
start: formatFR(r.start),
|
|
362
|
+
end: formatFR(r.end),
|
|
331
363
|
});
|
|
332
364
|
}
|
|
333
365
|
|
package/lib/helloasso.js
CHANGED
|
@@ -174,7 +174,7 @@ async function listCatalogItems({ env, token, organizationSlug, formType, formSl
|
|
|
174
174
|
};
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
-
async function createCheckoutIntent({ env, token, organizationSlug, formType, formSlug, totalAmount, payerEmail }) {
|
|
177
|
+
async function createCheckoutIntent({ env, token, organizationSlug, formType, formSlug, totalAmount, payerEmail, callbackUrl }) {
|
|
178
178
|
if (!token || !organizationSlug) return null;
|
|
179
179
|
// Checkout intents are created at organization level.
|
|
180
180
|
const url = `${baseUrl(env)}/v5/organizations/${encodeURIComponent(organizationSlug)}/checkout-intents`;
|
|
@@ -182,9 +182,9 @@ async function createCheckoutIntent({ env, token, organizationSlug, formType, fo
|
|
|
182
182
|
totalAmount: totalAmount,
|
|
183
183
|
initialAmount: totalAmount,
|
|
184
184
|
payer: payerEmail ? { email: payerEmail } : undefined,
|
|
185
|
-
backUrl: '',
|
|
186
|
-
errorUrl: '',
|
|
187
|
-
returnUrl: '',
|
|
185
|
+
backUrl: callbackUrl || '',
|
|
186
|
+
errorUrl: callbackUrl || '',
|
|
187
|
+
returnUrl: callbackUrl || '',
|
|
188
188
|
};
|
|
189
189
|
const { status, json } = await requestJson('POST', url, { Authorization: `Bearer ${token}` }, payload);
|
|
190
190
|
if (status >= 200 && status < 300 && json) {
|
package/package.json
CHANGED
package/public/admin.js
CHANGED
|
@@ -42,6 +42,7 @@ define('admin/plugins/calendar-onekite', ['alerts', 'bootbox'], function (alerts
|
|
|
42
42
|
});
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
|
|
45
46
|
function renderPending(list) {
|
|
46
47
|
const wrap = document.getElementById('onekite-pending');
|
|
47
48
|
if (!wrap) return;
|
|
@@ -52,28 +53,101 @@ define('admin/plugins/calendar-onekite', ['alerts', 'bootbox'], function (alerts
|
|
|
52
53
|
return;
|
|
53
54
|
}
|
|
54
55
|
|
|
56
|
+
const fmtFR = (ts) => {
|
|
57
|
+
const d = new Date(parseInt(ts, 10));
|
|
58
|
+
const dd = String(d.getDate()).padStart(2, '0');
|
|
59
|
+
const mm = String(d.getMonth() + 1).padStart(2, '0');
|
|
60
|
+
const yyyy = d.getFullYear();
|
|
61
|
+
const hh = String(d.getHours()).padStart(2, '0');
|
|
62
|
+
const mi = String(d.getMinutes()).padStart(2, '0');
|
|
63
|
+
return `${dd}/${mm}/${yyyy} ${hh}:${mi}`;
|
|
64
|
+
};
|
|
65
|
+
|
|
55
66
|
for (const r of list) {
|
|
56
|
-
const created = r.createdAt ?
|
|
57
|
-
const itemNames = Array.isArray(r.itemNames) && r.itemNames.length ? r.itemNames : [r.itemName || r.itemId];
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
<div
|
|
67
|
+
const created = r.createdAt ? fmtFR(r.createdAt) : '';
|
|
68
|
+
const itemNames = Array.isArray(r.itemNames) && r.itemNames.length ? r.itemNames : [r.itemName || r.itemId].filter(Boolean);
|
|
69
|
+
const itemsHtml = itemNames.map(n => `<div>• ${escapeHtml(String(n))}</div>`).join('');
|
|
70
|
+
const div = document.createElement('div');
|
|
71
|
+
div.className = 'list-group-item';
|
|
72
|
+
div.innerHTML = `
|
|
73
|
+
<div class="d-flex justify-content-between align-items-start gap-2">
|
|
74
|
+
<div style="min-width: 0;">
|
|
75
|
+
<div><strong>${itemsHtml || escapeHtml(r.itemName || '')}</strong></div>
|
|
76
|
+
<div class="text-muted" style="font-size: 12px;">Créée: ${escapeHtml(created)}</div>
|
|
77
|
+
<div class="text-muted" style="font-size: 12px;">Période: ${escapeHtml(new Date(parseInt(r.start, 10)).toLocaleDateString('fr-FR'))} → ${escapeHtml(new Date(parseInt(r.end, 10)).toLocaleDateString('fr-FR'))}</div>
|
|
78
|
+
</div>
|
|
79
|
+
<div class="d-flex gap-2">
|
|
80
|
+
<button class="btn btn-outline-danger btn-sm">Rejeter</button>
|
|
81
|
+
<button class="btn btn-success btn-sm">Valider</button>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
`;
|
|
85
|
+
const [refuseBtn, approveBtn] = div.querySelectorAll('button');
|
|
86
|
+
refuseBtn.addEventListener('click', async () => {
|
|
87
|
+
if (!confirm('Rejeter cette demande ?')) return;
|
|
88
|
+
try {
|
|
89
|
+
// Server route: PUT /api/v3/admin/plugins/calendar-onekite/reservations/:rid/refuse
|
|
90
|
+
await fetchJson(`/api/v3/admin/plugins/calendar-onekite/reservations/${encodeURIComponent(r.rid)}/refuse`, { method: 'PUT' });
|
|
91
|
+
showAlert('success', 'Demande rejetée.');
|
|
92
|
+
await loadPending();
|
|
93
|
+
} catch (e) {
|
|
94
|
+
showAlert('error', 'Rejet impossible.');
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
approveBtn.addEventListener('click', async () => {
|
|
99
|
+
const opts = (() => {
|
|
100
|
+
const out = [];
|
|
101
|
+
for (let h = 0; h < 24; h++) {
|
|
102
|
+
for (let m = 0; m < 60; m += 5) {
|
|
103
|
+
out.push(`${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return out;
|
|
107
|
+
})().map(t => `<option value="${t}">${t}</option>`).join('');
|
|
108
|
+
|
|
109
|
+
const html = `
|
|
110
|
+
<div class="mb-3">
|
|
111
|
+
<label class="form-label">Note (lieu de récupération)</label>
|
|
112
|
+
<textarea class="form-control" id="onekite-admin-note" placeholder="Matériel à récupérer à l'adresse : ..."></textarea>
|
|
65
113
|
</div>
|
|
66
|
-
<div class="
|
|
67
|
-
|
|
68
|
-
<
|
|
69
|
-
<button class="btn btn-sm btn-outline-danger" data-action="refuse" data-rid="${r.rid}">Refuser</button>
|
|
114
|
+
<div class="mb-2">
|
|
115
|
+
<label class="form-label">Heure de récupération</label>
|
|
116
|
+
<select class="form-select" id="onekite-pickup-time">${opts}</select>
|
|
70
117
|
</div>
|
|
71
118
|
`;
|
|
72
|
-
|
|
73
|
-
|
|
119
|
+
|
|
120
|
+
bootbox.dialog({
|
|
121
|
+
title: 'Valider la demande',
|
|
122
|
+
message: html,
|
|
123
|
+
buttons: {
|
|
124
|
+
cancel: { label: 'Annuler', className: 'btn-secondary' },
|
|
125
|
+
ok: {
|
|
126
|
+
label: 'Valider',
|
|
127
|
+
className: 'btn-success',
|
|
128
|
+
callback: async () => {
|
|
129
|
+
try {
|
|
130
|
+
const adminNote = (document.getElementById('onekite-admin-note')?.value || '').trim();
|
|
131
|
+
const pickupTime = (document.getElementById('onekite-pickup-time')?.value || '').trim();
|
|
132
|
+
// Server route: PUT /api/v3/admin/plugins/calendar-onekite/reservations/:rid/approve
|
|
133
|
+
await fetchJson(`/api/v3/admin/plugins/calendar-onekite/reservations/${encodeURIComponent(r.rid)}/approve`, { method: 'PUT', body: JSON.stringify({ adminNote, pickupTime }) });
|
|
134
|
+
showAlert('success', 'Demande validée.');
|
|
135
|
+
await loadPending();
|
|
136
|
+
} catch (e) {
|
|
137
|
+
showAlert('error', 'Validation impossible.');
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
return false;
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
wrap.appendChild(div);
|
|
74
147
|
}
|
|
75
148
|
}
|
|
76
149
|
|
|
150
|
+
|
|
77
151
|
function timeOptions(stepMinutes) {
|
|
78
152
|
const step = stepMinutes || 5;
|
|
79
153
|
const out = [];
|
package/public/client.js
CHANGED
|
@@ -7,6 +7,17 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
|
|
|
7
7
|
// interactions or quick re-renders.
|
|
8
8
|
let isDialogOpen = false;
|
|
9
9
|
|
|
10
|
+
function statusLabel(s) {
|
|
11
|
+
const map = {
|
|
12
|
+
pending: 'En attente',
|
|
13
|
+
awaiting_payment: 'Validée – paiement en attente',
|
|
14
|
+
paid: 'Payée',
|
|
15
|
+
rejected: 'Rejetée',
|
|
16
|
+
expired: 'Expirée',
|
|
17
|
+
};
|
|
18
|
+
return map[String(s || '')] || String(s || '');
|
|
19
|
+
}
|
|
20
|
+
|
|
10
21
|
function showAlert(type, msg) {
|
|
11
22
|
try {
|
|
12
23
|
if (alerts && typeof alerts[type] === 'function') {
|
|
@@ -234,7 +245,14 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
|
|
|
234
245
|
calendar.unselect();
|
|
235
246
|
isDialogOpen = false;
|
|
236
247
|
} catch (e) {
|
|
237
|
-
|
|
248
|
+
const code = String(e && e.message || '');
|
|
249
|
+
if (code === '403') {
|
|
250
|
+
showAlert('error', 'Impossible de créer la demande : droits insuffisants (groupe).');
|
|
251
|
+
} else if (code === '409') {
|
|
252
|
+
showAlert('error', 'Impossible : au moins un matériel est déjà réservé ou en attente sur cette période.');
|
|
253
|
+
} else {
|
|
254
|
+
showAlert('error', 'Erreur lors de la création de la demande.');
|
|
255
|
+
}
|
|
238
256
|
calendar.unselect();
|
|
239
257
|
isDialogOpen = false;
|
|
240
258
|
}
|
|
@@ -260,7 +278,7 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
|
|
|
260
278
|
const baseHtml = `
|
|
261
279
|
<div class="mb-2"><strong>Matériel</strong><br>${String(itemsLabel).replace(/</g, '<').replace(/>/g, '>')}</div>
|
|
262
280
|
<div class="mb-2"><strong>Période</strong><br>${period}</div>
|
|
263
|
-
<div class="text-muted" style="font-size: 12px;">Statut: ${
|
|
281
|
+
<div class="text-muted" style="font-size: 12px;">Statut: ${statusLabel(status)}</div>
|
|
264
282
|
`;
|
|
265
283
|
|
|
266
284
|
const showModeration = canModerate && isPending;
|
|
@@ -6,11 +6,12 @@
|
|
|
6
6
|
<li>Fin : {end}</li>
|
|
7
7
|
</ul>
|
|
8
8
|
<!-- IF pickupTime -->
|
|
9
|
-
<p><strong>Heure de récupération :</strong> {pickupTime}
|
|
10
|
-
<!--
|
|
9
|
+
<p><strong>Heure de récupération :</strong> {pickupTime}<!-- IF adminNote --> à {adminNote}<!-- ENDIF adminNote --></p>
|
|
10
|
+
<!-- ELSE -->
|
|
11
11
|
<!-- IF adminNote -->
|
|
12
|
-
<p><strong>
|
|
12
|
+
<p><strong>Heure de récupération :</strong> xx:xx à {adminNote}</p>
|
|
13
13
|
<!-- ENDIF adminNote -->
|
|
14
|
+
<!-- ENDIF pickupTime -->
|
|
14
15
|
<!-- IF paymentUrl -->
|
|
15
16
|
<p>Lien de paiement : <a href="{paymentUrl}">{paymentUrl}</a></p>
|
|
16
17
|
<!-- ENDIF paymentUrl -->
|