nodebb-plugin-onekite-calendar 2.0.86 → 2.0.88
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 +30 -6
- package/lib/api.js +28 -9
- package/lib/db.js +24 -0
- package/package.json +1 -1
- package/public/admin.js +30 -1
- package/templates/admin/plugins/calendar-onekite.tpl +1 -1
package/lib/admin.js
CHANGED
|
@@ -180,6 +180,19 @@ admin.approveReservation = async function (req, res) {
|
|
|
180
180
|
|
|
181
181
|
await dbLayer.saveReservation(r);
|
|
182
182
|
|
|
183
|
+
// Audit
|
|
184
|
+
try {
|
|
185
|
+
const year = new Date().getFullYear();
|
|
186
|
+
await dbLayer.addAuditEntry({
|
|
187
|
+
ts: Date.now(), year, action: 'reservation_approved',
|
|
188
|
+
targetType: 'reservation', targetId: String(rid),
|
|
189
|
+
reservationUid: Number(r.uid) || 0,
|
|
190
|
+
reservationUsername: String(r.username || ''),
|
|
191
|
+
actorUid: req.uid || 0,
|
|
192
|
+
actorUsername: r.approvedByUsername || '',
|
|
193
|
+
});
|
|
194
|
+
} catch (e) {}
|
|
195
|
+
|
|
183
196
|
// Real-time refresh for all viewers
|
|
184
197
|
realtime.emitCalendarUpdated({ kind: 'reservation', action: 'approved', rid: String(rid), status: r.status });
|
|
185
198
|
|
|
@@ -259,7 +272,6 @@ admin.markReservationPaid = async function (req, res) {
|
|
|
259
272
|
const requesterUid = parseInt(r.uid, 10);
|
|
260
273
|
const requester = await user.getUserFields(requesterUid, ['username']);
|
|
261
274
|
if (requesterUid) {
|
|
262
|
-
const { sendEmail, formatFR, buildCalendarLinks } = shared;
|
|
263
275
|
await sendEmail('calendar-onekite_paid', requesterUid, 'Location matériel - Paiement reçu', {
|
|
264
276
|
uid: requesterUid,
|
|
265
277
|
username: requester && requester.username ? requester.username : '',
|
|
@@ -294,6 +306,19 @@ admin.refuseReservation = async function (req, res) {
|
|
|
294
306
|
r.refusedReason = String((req.body && (req.body.reason || req.body.refusedReason || req.body.refuseReason)) || '').trim();
|
|
295
307
|
await dbLayer.saveReservation(r);
|
|
296
308
|
|
|
309
|
+
// Audit
|
|
310
|
+
try {
|
|
311
|
+
const year = new Date().getFullYear();
|
|
312
|
+
await dbLayer.addAuditEntry({
|
|
313
|
+
ts: Date.now(), year, action: 'reservation_refused',
|
|
314
|
+
targetType: 'reservation', targetId: String(rid),
|
|
315
|
+
reservationUid: Number(r.uid) || 0,
|
|
316
|
+
reservationUsername: String(r.username || ''),
|
|
317
|
+
actorUid: req.uid || 0,
|
|
318
|
+
refusedReason: r.refusedReason || '',
|
|
319
|
+
});
|
|
320
|
+
} catch (e) {}
|
|
321
|
+
|
|
297
322
|
// Real-time refresh for all viewers
|
|
298
323
|
realtime.emitCalendarUpdated({ kind: 'reservation', action: 'refused', rid: String(rid), status: r.status });
|
|
299
324
|
|
|
@@ -327,12 +352,11 @@ admin.purgeByYear = async function (req, res) {
|
|
|
327
352
|
const endTs = new Date(Date.UTC(y + 1, 0, 1)).getTime() - 1;
|
|
328
353
|
|
|
329
354
|
const ids = await dbLayer.listReservationIdsByStartRange(startTs, endTs, 100000);
|
|
355
|
+
const rows = ids && ids.length ? await dbLayer.getReservations(ids) : [];
|
|
330
356
|
let removed = 0;
|
|
331
|
-
for (
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
await dbLayer.removeReservation(rid);
|
|
357
|
+
for (let i = 0; i < ids.length; i++) {
|
|
358
|
+
if (!rows[i]) continue;
|
|
359
|
+
await dbLayer.removeReservation(ids[i]);
|
|
336
360
|
removed++;
|
|
337
361
|
}
|
|
338
362
|
try {
|
package/lib/api.js
CHANGED
|
@@ -1192,8 +1192,15 @@ api.getItems = async function (req, res) {
|
|
|
1192
1192
|
maint = new Set((ids || []).map(String));
|
|
1193
1193
|
} catch (e) {}
|
|
1194
1194
|
|
|
1195
|
+
const itemIds = normalized.map((it) => String(it.id));
|
|
1196
|
+
let locations = {};
|
|
1197
|
+
try {
|
|
1198
|
+
locations = await dbLayer.getMaintenanceLocations(itemIds);
|
|
1199
|
+
} catch (e) {}
|
|
1200
|
+
|
|
1195
1201
|
const out = normalized.map((it) => Object.assign({}, it, {
|
|
1196
1202
|
maintenance: maint.has(String(it.id)),
|
|
1203
|
+
location: locations[String(it.id)] || '',
|
|
1197
1204
|
}));
|
|
1198
1205
|
|
|
1199
1206
|
res.json(out);
|
|
@@ -1963,15 +1970,27 @@ api.setMaintenance = async function (req, res) {
|
|
|
1963
1970
|
if (!ok) return res.status(403).json({ error: 'not-allowed' });
|
|
1964
1971
|
const itemId = String(req.params.itemId || '').trim();
|
|
1965
1972
|
if (!itemId) return res.status(400).json({ error: 'missing-itemId' });
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1973
|
+
|
|
1974
|
+
const hasEnabled = req.body && 'enabled' in req.body;
|
|
1975
|
+
const hasLocation = req.body && 'location' in req.body;
|
|
1976
|
+
if (!hasEnabled && !hasLocation) return res.status(400).json({ error: 'missing-fields' });
|
|
1977
|
+
|
|
1978
|
+
if (hasLocation) {
|
|
1979
|
+
await dbLayer.setMaintenanceLocation(itemId, String(req.body.location || ''));
|
|
1980
|
+
}
|
|
1981
|
+
|
|
1982
|
+
if (hasEnabled) {
|
|
1983
|
+
const enabled = !!(req.body.enabled === true || req.body.enabled === '1' || req.body.enabled === 1);
|
|
1984
|
+
await dbLayer.setItemMaintenance(itemId, enabled);
|
|
1985
|
+
await auditLog(enabled ? 'maintenance_on' : 'maintenance_off', uid, {
|
|
1986
|
+
targetType: 'item',
|
|
1987
|
+
targetId: itemId,
|
|
1988
|
+
});
|
|
1989
|
+
// Maintenance impacts availability; ask clients to refetch.
|
|
1990
|
+
realtime.emitCalendarUpdated({ kind: 'maintenance', action: enabled ? 'on' : 'off', itemId });
|
|
1991
|
+
}
|
|
1992
|
+
|
|
1993
|
+
return res.json({ ok: true, itemId });
|
|
1975
1994
|
};
|
|
1976
1995
|
|
|
1977
1996
|
// Bulk toggle: enable/disable maintenance for ALL catalog items.
|
package/lib/db.js
CHANGED
|
@@ -16,6 +16,7 @@ const KEY_OUTING_OBJ = (oid) => `calendar-onekite:outings:${oid}`;
|
|
|
16
16
|
|
|
17
17
|
// Maintenance (simple ON/OFF per item, no dates)
|
|
18
18
|
const KEY_MAINTENANCE_ZSET = 'calendar-onekite:maintenance:itemIds';
|
|
19
|
+
const KEY_MAINTENANCE_META = (itemId) => `calendar-onekite:maintenance:meta:${itemId}`;
|
|
19
20
|
|
|
20
21
|
// Audit log (partitioned by year)
|
|
21
22
|
const KEY_AUDIT_ZSET = (year) => `calendar-onekite:audit:${year}`;
|
|
@@ -103,6 +104,27 @@ async function setItemMaintenance(itemId, enabled) {
|
|
|
103
104
|
}
|
|
104
105
|
}
|
|
105
106
|
|
|
107
|
+
async function getMaintenanceLocations(itemIds) {
|
|
108
|
+
if (!Array.isArray(itemIds) || !itemIds.length) return {};
|
|
109
|
+
const keys = itemIds.map((id) => KEY_MAINTENANCE_META(String(id)));
|
|
110
|
+
let metas = [];
|
|
111
|
+
try {
|
|
112
|
+
metas = await db.getObjects(keys);
|
|
113
|
+
} catch (e) {}
|
|
114
|
+
const result = {};
|
|
115
|
+
itemIds.forEach((id, i) => {
|
|
116
|
+
const meta = metas && metas[i];
|
|
117
|
+
result[String(id)] = (meta && meta.location) ? String(meta.location) : '';
|
|
118
|
+
});
|
|
119
|
+
return result;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function setMaintenanceLocation(itemId, location) {
|
|
123
|
+
const id = String(itemId || '').trim();
|
|
124
|
+
if (!id) return;
|
|
125
|
+
await db.setObjectField(KEY_MAINTENANCE_META(id), 'location', String(location || '').trim());
|
|
126
|
+
}
|
|
127
|
+
|
|
106
128
|
async function setAllMaintenance(enabled, itemIds) {
|
|
107
129
|
// Clear set first (fast)
|
|
108
130
|
await db.delete(KEY_MAINTENANCE_ZSET);
|
|
@@ -242,6 +264,8 @@ module.exports = {
|
|
|
242
264
|
isItemInMaintenance,
|
|
243
265
|
setItemMaintenance,
|
|
244
266
|
setAllMaintenance,
|
|
267
|
+
getMaintenanceLocations,
|
|
268
|
+
setMaintenanceLocation,
|
|
245
269
|
|
|
246
270
|
// Audit
|
|
247
271
|
addAuditEntry,
|
package/package.json
CHANGED
package/public/admin.js
CHANGED
|
@@ -1518,15 +1518,17 @@ define('admin/plugins/calendar-onekite', ['alerts', 'bootbox'], function (alerts
|
|
|
1518
1518
|
const id = String(it.id);
|
|
1519
1519
|
const name = String(it.name || '').replace(/</g, '<').replace(/>/g, '>');
|
|
1520
1520
|
const checked = it.maintenance ? 'checked' : '';
|
|
1521
|
+
const location = String(it.location || '').replace(/"/g, '"').replace(/</g, '<').replace(/>/g, '>');
|
|
1521
1522
|
return `<tr data-itemid="${id}">
|
|
1522
1523
|
<td>${name}</td>
|
|
1524
|
+
<td><input type="text" class="form-control form-control-sm onekite-maint-location" value="${location}" placeholder="Emplacement..." style="min-width:180px;"></td>
|
|
1523
1525
|
<td>
|
|
1524
1526
|
<div class="form-check form-switch">
|
|
1525
1527
|
<input class="form-check-input onekite-maint-toggle" type="checkbox" ${checked} />
|
|
1526
1528
|
</div>
|
|
1527
1529
|
</td>
|
|
1528
1530
|
</tr>`;
|
|
1529
|
-
}).join('') || '<tr><td colspan="
|
|
1531
|
+
}).join('') || '<tr><td colspan="3" class="text-muted">Aucun matériel.</td></tr>';
|
|
1530
1532
|
}
|
|
1531
1533
|
|
|
1532
1534
|
async function refreshMaintenance() {
|
|
@@ -1585,6 +1587,33 @@ define('admin/plugins/calendar-onekite', ['alerts', 'bootbox'], function (alerts
|
|
|
1585
1587
|
showAlert('error', 'Impossible de modifier la maintenance.');
|
|
1586
1588
|
}
|
|
1587
1589
|
});
|
|
1590
|
+
|
|
1591
|
+
maintTableBody.addEventListener('input', (ev) => {
|
|
1592
|
+
const el = ev && ev.target;
|
|
1593
|
+
if (!el || !el.classList || !el.classList.contains('onekite-maint-location')) return;
|
|
1594
|
+
const tr = el.closest('tr');
|
|
1595
|
+
const itemId = tr ? tr.getAttribute('data-itemid') : '';
|
|
1596
|
+
if (!itemId) return;
|
|
1597
|
+
// Keep cache in sync so re-renders preserve typed value
|
|
1598
|
+
maintItemsCache = maintItemsCache.map((it) => (String(it.id) === String(itemId) ? Object.assign({}, it, { location: el.value }) : it));
|
|
1599
|
+
});
|
|
1600
|
+
|
|
1601
|
+
maintTableBody.addEventListener('focusout', async (ev) => {
|
|
1602
|
+
const el = ev && ev.target;
|
|
1603
|
+
if (!el || !el.classList || !el.classList.contains('onekite-maint-location')) return;
|
|
1604
|
+
const tr = el.closest('tr');
|
|
1605
|
+
const itemId = tr ? tr.getAttribute('data-itemid') : '';
|
|
1606
|
+
if (!itemId) return;
|
|
1607
|
+
const location = String(el.value || '');
|
|
1608
|
+
try {
|
|
1609
|
+
await fetchJson(`/api/v3/plugins/calendar-onekite/maintenance/${encodeURIComponent(String(itemId))}`, {
|
|
1610
|
+
method: 'PUT',
|
|
1611
|
+
body: JSON.stringify({ location }),
|
|
1612
|
+
});
|
|
1613
|
+
} catch (e) {
|
|
1614
|
+
showAlert('error', 'Impossible de sauvegarder l\'emplacement.');
|
|
1615
|
+
}
|
|
1616
|
+
});
|
|
1588
1617
|
}
|
|
1589
1618
|
|
|
1590
1619
|
// Load once
|
|
@@ -314,7 +314,7 @@
|
|
|
314
314
|
<div class="table-responsive">
|
|
315
315
|
<table class="table table-sm" id="onekite-maint-table">
|
|
316
316
|
<thead>
|
|
317
|
-
<tr><th>Matériel</th><th style="width: 140px;">Maintenance</th></tr>
|
|
317
|
+
<tr><th>Matériel</th><th>Emplacement</th><th style="width: 140px;">Maintenance</th></tr>
|
|
318
318
|
</thead>
|
|
319
319
|
<tbody></tbody>
|
|
320
320
|
</table>
|