nodebb-plugin-onekite-calendar 1.0.24 → 1.0.26
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 +7 -7
- package/lib/api.js +66 -50
- package/lib/helloassoWebhook.js +4 -4
- package/lib/scheduler.js +4 -4
- package/library.js +7 -1
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/public/admin.js +3 -3
- package/public/client.js +9 -4
package/lib/admin.js
CHANGED
|
@@ -162,11 +162,11 @@ admin.approveReservation = async function (req, res) {
|
|
|
162
162
|
// User return/back/error URLs must be real pages; webhook uses the plugin endpoint.
|
|
163
163
|
callbackUrl: returnUrl,
|
|
164
164
|
webhookUrl: webhookUrl,
|
|
165
|
-
itemName: buildHelloAssoItemName('Réservation matériel Onekite',
|
|
165
|
+
itemName: buildHelloAssoItemName('Réservation matériel Onekite', r.itemNames || (r.itemName ? [r.itemName] : []), r.start, r.end),
|
|
166
166
|
containsDonation: false,
|
|
167
167
|
metadata: {
|
|
168
168
|
reservationId: String(rid),
|
|
169
|
-
items: (Array.isArray(r.itemNames) ? r.itemNames : []).filter(Boolean),
|
|
169
|
+
items: (Array.isArray(r.itemNames) ? r.itemNames : (r.itemName ? [r.itemName] : [])).filter(Boolean),
|
|
170
170
|
dateRange: `Du ${formatFR(r.start)} au ${formatFR(r.end)}`,
|
|
171
171
|
},
|
|
172
172
|
});
|
|
@@ -199,8 +199,8 @@ admin.approveReservation = async function (req, res) {
|
|
|
199
199
|
await sendEmail('calendar-onekite_approved', requesterUid, 'Location matériel - Réservation validée', {
|
|
200
200
|
uid: requesterUid,
|
|
201
201
|
username: requester && requester.username ? requester.username : '',
|
|
202
|
-
itemName: (Array.isArray(r.itemNames) ? r.itemNames.join(', ') : ''),
|
|
203
|
-
itemNames: (Array.isArray(r.itemNames) ? r.itemNames : []),
|
|
202
|
+
itemName: (Array.isArray(r.itemNames) ? r.itemNames.join(', ') : (r.itemName || '')),
|
|
203
|
+
itemNames: (Array.isArray(r.itemNames) ? r.itemNames : (r.itemName ? [r.itemName] : [])),
|
|
204
204
|
dateRange: `Du ${formatFR(r.start)} au ${formatFR(r.end)}`,
|
|
205
205
|
paymentUrl: paymentUrl || '',
|
|
206
206
|
pickupAddress: r.pickupAddress || '',
|
|
@@ -235,8 +235,8 @@ admin.refuseReservation = async function (req, res) {
|
|
|
235
235
|
await sendEmail('calendar-onekite_refused', requesterUid, 'Location matériel - Réservation refusée', {
|
|
236
236
|
uid: requesterUid,
|
|
237
237
|
username: requester && requester.username ? requester.username : '',
|
|
238
|
-
itemName: (Array.isArray(r.itemNames) ? r.itemNames.join(', ') : ''),
|
|
239
|
-
itemNames: (Array.isArray(r.itemNames) ? r.itemNames : []),
|
|
238
|
+
itemName: (Array.isArray(r.itemNames) ? r.itemNames.join(', ') : (r.itemName || '')),
|
|
239
|
+
itemNames: (Array.isArray(r.itemNames) ? r.itemNames : (r.itemName ? [r.itemName] : [])),
|
|
240
240
|
dateRange: `Du ${formatFR(r.start)} au ${formatFR(r.end)}`,
|
|
241
241
|
start: formatFR(r.start),
|
|
242
242
|
end: formatFR(r.end),
|
|
@@ -419,7 +419,7 @@ admin.getAccounting = async function (req, res) {
|
|
|
419
419
|
|
|
420
420
|
const itemNames = Array.isArray(r.itemNames) && r.itemNames.length
|
|
421
421
|
? r.itemNames
|
|
422
|
-
: [];
|
|
422
|
+
: (r.itemName ? [r.itemName] : []);
|
|
423
423
|
|
|
424
424
|
const total = Number(r.total) || 0;
|
|
425
425
|
const startDate = formatFR(r.start);
|
package/lib/api.js
CHANGED
|
@@ -328,18 +328,15 @@ function eventsFor(resv) {
|
|
|
328
328
|
const startIsoDate = new Date(parseInt(resv.start, 10)).toISOString().slice(0, 10);
|
|
329
329
|
const endIsoDate = new Date(parseInt(resv.end, 10)).toISOString().slice(0, 10);
|
|
330
330
|
|
|
331
|
-
const itemIds = Array.isArray(resv.itemIds) ? resv.itemIds.
|
|
332
|
-
const itemNames = Array.isArray(resv.itemNames) ? resv.itemNames.
|
|
331
|
+
const itemIds = Array.isArray(resv.itemIds) ? resv.itemIds : (resv.itemId ? [resv.itemId] : []);
|
|
332
|
+
const itemNames = Array.isArray(resv.itemNames) ? resv.itemNames : (resv.itemName ? [resv.itemName] : []);
|
|
333
333
|
|
|
334
334
|
// One line = one material: return one calendar event per item
|
|
335
|
-
if (!itemIds.length && !itemNames.length) {
|
|
336
|
-
return [];
|
|
337
|
-
}
|
|
338
335
|
const out = [];
|
|
339
|
-
const count = Math.max(itemIds.length, itemNames.length);
|
|
336
|
+
const count = Math.max(itemIds.length, itemNames.length, 1);
|
|
340
337
|
for (let i = 0; i < count; i++) {
|
|
341
|
-
const itemId = String(itemIds[i] || itemIds[0] ||
|
|
342
|
-
const itemName = String(itemNames[i] || itemNames[0] || itemId);
|
|
338
|
+
const itemId = String(itemIds[i] || itemIds[0] || resv.itemId || '');
|
|
339
|
+
const itemName = String(itemNames[i] || itemNames[0] || resv.itemName || itemId);
|
|
343
340
|
out.push({
|
|
344
341
|
// keep id unique per item for FullCalendar, but keep the real rid in extendedProps.rid
|
|
345
342
|
id: `${resv.rid}:${itemId || i}`,
|
|
@@ -356,14 +353,15 @@ function eventsFor(resv) {
|
|
|
356
353
|
uid: resv.uid,
|
|
357
354
|
approvedBy: resv.approvedBy || 0,
|
|
358
355
|
approvedByUsername: resv.approvedByUsername || '',
|
|
359
|
-
itemIds: itemIds,
|
|
360
|
-
itemNames: itemNames,
|
|
356
|
+
itemIds: itemIds.filter(Boolean),
|
|
357
|
+
itemNames: itemNames.filter(Boolean),
|
|
361
358
|
itemIdLine: itemId,
|
|
362
359
|
itemNameLine: itemName,
|
|
363
360
|
},
|
|
364
361
|
});
|
|
365
362
|
}
|
|
366
363
|
return out;
|
|
364
|
+
}
|
|
367
365
|
|
|
368
366
|
function eventsForSpecial(ev) {
|
|
369
367
|
const start = new Date(parseInt(ev.start, 10));
|
|
@@ -533,8 +531,8 @@ api.getReservationDetails = async function (req, res) {
|
|
|
533
531
|
status: r.status,
|
|
534
532
|
uid: r.uid,
|
|
535
533
|
username: r.username || '',
|
|
536
|
-
itemNames: Array.isArray(r.itemNames) ? r.itemNames : [],
|
|
537
|
-
itemIds: Array.isArray(r.itemIds) ? r.itemIds : [],
|
|
534
|
+
itemNames: Array.isArray(r.itemNames) ? r.itemNames : (r.itemName ? [r.itemName] : []),
|
|
535
|
+
itemIds: Array.isArray(r.itemIds) ? r.itemIds : (r.itemId ? [r.itemId] : []),
|
|
538
536
|
start: r.start,
|
|
539
537
|
end: r.end,
|
|
540
538
|
approvedByUsername: r.approvedByUsername || '',
|
|
@@ -698,9 +696,9 @@ api.createReservation = async function (req, res) {
|
|
|
698
696
|
return res.status(400).json({ error: "bad-dates" });
|
|
699
697
|
}
|
|
700
698
|
|
|
701
|
-
//
|
|
702
|
-
const itemIds = Array.isArray(req.body.itemIds) ? req.body.itemIds.map(String) : [];
|
|
703
|
-
const itemNames = Array.isArray(req.body.itemNames) ? req.body.itemNames.map(String) : [];
|
|
699
|
+
// Support both legacy single itemId and new itemIds[] payload
|
|
700
|
+
const itemIds = Array.isArray(req.body.itemIds) ? req.body.itemIds.map(String) : ((req.body.itemId ? [String(req.body.itemId)] : []));
|
|
701
|
+
const itemNames = Array.isArray(req.body.itemNames) ? req.body.itemNames.map(String) : (req.body.itemName ? [String(req.body.itemName)] : []);
|
|
704
702
|
|
|
705
703
|
const total = typeof req.body.total === 'number' ? req.body.total : parseFloat(String(req.body.total || '0'));
|
|
706
704
|
|
|
@@ -719,7 +717,7 @@ api.createReservation = async function (req, res) {
|
|
|
719
717
|
const exStart = parseInt(existing.start, 10);
|
|
720
718
|
const exEnd = parseInt(existing.end, 10);
|
|
721
719
|
if (!(exStart < end && start < exEnd)) continue;
|
|
722
|
-
const exItemIds = Array.isArray(existing.itemIds) ? existing.itemIds : [];
|
|
720
|
+
const exItemIds = Array.isArray(existing.itemIds) ? existing.itemIds : (existing.itemId ? [existing.itemId] : []);
|
|
723
721
|
const shared = exItemIds.filter(x => itemIds.includes(String(x)));
|
|
724
722
|
if (shared.length) {
|
|
725
723
|
conflicts.push({ rid: existing.rid, itemIds: shared, status: existing.status });
|
|
@@ -744,6 +742,9 @@ api.createReservation = async function (req, res) {
|
|
|
744
742
|
username: username || null,
|
|
745
743
|
itemIds,
|
|
746
744
|
itemNames: itemNames.length ? itemNames : itemIds,
|
|
745
|
+
// keep legacy fields for backward compatibility
|
|
746
|
+
itemId: itemIds[0],
|
|
747
|
+
itemName: (itemNames[0] || itemIds[0]),
|
|
747
748
|
start,
|
|
748
749
|
end,
|
|
749
750
|
status: 'pending',
|
|
@@ -754,35 +755,51 @@ api.createReservation = async function (req, res) {
|
|
|
754
755
|
// Save
|
|
755
756
|
await dbLayer.saveReservation(resv);
|
|
756
757
|
|
|
757
|
-
// Notify groups by email (NodeBB
|
|
758
|
+
// Notify groups by email (NodeBB emailer config)
|
|
758
759
|
try {
|
|
759
|
-
const notifyGroups =
|
|
760
|
-
.split(/[\n,;]+/)
|
|
761
|
-
.map(s => s.trim())
|
|
762
|
-
.filter(Boolean);
|
|
763
|
-
|
|
760
|
+
const notifyGroups = (settings.notifyGroups || '').split(',').map(s => s.trim()).filter(Boolean);
|
|
764
761
|
if (notifyGroups.length) {
|
|
765
|
-
const requester = await user.getUserFields(uid, ['username']);
|
|
766
|
-
const itemsLabel = (
|
|
767
|
-
|
|
762
|
+
const requester = await user.getUserFields(uid, ['username', 'email']);
|
|
763
|
+
const itemsLabel = (resv.itemNames || []).join(', ');
|
|
768
764
|
for (const g of notifyGroups) {
|
|
769
765
|
const members = await getMembersByGroupIdentifier(g);
|
|
770
766
|
const uids = normalizeUids(members);
|
|
771
767
|
|
|
772
|
-
|
|
768
|
+
// Batch fetch user email/username when supported by this NodeBB version.
|
|
769
|
+
let usersData = [];
|
|
770
|
+
try {
|
|
771
|
+
if (typeof user.getUsersFields === 'function') {
|
|
772
|
+
usersData = await user.getUsersFields(uids, ['username', 'email']);
|
|
773
|
+
// Some NodeBB versions omit uid in returned rows; re-attach it from input order.
|
|
774
|
+
if (Array.isArray(usersData)) {
|
|
775
|
+
usersData = usersData.map((row, idx) => (row ? Object.assign({ uid: uids[idx] }, row) : null));
|
|
776
|
+
}
|
|
777
|
+
} else {
|
|
778
|
+
usersData = await Promise.all(uids.map(async (memberUid) => {
|
|
779
|
+
try { return await user.getUserFields(memberUid, ['username', 'email']); }
|
|
780
|
+
catch (e) { return null; }
|
|
781
|
+
}));
|
|
782
|
+
}
|
|
783
|
+
} catch (e) {
|
|
784
|
+
usersData = [];
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
for (const md of (usersData || [])) {
|
|
788
|
+
const memberUid = md && (md.uid || md.userId || md.userid || md.user_id);
|
|
773
789
|
const u = parseInt(memberUid, 10);
|
|
774
|
-
if (
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
790
|
+
if (Number.isInteger(u) && u > 0) {
|
|
791
|
+
await sendEmail('calendar-onekite_pending', u, 'Location matériel - Demande de réservation', {
|
|
792
|
+
uid: u,
|
|
793
|
+
username: md && md.username ? md.username : '',
|
|
794
|
+
requester: requester.username,
|
|
795
|
+
itemName: itemsLabel,
|
|
796
|
+
itemNames: resv.itemNames || [],
|
|
797
|
+
dateRange: `Du ${formatFR(start)} au ${formatFR(end)}`,
|
|
798
|
+
start: formatFR(start),
|
|
799
|
+
end: formatFR(end),
|
|
800
|
+
total: resv.total || 0,
|
|
801
|
+
});
|
|
802
|
+
}
|
|
786
803
|
}
|
|
787
804
|
}
|
|
788
805
|
}
|
|
@@ -796,8 +813,8 @@ api.createReservation = async function (req, res) {
|
|
|
796
813
|
rid: resv.rid,
|
|
797
814
|
uid: resv.uid,
|
|
798
815
|
username: resv.username || '',
|
|
799
|
-
itemIds: itemIds || [],
|
|
800
|
-
itemNames: itemNames || [],
|
|
816
|
+
itemIds: resv.itemIds || [],
|
|
817
|
+
itemNames: resv.itemNames || [],
|
|
801
818
|
start: resv.start,
|
|
802
819
|
end: resv.end,
|
|
803
820
|
status: resv.status,
|
|
@@ -821,7 +838,8 @@ api.approveReservation = async function (req, res) {
|
|
|
821
838
|
if (r.status !== 'pending') return res.status(400).json({ error: 'bad-status' });
|
|
822
839
|
|
|
823
840
|
r.status = 'awaiting_payment';
|
|
824
|
-
|
|
841
|
+
// Backwards compatible: old clients sent `adminNote` to describe the pickup place.
|
|
842
|
+
r.pickupAddress = String((req.body && (req.body.pickupAddress || req.body.adminNote)) || '').trim();
|
|
825
843
|
r.notes = String((req.body && req.body.notes) || '').trim();
|
|
826
844
|
r.pickupTime = String((req.body && req.body.pickupTime) || '').trim();
|
|
827
845
|
r.pickupLat = String((req.body && req.body.pickupLat) || '').trim();
|
|
@@ -861,11 +879,11 @@ api.approveReservation = async function (req, res) {
|
|
|
861
879
|
// Can be overridden via ACP setting `helloassoCallbackUrl`.
|
|
862
880
|
callbackUrl: normalizeReturnUrl(meta),
|
|
863
881
|
webhookUrl: normalizeCallbackUrl(settings2.helloassoCallbackUrl, meta),
|
|
864
|
-
itemName: buildHelloAssoItemName('',
|
|
882
|
+
itemName: buildHelloAssoItemName('', r.itemNames || (r.itemName ? [r.itemName] : []), r.start, r.end),
|
|
865
883
|
containsDonation: false,
|
|
866
884
|
metadata: {
|
|
867
885
|
reservationId: String(rid),
|
|
868
|
-
items: (Array.isArray(r.itemNames) ? r.itemNames : []).filter(Boolean),
|
|
886
|
+
items: (Array.isArray(r.itemNames) ? r.itemNames : (r.itemName ? [r.itemName] : [])).filter(Boolean),
|
|
869
887
|
dateRange: `Du ${formatFR(r.start)} au ${formatFR(r.end)}`,
|
|
870
888
|
},
|
|
871
889
|
});
|
|
@@ -897,8 +915,8 @@ api.approveReservation = async function (req, res) {
|
|
|
897
915
|
await sendEmail('calendar-onekite_approved', requesterUid, 'Location matériel - Réservation validée', {
|
|
898
916
|
uid: requesterUid,
|
|
899
917
|
username: requester && requester.username ? requester.username : '',
|
|
900
|
-
itemName: (Array.isArray(r.itemNames) ? r.itemNames.join(', ') : ''),
|
|
901
|
-
itemNames: (Array.isArray(r.itemNames) ? r.itemNames : []),
|
|
918
|
+
itemName: (Array.isArray(r.itemNames) ? r.itemNames.join(', ') : (r.itemName || '')),
|
|
919
|
+
itemNames: (Array.isArray(r.itemNames) ? r.itemNames : (r.itemName ? [r.itemName] : [])),
|
|
902
920
|
dateRange: `Du ${formatFR(r.start)} au ${formatFR(r.end)}`,
|
|
903
921
|
start: formatFR(r.start),
|
|
904
922
|
end: formatFR(r.end),
|
|
@@ -938,8 +956,8 @@ api.refuseReservation = async function (req, res) {
|
|
|
938
956
|
await sendEmail('calendar-onekite_refused', requesterUid2, 'Location matériel - Demande de réservation', {
|
|
939
957
|
uid: requesterUid2,
|
|
940
958
|
username: requester && requester.username ? requester.username : '',
|
|
941
|
-
itemName: (Array.isArray(r.itemNames) ? r.itemNames.join(', ') : ''),
|
|
942
|
-
itemNames: (Array.isArray(r.itemNames) ? r.itemNames : []),
|
|
959
|
+
itemName: (Array.isArray(r.itemNames) ? r.itemNames.join(', ') : (r.itemName || '')),
|
|
960
|
+
itemNames: (Array.isArray(r.itemNames) ? r.itemNames : (r.itemName ? [r.itemName] : [])),
|
|
943
961
|
dateRange: `Du ${formatFR(r.start)} au ${formatFR(r.end)}`,
|
|
944
962
|
start: formatFR(r.start),
|
|
945
963
|
end: formatFR(r.end),
|
|
@@ -1000,6 +1018,4 @@ api.cancelReservation = async function (req, res) {
|
|
|
1000
1018
|
return res.json({ ok: true, status: 'cancelled' });
|
|
1001
1019
|
};
|
|
1002
1020
|
|
|
1003
|
-
module.exports = api;
|
|
1004
|
-
|
|
1005
|
-
}
|
|
1021
|
+
module.exports = api;
|
package/lib/helloassoWebhook.js
CHANGED
|
@@ -330,8 +330,8 @@ async function handler(req, res, next) {
|
|
|
330
330
|
await sendEmail('calendar-onekite_paid', requesterUid, 'Location matériel - Paiement reçu', {
|
|
331
331
|
uid: requesterUid,
|
|
332
332
|
username: requester && requester.username ? requester.username : '',
|
|
333
|
-
itemName: (Array.isArray(r.itemNames) ? r.itemNames.join(', ') : ''),
|
|
334
|
-
itemNames: (Array.isArray(r.itemNames) ? r.itemNames : []),
|
|
333
|
+
itemName: (Array.isArray(r.itemNames) ? r.itemNames.join(', ') : (r.itemName || '')),
|
|
334
|
+
itemNames: (Array.isArray(r.itemNames) ? r.itemNames : (r.itemName ? [r.itemName] : [])),
|
|
335
335
|
dateRange: `Du ${formatFR(r.start)} au ${formatFR(r.end)}`,
|
|
336
336
|
paymentReceiptUrl: r.paymentReceiptUrl || '',
|
|
337
337
|
});
|
|
@@ -343,8 +343,8 @@ async function handler(req, res, next) {
|
|
|
343
343
|
rid: r.rid,
|
|
344
344
|
uid: r.uid,
|
|
345
345
|
username: (requester && requester.username) ? requester.username : (r.username || ''),
|
|
346
|
-
itemIds:
|
|
347
|
-
itemNames:
|
|
346
|
+
itemIds: r.itemIds || (r.itemId ? [r.itemId] : []),
|
|
347
|
+
itemNames: r.itemNames || (r.itemName ? [r.itemName] : []),
|
|
348
348
|
start: r.start,
|
|
349
349
|
end: r.end,
|
|
350
350
|
status: r.status,
|
package/lib/scheduler.js
CHANGED
|
@@ -118,8 +118,8 @@ async function processAwaitingPayment() {
|
|
|
118
118
|
await sendEmail('calendar-onekite_reminder', toUid, 'Location matériel - Rappel', {
|
|
119
119
|
uid: toUid,
|
|
120
120
|
username: (u && u.username) ? u.username : '',
|
|
121
|
-
itemName: (Array.isArray(r.itemNames) ? r.itemNames.join(', ') : ''),
|
|
122
|
-
itemNames: (Array.isArray(r.itemNames) ? r.itemNames : []),
|
|
121
|
+
itemName: (Array.isArray(r.itemNames) ? r.itemNames.join(', ') : (r.itemName || '')),
|
|
122
|
+
itemNames: (Array.isArray(r.itemNames) ? r.itemNames : (r.itemName ? [r.itemName] : [])),
|
|
123
123
|
dateRange: `Du ${formatFR(r.start)} au ${formatFR(r.end)}`,
|
|
124
124
|
paymentUrl: r.paymentUrl || '',
|
|
125
125
|
delayMinutes: holdMins,
|
|
@@ -152,8 +152,8 @@ async function processAwaitingPayment() {
|
|
|
152
152
|
await sendEmail('calendar-onekite_expired', toUid, 'Location matériel - Rappel', {
|
|
153
153
|
uid: toUid,
|
|
154
154
|
username: (u && u.username) ? u.username : '',
|
|
155
|
-
itemName: (Array.isArray(r.itemNames) ? r.itemNames.join(', ') : ''),
|
|
156
|
-
itemNames: (Array.isArray(r.itemNames) ? r.itemNames : []),
|
|
155
|
+
itemName: (Array.isArray(r.itemNames) ? r.itemNames.join(', ') : (r.itemName || '')),
|
|
156
|
+
itemNames: (Array.isArray(r.itemNames) ? r.itemNames : (r.itemName ? [r.itemName] : [])),
|
|
157
157
|
dateRange: `Du ${formatFR(r.start)} au ${formatFR(r.end)}`,
|
|
158
158
|
delayMinutes: holdMins,
|
|
159
159
|
});
|
package/library.js
CHANGED
|
@@ -109,8 +109,13 @@ Plugin.init = async function (params) {
|
|
|
109
109
|
},
|
|
110
110
|
type: ['application/json', 'application/*+json'],
|
|
111
111
|
});
|
|
112
|
+
// Accept webhook on both legacy root path and namespaced plugin path.
|
|
113
|
+
// Some reverse proxies block unknown root paths, so /plugins/... is recommended.
|
|
114
|
+
router.post('/helloasso', helloassoJson, helloassoWebhook.handler);
|
|
112
115
|
router.post('/plugins/calendar-onekite/helloasso', helloassoJson, helloassoWebhook.handler);
|
|
113
116
|
|
|
117
|
+
// Optional: health checks
|
|
118
|
+
router.get('/helloasso', (req, res) => res.json({ ok: true }));
|
|
114
119
|
router.get('/plugins/calendar-onekite/helloasso', (req, res) => res.json({ ok: true }));
|
|
115
120
|
|
|
116
121
|
scheduler.start();
|
|
@@ -128,7 +133,8 @@ Plugin.addAdminNavigation = async function (header) {
|
|
|
128
133
|
|
|
129
134
|
|
|
130
135
|
// Ensure our transactional emails always get a subject.
|
|
131
|
-
//
|
|
136
|
+
// NodeBB's Emailer.sendToEmail signature expects (template, email, language, params),
|
|
137
|
+
// so plugins typically inject/modify the subject via this hook.
|
|
132
138
|
Plugin.emailModify = async function (data) {
|
|
133
139
|
try {
|
|
134
140
|
if (!data || !data.template) return data;
|
package/package.json
CHANGED
package/plugin.json
CHANGED
package/public/admin.js
CHANGED
|
@@ -221,14 +221,14 @@ define('admin/plugins/calendar-onekite', ['alerts', 'bootbox'], function (alerts
|
|
|
221
221
|
for (const r of list) {
|
|
222
222
|
if (r && r.rid) pendingCache.set(String(r.rid), r);
|
|
223
223
|
const created = r.createdAt ? fmtFR(r.createdAt) : '';
|
|
224
|
-
const itemNames = Array.isArray(r.itemNames) ? r.itemNames : [];
|
|
224
|
+
const itemNames = Array.isArray(r.itemNames) && r.itemNames.length ? r.itemNames : [r.itemName || r.itemId].filter(Boolean);
|
|
225
225
|
const itemsHtml = `<ul style="margin: 0 0 10px 18px;">${itemNames.map(n => `<li>${escapeHtml(String(n))}</li>`).join('')}</ul>`;
|
|
226
226
|
const div = document.createElement('div');
|
|
227
227
|
div.className = 'list-group-item onekite-pending-row';
|
|
228
228
|
div.innerHTML = `
|
|
229
229
|
<div class="d-flex justify-content-between align-items-start gap-2">
|
|
230
230
|
<div style="min-width: 0;">
|
|
231
|
-
<div><strong>${itemsHtml}</strong></div>
|
|
231
|
+
<div><strong>${itemsHtml || escapeHtml(r.itemName || '')}</strong></div>
|
|
232
232
|
<div class="text-muted" style="font-size: 12px;">Créée: ${escapeHtml(created)}</div>
|
|
233
233
|
<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>
|
|
234
234
|
</div>
|
|
@@ -492,7 +492,7 @@ define('admin/plugins/calendar-onekite', ['alerts', 'bootbox'], function (alerts
|
|
|
492
492
|
? r.itemNames
|
|
493
493
|
: (typeof r.itemNames === 'string' && r.itemNames.trim()
|
|
494
494
|
? r.itemNames.split(',').map(s => s.trim()).filter(Boolean)
|
|
495
|
-
: ([]));
|
|
495
|
+
: ([r.itemName || r.itemId].filter(Boolean)));
|
|
496
496
|
const itemsListHtml = itemNames.length
|
|
497
497
|
? `<div class="mb-2"><strong>Matériel</strong><ul style="margin:0.25rem 0 0 1.1rem; padding:0;">${itemNames.map(n => `<li>${escapeHtml(String(n))}</li>`).join('')}</ul></div>`
|
|
498
498
|
: '';
|
package/public/client.js
CHANGED
|
@@ -787,7 +787,7 @@ function toDatetimeLocalValue(date) {
|
|
|
787
787
|
(evs || []).forEach((ev) => {
|
|
788
788
|
const st = (ev.extendedProps && ev.extendedProps.status) || '';
|
|
789
789
|
if (!['pending', 'awaiting_payment', 'approved', 'paid'].includes(st)) return;
|
|
790
|
-
const ids = (ev.extendedProps && ev.extendedProps.itemIds) || [];
|
|
790
|
+
const ids = (ev.extendedProps && ev.extendedProps.itemIds) || (ev.extendedProps && ev.extendedProps.itemId ? [ev.extendedProps.itemId] : []);
|
|
791
791
|
ids.forEach((id) => blocked.add(String(id)));
|
|
792
792
|
});
|
|
793
793
|
} catch (e) {}
|
|
@@ -1300,7 +1300,12 @@ function toDatetimeLocalValue(date) {
|
|
|
1300
1300
|
p = Object.assign({}, p0, details);
|
|
1301
1301
|
} else if (p0.type === 'special' && p0.eid) {
|
|
1302
1302
|
const details = await fetchJson(`/api/v3/plugins/calendar-onekite/special-events/${encodeURIComponent(String(p0.eid))}`);
|
|
1303
|
-
p = Object.assign({}, p0, details
|
|
1303
|
+
p = Object.assign({}, p0, details, {
|
|
1304
|
+
// keep backward compat with older field names used by templates below
|
|
1305
|
+
pickupAddress: details.address || details.pickupAddress || p0.pickupAddress,
|
|
1306
|
+
pickupLat: details.lat || details.pickupLat || p0.pickupLat,
|
|
1307
|
+
pickupLon: details.lon || details.pickupLon || p0.pickupLon,
|
|
1308
|
+
});
|
|
1304
1309
|
}
|
|
1305
1310
|
} catch (e) {
|
|
1306
1311
|
// ignore detail fetch errors; fall back to minimal props
|
|
@@ -1365,7 +1370,7 @@ function toDatetimeLocalValue(date) {
|
|
|
1365
1370
|
? `<div class="mb-2"><strong>Réservée par</strong><br><a class="onekite-user-link" href="${window.location.origin}/user/${encodeURIComponent(username)}">${escapeHtml(username)}</a></div>`
|
|
1366
1371
|
: '';
|
|
1367
1372
|
const itemsHtml = (() => {
|
|
1368
|
-
const names = Array.isArray(p.itemNames) ? p.itemNames : (typeof p.itemNames === 'string' && p.itemNames.trim() ? p.itemNames.split(',').map(s=>s.trim()).filter(Boolean) : []);
|
|
1373
|
+
const names = Array.isArray(p.itemNames) ? p.itemNames : (typeof p.itemNames === 'string' && p.itemNames.trim() ? p.itemNames.split(',').map(s=>s.trim()).filter(Boolean) : (p.itemName ? [p.itemName] : []));
|
|
1369
1374
|
if (names.length) {
|
|
1370
1375
|
return `<ul style="margin:0 0 0 1.1rem; padding:0;">${names.map(n => `<li>${String(n).replace(/</g,'<').replace(/>/g,'>')}</li>`).join('')}</ul>`;
|
|
1371
1376
|
}
|
|
@@ -1500,7 +1505,7 @@ function toDatetimeLocalValue(date) {
|
|
|
1500
1505
|
callback: async () => {
|
|
1501
1506
|
const itemNames = Array.isArray(p.itemNames) && p.itemNames.length
|
|
1502
1507
|
? p.itemNames
|
|
1503
|
-
: (typeof p.itemNames === 'string' && p.itemNames.trim() ? p.itemNames.split(',').map(s=>s.trim()).filter(Boolean) : []);
|
|
1508
|
+
: (typeof p.itemNames === 'string' && p.itemNames.trim() ? p.itemNames.split(',').map(s=>s.trim()).filter(Boolean) : (p.itemName ? [p.itemName] : []));
|
|
1504
1509
|
const itemsListHtml = itemNames.length
|
|
1505
1510
|
? `<div class="mb-2"><strong>Matériel</strong><ul style="margin:0.25rem 0 0 1.1rem; padding:0;">${itemNames.map(n => `<li>${String(n).replace(/</g, '<').replace(/>/g, '>')}</li>`).join('')}</ul></div>`
|
|
1506
1511
|
: '';
|