@saltcorn/reservable 0.1.2 → 0.1.3

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/available-feed.js CHANGED
@@ -56,6 +56,8 @@ const configuration_workflow = (req) =>
56
56
 
57
57
  const reservable_entity_fields = fields.filter((f) => f.is_fkey);
58
58
  const show_view_opts = {};
59
+ const slots_available_field = {};
60
+ const distinct_slot_fields = new Set();
59
61
  for (const rfield of reservable_entity_fields) {
60
62
  const retable = Table.findOne(rfield.reftable_name);
61
63
  const show_views = await View.find_table_views_where(
@@ -66,6 +68,13 @@ const configuration_workflow = (req) =>
66
68
  state_fields.some((sf) => sf.name === "id")
67
69
  );
68
70
  show_view_opts[rfield.name] = show_views.map((v) => v.name);
71
+ slots_available_field[rfield.name] = retable.fields
72
+ .filter((f) => f.type?.name === "Integer")
73
+ .map((f) => f.name);
74
+ slots_available_field[rfield.name].forEach((v) =>
75
+ distinct_slot_fields.add(v)
76
+ );
77
+ slots_available_field[rfield.name].unshift("");
69
78
  }
70
79
  return new Form({
71
80
  fields: [
@@ -78,6 +87,17 @@ const configuration_workflow = (req) =>
78
87
  options: reservable_entity_fields.map((f) => f.name),
79
88
  },
80
89
  },
90
+ {
91
+ name: "valid_field",
92
+ label: "Valid reservation field",
93
+ sublabel: "Only consider reservations with this field valid",
94
+ type: "String",
95
+ attributes: {
96
+ options: fields
97
+ .filter((f) => f.type.name === "Bool")
98
+ .map((f) => f.name),
99
+ },
100
+ },
81
101
  {
82
102
  name: "start_field",
83
103
  label: "Start date field",
@@ -93,13 +113,14 @@ const configuration_workflow = (req) =>
93
113
  name: "end_field",
94
114
  label: "End date field",
95
115
  type: "String",
116
+ required: true,
96
117
  attributes: {
97
118
  options: fields
98
119
  .filter((f) => f.type.name === "Date")
99
120
  .map((f) => f.name),
100
121
  },
101
122
  },
102
- {
123
+ /*{
103
124
  name: "duration_field",
104
125
  label: "Duration field",
105
126
  sublabel: "Integer field holding booked duration in minutes",
@@ -109,7 +130,7 @@ const configuration_workflow = (req) =>
109
130
  .filter((f) => f.type.name === "Integer")
110
131
  .map((f) => f.name),
111
132
  },
112
- },
133
+ }, */
113
134
  {
114
135
  name: "show_view",
115
136
  label: req.__("Single item view"),
@@ -129,6 +150,29 @@ const configuration_workflow = (req) =>
129
150
  calcOptions: ["reservable_entity_key", show_view_opts],
130
151
  },
131
152
  },
153
+ {
154
+ name: "slots_available_field",
155
+ label: "Slots available field",
156
+ sublabel:
157
+ "Field with a number of available instances of the reservable entity. If blank, treat as one per entity.",
158
+ type: "String",
159
+ attributes: {
160
+ calcOptions: ["reservable_entity_key", slots_available_field],
161
+ },
162
+ },
163
+ {
164
+ name: "slot_count_field",
165
+ label: "Slots taken field",
166
+ sublabel:
167
+ "Field with the number of entities reserved. If blank, treat as one per entity.",
168
+ type: "String",
169
+ showIf: { slots_available_field: [...distinct_slot_fields] },
170
+ attributes: {
171
+ options: fields
172
+ .filter((f) => f.type.name === "Integer")
173
+ .map((f) => f.name),
174
+ },
175
+ },
132
176
  ],
133
177
  });
134
178
  },
@@ -136,10 +180,20 @@ const configuration_workflow = (req) =>
136
180
  ],
137
181
  });
138
182
 
183
+ const first = (xs) => (Array.isArray(xs) ? xs[0] : xs);
184
+
139
185
  const run = async (
140
186
  table_id,
141
187
  viewname,
142
- { reservable_entity_key, start_field, end_field, duration_field, show_view },
188
+ {
189
+ reservable_entity_key,
190
+ valid_field,
191
+ slot_count_field,
192
+ slots_available_field,
193
+ show_view,
194
+ start_field,
195
+ end_field,
196
+ },
143
197
  state,
144
198
  extraArgs
145
199
  ) => {
@@ -160,9 +214,9 @@ const run = async (
160
214
  table: restable,
161
215
  });
162
216
 
163
- const ress = await restable.getRows(reswhere);
164
- const resEnts = new Set(ress.map((r) => r[reservable_entity_key]));
165
-
217
+ if (valid_field) reswhere[valid_field] = true;
218
+ const reservations = await restable.getRows(reswhere);
219
+ const use_slots = slot_count_field || slots_available_field;
166
220
  const sview = await View.findOne({ name: show_view });
167
221
  if (!sview)
