nodebb-plugin-calendar-onekite 11.1.63 → 11.1.65
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 +42 -0
- package/lib/helloassoWebhook.js +13 -0
- package/library.js +1 -0
- package/package.json +2 -2
- package/plugin.json +2 -2
- package/public/admin.js +27 -0
- package/templates/admin/plugins/calendar-onekite.tpl +1 -0
package/lib/admin.js
CHANGED
|
@@ -348,6 +348,7 @@ admin.getAccounting = async function (req, res) {
|
|
|
348
348
|
const r = await dbLayer.getReservation(rid);
|
|
349
349
|
if (!r) continue;
|
|
350
350
|
if (String(r.status) !== 'paid') continue;
|
|
351
|
+
if (r.accPurgedAt) continue;
|
|
351
352
|
const start = parseInt(r.start, 10);
|
|
352
353
|
if (!Number.isFinite(start)) continue;
|
|
353
354
|
if (start < minTs || start >= maxTs) continue;
|
|
@@ -434,4 +435,45 @@ admin.exportAccountingCsv = async function (req, res) {
|
|
|
434
435
|
return res.send(csv);
|
|
435
436
|
};
|
|
436
437
|
|
|
438
|
+
|
|
439
|
+
admin.purgeAccounting = async function (req, res) {
|
|
440
|
+
const qFrom = String((req.query && req.query.from) || '').trim();
|
|
441
|
+
const qTo = String((req.query && req.query.to) || '').trim();
|
|
442
|
+
const parseDay = (s) => {
|
|
443
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(s)) return null;
|
|
444
|
+
const [y, m, d] = s.split('-').map((x) => parseInt(x, 10));
|
|
445
|
+
const dt = new Date(Date.UTC(y, m - 1, d));
|
|
446
|
+
const ts = dt.getTime();
|
|
447
|
+
return Number.isFinite(ts) ? ts : null;
|
|
448
|
+
};
|
|
449
|
+
const fromTs = parseDay(qFrom);
|
|
450
|
+
const toTs = parseDay(qTo);
|
|
451
|
+
|
|
452
|
+
const now = new Date();
|
|
453
|
+
const defaultTo = Date.UTC(now.getUTCFullYear() + 100, 0, 1); // far future
|
|
454
|
+
const defaultFrom = Date.UTC(1970, 0, 1);
|
|
455
|
+
const minTs = fromTs ?? defaultFrom;
|
|
456
|
+
const maxTs = toTs ?? defaultTo;
|
|
457
|
+
|
|
458
|
+
const ids = await dbLayer.listAllReservationIds(200000);
|
|
459
|
+
let purged = 0;
|
|
460
|
+
const ts = Date.now();
|
|
461
|
+
|
|
462
|
+
for (const rid of ids) {
|
|
463
|
+
const r = await dbLayer.getReservation(rid);
|
|
464
|
+
if (!r) continue;
|
|
465
|
+
if (String(r.status) !== 'paid') continue;
|
|
466
|
+
const start = parseInt(r.start, 10);
|
|
467
|
+
if (!Number.isFinite(start)) continue;
|
|
468
|
+
if (start < minTs || start >= maxTs) continue;
|
|
469
|
+
|
|
470
|
+
r.accPurgedAt = ts;
|
|
471
|
+
await dbLayer.saveReservation(r);
|
|
472
|
+
purged++;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
return res.json({ ok: true, purged });
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
|
|
437
479
|
module.exports = admin;
|
package/lib/helloassoWebhook.js
CHANGED
|
@@ -295,7 +295,20 @@ async function handler(req, res, next) {
|
|
|
295
295
|
return res.json({ ok: true, ignored: true });
|
|
296
296
|
}
|
|
297
297
|
|
|
298
|
+
// Ignore incomplete Payment events (HelloAsso sometimes omits metadata and checkoutIntentId on Payment webhooks).
|
|
299
|
+
// We rely on Order events (or checkoutIntent mappings) for reliable reconciliation.
|
|
300
|
+
const _eventType = String((payload && payload.eventType) || '').toLowerCase();
|
|
301
|
+
const _rid = getReservationIdFromPayload(payload);
|
|
302
|
+
const _checkoutIntentId = getCheckoutIntentIdFromPayload(payload);
|
|
303
|
+
if (_eventType === 'payment' && !_rid && !_checkoutIntentId) {
|
|
304
|
+
return res.json({ ok: true, ignored: true, incompletePayment: true });
|
|
305
|
+
}
|
|
306
|
+
|
|
298
307
|
const paymentId = payload && payload.data ? (payload.data.id || payload.data.paymentId) : null;
|
|
308
|
+
// If we can't identify the payment, acknowledge and ignore (prevents accidental crashes).
|
|
309
|
+
if (!paymentId) {
|
|
310
|
+
return res.json({ ok: true, ignored: true, missingPaymentId: true });
|
|
311
|
+
}
|
|
299
312
|
if (await alreadyProcessed(paymentId)) {
|
|
300
313
|
return res.json({ ok: true, duplicate: true });
|
|
301
314
|
}
|
package/library.js
CHANGED
|
@@ -97,6 +97,7 @@ Plugin.init = async function (params) {
|
|
|
97
97
|
// Accounting / exports
|
|
98
98
|
router.get(`${base}/accounting`, ...adminMws, admin.getAccounting);
|
|
99
99
|
router.get(`${base}/accounting.csv`, ...adminMws, admin.exportAccountingCsv);
|
|
100
|
+
router.post(`${base}/accounting/purge`, ...adminMws, admin.purgeAccounting);
|
|
100
101
|
});
|
|
101
102
|
|
|
102
103
|
// HelloAsso callback endpoint (hardened)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nodebb-plugin-calendar-onekite",
|
|
3
|
-
"version":"11.1.
|
|
3
|
+
"version": "11.1.65",
|
|
4
4
|
"description": "FullCalendar-based equipment reservation workflow with admin approval & HelloAsso payment for NodeBB",
|
|
5
5
|
"main": "library.js",
|
|
6
6
|
"license": "MIT",
|
|
@@ -8,4 +8,4 @@
|
|
|
8
8
|
"node": ">=18"
|
|
9
9
|
},
|
|
10
10
|
"dependencies": {}
|
|
11
|
-
}
|
|
11
|
+
}
|
package/plugin.json
CHANGED
package/public/admin.js
CHANGED
|
@@ -516,6 +516,7 @@ define('admin/plugins/calendar-onekite', ['alerts', 'bootbox'], function (alerts
|
|
|
516
516
|
const accTo = document.getElementById('onekite-acc-to');
|
|
517
517
|
const accRefresh = document.getElementById('onekite-acc-refresh');
|
|
518
518
|
const accExport = document.getElementById('onekite-acc-export');
|
|
519
|
+
const accPurge = document.getElementById('onekite-acc-purge');
|
|
519
520
|
const accSummary = document.querySelector('#onekite-acc-summary tbody');
|
|
520
521
|
const accRows = document.querySelector('#onekite-acc-rows tbody');
|
|
521
522
|
|
|
@@ -584,6 +585,32 @@ define('admin/plugins/calendar-onekite', ['alerts', 'bootbox'], function (alerts
|
|
|
584
585
|
const url = `/api/v3/admin/plugins/calendar-onekite/accounting.csv${qs ? `?${qs}` : ''}`;
|
|
585
586
|
window.open(url, '_blank');
|
|
586
587
|
});
|
|
588
|
+
|
|
589
|
+
if (accPurge) {
|
|
590
|
+
accPurge.addEventListener('click', async () => {
|
|
591
|
+
const ok = window.confirm('Purger la comptabilité pour la période sélectionnée ?
|
|
592
|
+
Cela masquera ces réservations dans l\'onglet Comptabilisation (sans modifier leur statut payé).');
|
|
593
|
+
if (!ok) return;
|
|
594
|
+
try {
|
|
595
|
+
const params = new URLSearchParams();
|
|
596
|
+
if (accFrom && accFrom.value) params.set('from', accFrom.value);
|
|
597
|
+
if (accTo && accTo.value) params.set('to', accTo.value);
|
|
598
|
+
const qs = params.toString();
|
|
599
|
+
const url = `/api/v3/admin/plugins/calendar-onekite/accounting/purge${qs ? `?${qs}` : ''}`;
|
|
600
|
+
const res = await fetchJson(url, { method: 'POST' });
|
|
601
|
+
if (res && res.ok) {
|
|
602
|
+
showAlert('success', `Compta purgée : ${res.purged || 0} réservation(s).`);
|
|
603
|
+
const data = await fetchAccounting();
|
|
604
|
+
renderAccounting(data);
|
|
605
|
+
} else {
|
|
606
|
+
showAlert('error', 'Purge impossible.');
|
|
607
|
+
}
|
|
608
|
+
} catch (e) {
|
|
609
|
+
showAlert('error', 'Purge impossible.');
|
|
610
|
+
}
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
|
|
587
614
|
}
|
|
588
615
|
}
|
|
589
616
|
|
|
@@ -122,6 +122,7 @@
|
|
|
122
122
|
<div>
|
|
123
123
|
<button type="button" class="btn btn-primary" id="onekite-acc-refresh">Rafraîchir</button>
|
|
124
124
|
<button type="button" class="btn btn-outline-secondary" id="onekite-acc-export">Exporter CSV</button>
|
|
125
|
+
<button type="button" class="btn btn-outline-danger" id="onekite-acc-purge">Purger la compta</button>
|
|
125
126
|
</div>
|
|
126
127
|
</div>
|
|
127
128
|
|