nodebb-plugin-equipment-calendar 8.0.0 → 8.0.2
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/library.js +76 -44
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/public/js/client.js +25 -27
package/library.js
CHANGED
|
@@ -27,6 +27,24 @@ function generateId() {
|
|
|
27
27
|
return String(Date.now()) + '-' + Math.random().toString(16).slice(2);
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
function normalizeItemIds(itemIdsRaw) {
|
|
33
|
+
if (!itemIdsRaw) return [];
|
|
34
|
+
if (Array.isArray(itemIdsRaw)) {
|
|
35
|
+
return itemIdsRaw.map(String).flatMap((v) => String(v).split(',')).map(s => s.trim()).filter(Boolean);
|
|
36
|
+
}
|
|
37
|
+
if (typeof itemIdsRaw === 'string') {
|
|
38
|
+
return itemIdsRaw.split(',').map(s => s.trim()).filter(Boolean);
|
|
39
|
+
}
|
|
40
|
+
// fallback: single value (number/object/etc.)
|
|
41
|
+
try {
|
|
42
|
+
return [String(itemIdsRaw).trim()].filter(Boolean);
|
|
43
|
+
} catch (e) {
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
30
48
|
const axios = require('axios');
|
|
31
49
|
const { DateTime } = require('luxon');
|
|
32
50
|
const { v4: uuidv4 } = require('uuid');
|
|
@@ -316,10 +334,10 @@ async function getSettings() {
|
|
|
316
334
|
// Keys:
|
|
317
335
|
// item hash: equipmentCalendar:items (stored in settings as JSON)
|
|
318
336
|
// reservations stored as objects in db, indexed by id, and by itemId
|
|
319
|
-
// reservation object key: equipmentCalendar:
|
|
337
|
+
// reservation object key: equipmentCalendar:reservation:<rid>
|
|
320
338
|
// index by item: equipmentCalendar:item:<itemId>:res (sorted set score=startMillis, value=resId)
|
|
321
339
|
|
|
322
|
-
function resKey(
|
|
340
|
+
function resKey(rid) { return `equipmentCalendar:reservation:${rid}`; }
|
|
323
341
|
function itemIndexKey(itemId) { return `equipmentCalendar:item:${itemId}:res`; }
|
|
324
342
|
|
|
325
343
|
function statusBlocksItem(status) {
|
|
@@ -348,19 +366,19 @@ async function getBookingRids(bookingId) {
|
|
|
348
366
|
return await db.getSetMembers(`equipmentCalendar:booking:${bookingId}:rids`) || [];
|
|
349
367
|
}
|
|
350
368
|
|
|
351
|
-
async function saveReservation(
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
await db.sortedSetAdd(
|
|
369
|
+
async function saveReservation(r) {
|
|
370
|
+
const rid = String(r.rid || r.id || '');
|
|
371
|
+
if (!rid) throw new Error('missing rid');
|
|
372
|
+
r.rid = rid;
|
|
373
|
+
await db.setObject(resKey(rid), r);
|
|
374
|
+
await db.sortedSetAdd(itemIndexKey(r.itemId), r.startMs, rid);
|
|
375
|
+
await db.sortedSetAdd('equipmentCalendar:reservations', r.startMs, rid);
|
|
357
376
|
}
|
|
358
377
|
|
|
359
|
-
async function getReservation(
|
|
360
|
-
const
|
|
361
|
-
if (!
|
|
362
|
-
|
|
363
|
-
return normalizeReservation(obj);
|
|
378
|
+
async function getReservation(rid) {
|
|
379
|
+
const id = String(rid || '');
|
|
380
|
+
if (!id) return null;
|
|
381
|
+
return await db.getObject(resKey(id));
|
|
364
382
|
}
|
|
365
383
|
|
|
366
384
|
function normalizeReservation(obj) {
|
|
@@ -1176,47 +1194,61 @@ async function handleCreateReservation(req, res) {
|
|
|
1176
1194
|
return helpers.notAllowed(req, res);
|
|
1177
1195
|
}
|
|
1178
1196
|
|
|
1179
|
-
const itemIdsRaw = req.body.itemIds
|
|
1180
|
-
const itemIds = (
|
|
1181
|
-
|
|
1182
|
-
.map(s => String(s).trim())
|
|
1183
|
-
.filter(Boolean);
|
|
1197
|
+
const itemIdsRaw = (req.body.itemIds !== undefined ? req.body.itemIds : req.body.itemId);
|
|
1198
|
+
const itemIds = normalizeItemIds(itemIdsRaw);
|
|
1199
|
+
if (!itemIds.length) return res.status(400).send('itemIds required');
|
|
1184
1200
|
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1201
|
+
// Dates come from modal as YYYY-MM-DD
|
|
1202
|
+
const startStr = String(req.body.start || req.body.startDate || req.body.startIso || '');
|
|
1203
|
+
const endStr = String(req.body.end || req.body.endDate || req.body.endIso || '');
|
|
1204
|
+
if (!startStr || !endStr) return res.status(400).send('dates required');
|
|
1188
1205
|
|
|
1189
|
-
const
|
|
1190
|
-
const
|
|
1191
|
-
const
|
|
1192
|
-
|
|
1206
|
+
const start = new Date(startStr + 'T00:00:00Z');
|
|
1207
|
+
const end = new Date(endStr + 'T00:00:00Z');
|
|
1208
|
+
const startMs = Date.UTC(start.getUTCFullYear(), start.getUTCMonth(), start.getUTCDate());
|
|
1209
|
+
let endMs = Date.UTC(end.getUTCFullYear(), end.getUTCMonth(), end.getUTCDate());
|
|
1210
|
+
if (endMs <= startMs) endMs = startMs + 24 * 60 * 60 * 1000;
|
|
1193
1211
|
|
|
1194
|
-
|
|
1195
|
-
const startDay = start.startOf('day');
|
|
1196
|
-
let endDay = end.startOf('day');
|
|
1197
|
-
if (endDay <= startDay) {
|
|
1198
|
-
endDay = startDay.plus({ days: 1 });
|
|
1199
|
-
}
|
|
1212
|
+
const days = Math.max(1, Math.round((endMs - startMs) / (24 * 60 * 60 * 1000)));
|
|
1200
1213
|
|
|
1201
|
-
const
|
|
1202
|
-
const
|
|
1214
|
+
const items = await getActiveItems(settings);
|
|
1215
|
+
const byId = {};
|
|
1216
|
+
items.forEach((it) => { byId[String(it.id)] = it; });
|
|
1203
1217
|
|
|
1204
|
-
const
|
|
1218
|
+
const validIds = itemIds.map(String).filter((id) => byId[id]);
|
|
1219
|
+
if (!validIds.length) return res.status(400).send('unknown item');
|
|
1205
1220
|
|
|
1206
|
-
const
|
|
1221
|
+
const notesUser = String(req.body.notesUser || '').trim();
|
|
1207
1222
|
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
const
|
|
1212
|
-
|
|
1223
|
+
for (const itemId of validIds) {
|
|
1224
|
+
const it = byId[itemId];
|
|
1225
|
+
const unitPrice = Number(it.price || 0) || 0;
|
|
1226
|
+
const total = unitPrice * days;
|
|
1227
|
+
|
|
1228
|
+
const rid = generateId();
|
|
1229
|
+
const r = {
|
|
1230
|
+
rid,
|
|
1231
|
+
uid: req.uid,
|
|
1232
|
+
itemId,
|
|
1233
|
+
startMs,
|
|
1234
|
+
endMs,
|
|
1235
|
+
startIso: new Date(startMs).toISOString().slice(0, 10),
|
|
1236
|
+
endIso: new Date(endMs).toISOString().slice(0, 10),
|
|
1237
|
+
days,
|
|
1238
|
+
unitPrice,
|
|
1239
|
+
total,
|
|
1240
|
+
notesUser,
|
|
1241
|
+
status: 'pending',
|
|
1242
|
+
createdAt: Date.now(),
|
|
1243
|
+
};
|
|
1244
|
+
// eslint-disable-next-line no-await-in-loop
|
|
1245
|
+
await saveReservation(r);
|
|
1213
1246
|
}
|
|
1214
1247
|
|
|
1215
|
-
|
|
1216
|
-
return res.redirect('/equipment/calendar?requested=1');
|
|
1248
|
+
return res.redirect(nconf.get('relative_path') + '/calendar?created=1');
|
|
1217
1249
|
} catch (err) {
|
|
1218
|
-
winston.error(err);
|
|
1219
|
-
return res.status(500).send(
|
|
1250
|
+
winston.error('[equipment-calendar] create error', err);
|
|
1251
|
+
return res.status(500).send('error');
|
|
1220
1252
|
}
|
|
1221
1253
|
}
|
|
1222
1254
|
|
package/package.json
CHANGED
package/plugin.json
CHANGED
package/public/js/client.js
CHANGED
|
@@ -29,34 +29,32 @@ require(['jquery', 'bootstrap'], function ($, bootstrap) {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
function updateTotalPrice() {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if (startEl) startEl.addEventListener('change', updateTotalPrice);
|
|
39
|
-
if (endEl) endEl.addEventListener('change', updateTotalPrice);
|
|
40
|
-
if (!sel || !out || !startEl || !endEl) return;
|
|
32
|
+
try {
|
|
33
|
+
const sel = document.getElementById('ec-item-ids');
|
|
34
|
+
const out = document.getElementById('ec-total');
|
|
35
|
+
const startEl = document.getElementById('ec-start');
|
|
36
|
+
const endEl = document.getElementById('ec-end');
|
|
37
|
+
if (!sel || !out || !startEl || !endEl) return;
|
|
41
38
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
39
|
+
const start = startEl.value ? new Date(startEl.value + 'T00:00:00Z') : null;
|
|
40
|
+
const end = endEl.value ? new Date(endEl.value + 'T00:00:00Z') : null;
|
|
41
|
+
let days = 1;
|
|
42
|
+
if (start && end) {
|
|
43
|
+
const ms = end.getTime() - start.getTime();
|
|
44
|
+
days = Math.max(1, Math.round(ms / (24 * 60 * 60 * 1000)));
|
|
45
|
+
}
|
|
49
46
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
47
|
+
let sum = 0;
|
|
48
|
+
const opts = Array.from(sel.selectedOptions || []);
|
|
49
|
+
opts.forEach((opt) => {
|
|
50
|
+
const raw = (opt && opt.dataset) ? opt.dataset.price : '';
|
|
51
|
+
const num = Number(String(raw).replace(',', '.'));
|
|
52
|
+
if (!Number.isNaN(num)) sum += num;
|
|
53
|
+
});
|
|
57
54
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
55
|
+
const total = sum * days;
|
|
56
|
+
const txt = Number.isInteger(total) ? String(total) : total.toFixed(2);
|
|
57
|
+
out.textContent = txt + ' €';
|
|
58
|
+
} catch (e) {}
|
|
59
|
+
})();
|
|
62
60
|
});
|