168
222
  throw new InvalidConfiguration(
@@ -170,8 +224,45 @@ const run = async (
170
224
  );
171
225
  const srespAll = await sview.runMany(state, extraArgs);
172
226
  const srespsAvailable = [];
173
- for (const sresp of srespAll) {
174
- if (!resEnts.has(sresp.row[retable.pk_name])) srespsAvailable.push(sresp);
227
+
228
+ if (!use_slots) {
229
+ const resEnts = new Set(reservations.map((r) => r[reservable_entity_key]));
230
+ for (const sresp of srespAll) {
231
+ if (!resEnts.has(sresp.row[retable.pk_name])) srespsAvailable.push(sresp);
232
+ }
233
+ } else {
234
+ //console.log("state_res", state_res);
235
+ //console.log("reswhere", reswhere);
236
+ const to = new Date(first(reswhere[start_field])?.lt);
237
+ const from = new Date(first(reswhere[end_field])?.gt);
238
+ if (!from || !to) srespsAvailable.push(...srespAll);
239
+ else
240
+ for (const sresp of srespAll) {
241
+ const myreservations = reservations.filter(
242
+ (r) => r[reservable_entity_key] === sresp.row[retable.pk_name]
243
+ );
244
+ /*console.log({
245
+ taken: resEnts[sresp.row[retable.pk_name]] || 0,
246
+ available: sresp.row[slots_available_field],
247
+ });*/
248
+ const to = new Date(first(reswhere[start_field])?.lt);
249
+ const from = new Date(first(reswhere[end_field])?.gt);
250
+ let maxAvailable = sresp.row[slots_available_field];
251
+ for (let day = from; day <= to; day.setDate(day.getDate() + 1)) {
252
+ const active = myreservations.filter(
253
+ (r) => r[start_field] <= day && r[end_field] >= day
254
+ );
255
+ const taken = active
256
+ .map((r) => r[slot_count_field] || 1)
257
+ .reduce((a, b) => a + b, 0);
258
+ maxAvailable = Math.min(
259
+ maxAvailable,
260
+ sresp.row[slots_available_field] - taken
261
+ );
262
+ //console.log({ car: sresp.row.name, day, maxAvailable });
263
+ }
264
+ if (maxAvailable > 0) srespsAvailable.push(sresp);
265
+ }
175
266
  }
176
267
  const showRow = (r) => r.html;
177
268
  return div(srespsAvailable.map(showRow));
package/index.js CHANGED
@@ -528,6 +528,7 @@ module.exports = {
528
528
  },
529
529
  require("./available-feed"),
530
530
  ],
531
+ actions: { validate_reservation: require("./validate") },
531
532
  };
532
533
 
533
534
  /*
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saltcorn/reservable",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Reservable resources",
5
5
  "main": "index.js",
6
6
  "dependencies": {
package/validate.js ADDED
@@ -0,0 +1,71 @@
1
+ const View = require("@saltcorn/data/models/view");
2
+ const Table = require("@saltcorn/data/models/table");
3
+
4
+ module.exports = {
5
+ configFields: async ({ table }) => {
6
+ const views = await View.find({
7
+ viewtemplate: "Available Resources Feed",
8
+ table_id: table.id,
9
+ });
10
+ return [
11
+ {
12
+ name: "feedview",
13
+ label: "View",
14
+ sublabel: `A view on table ${table.name} with pattern: Available Resources Feed`,
15
+ type: "String",
16
+ required: true,
17
+ attributes: {
18
+ options: views.map((f) => f.name),
19
+ },
20
+ },
21
+ ];
22
+ },
23
+ requireRow: true,
24
+ run: async ({ table, req, row, configuration: { feedview } }) => {
25
+ const view = View.findOne({ name: feedview });
26
+ const {
27
+ reservable_entity_key,
28
+ valid_field,
29
+ slot_count_field,
30
+ slots_available_field,
31
+ show_view,
32
+ start_field,
33
+ end_field,
34
+ } = view.configuration;
35
+ //get all relevant reservations
36
+
37
+ const ress = await table.getRows({
38
+ [reservable_entity_key]: row[reservable_entity_key],
39
+ [start_field]: { lt: row[end_field], equal: true, day_only: true },
40
+ [end_field]: { gt: row[start_field], equal: true, day_only: true },
41
+ ...(valid_field ? { [valid_field]: true } : {}),
42
+ });
43
+
44
+ //get entity
45
+ const refield = table.getField(reservable_entity_key);
46
+ const retable = Table.findOne(refield.reftable_name);
47
+ const entity = await retable.getRow({
48
+ [retable.pk_name]: row[reservable_entity_key],
49
+ });
50
+ //check that for every day, there is availablity
51
+ const from = new Date(row[start_field]);
52
+ const to = new Date(row[end_field]);
53
+ let maxAvailable = entity[slots_available_field];
54
+ // loop for every day
55
+ for (let day = from; day <= to; day.setDate(day.getDate() + 1)) {
56
+ // your day is here
57
+ const active = ress.filter(
58
+ (r) => r[start_field] <= day && r[end_field] >= day
59
+ );
60
+ const taken = active
61
+ .map((r) => r[slot_count_field])
62
+ .reduce((a, b) => a + b, 0);
63
+ maxAvailable = Math.min(
64
+ maxAvailable,
65
+ entity[slots_available_field] - taken
66
+ );
67
+ }
68
+ if (maxAvailable < row[slot_count_field])
69
+ return { error: `Only ${maxAvailable} are available` };
70
+ },
71
+ };