@aglyn/plugins-bookings 1.0.0-beta.262 → 1.0.0-beta.263

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aglyn/plugins-bookings",
3
- "version": "1.0.0-beta.262",
3
+ "version": "1.0.0-beta.263",
4
4
  "license": "Apache-2.0",
5
5
  "homepage": "https://aglyn.com",
6
6
  "repository": {
@@ -25,16 +25,16 @@
25
25
  "./package.json": "./package.json"
26
26
  },
27
27
  "dependencies": {
28
- "@aglyn/aglyn": "1.0.0-beta.262",
29
- "@aglyn/shared-data-mdi": "1.0.0-beta.262",
30
- "@aglyn/shared-ui-jsx": "1.0.0-beta.262",
31
- "@aglyn/shared-ui-snackstack": "1.0.0-beta.262",
32
- "@aglyn/shared-util-email": "1.0.0-beta.262",
33
- "@aglyn/shared-util-http": "1.0.0-beta.262",
34
- "@aglyn/shared-util-timestamp": "1.0.0-beta.262",
35
- "@aglyn/tenant-data-admin": "1.0.0-beta.262",
36
- "@aglyn/tenant-feature-instance": "1.0.0-beta.262",
37
- "@aglyn/tenant-runtime": "1.0.0-beta.262",
28
+ "@aglyn/aglyn": "1.0.0-beta.263",
29
+ "@aglyn/shared-data-mdi": "1.0.0-beta.263",
30
+ "@aglyn/shared-ui-jsx": "1.0.0-beta.263",
31
+ "@aglyn/shared-ui-snackstack": "1.0.0-beta.263",
32
+ "@aglyn/shared-util-email": "1.0.0-beta.263",
33
+ "@aglyn/shared-util-http": "1.0.0-beta.263",
34
+ "@aglyn/shared-util-timestamp": "1.0.0-beta.263",
35
+ "@aglyn/tenant-data-admin": "1.0.0-beta.263",
36
+ "@aglyn/tenant-feature-instance": "1.0.0-beta.263",
37
+ "@aglyn/tenant-runtime": "1.0.0-beta.263",
38
38
  "@swc/helpers": "0.5.23"
39
39
  },
40
40
  "peerDependencies": {
@@ -14,7 +14,7 @@
14
14
  * See the License for the specific language governing permissions and
15
15
  * limitations under the License.
16
16
  */ import { PLUGIN_FIGURE_MAX_ROWS, pluginFigureChange, pluginFigureWindows, registerPluginFigureReader } from "@aglyn/aglyn/plugin-manager/plugin-figures";
17
- import { figureCount, figureRanked, figureRow, figureWithChange } from "@aglyn/aglyn/app-utils/weekly-summary";
17
+ import { figureCount, figureRanked, figureRow, figureStat } from "@aglyn/aglyn/app-utils/weekly-summary";
18
18
  import { BUNDLE_ID } from "../constants/bundle-common.js";
19
19
  /** Bookings one window reads; a busier site is told its figures read low. */ export const BOOKING_FIGURES_WINDOW_CEILING = 1000;
20
20
  const WINDOWS = [
@@ -59,23 +59,25 @@ export function bookingFigureReader(firestore) {
59
59
  feature: 'bookings',
60
60
  weeklySummary: {
61
61
  order: 60,
62
- lines: (table)=>{
62
+ summarize: (table)=>{
63
63
  const all = figureRow(table, 'service', 'All services');
64
- if (!all || !figureCount(all['booked']) && !figureCount(all['canceled'])) return [];
65
- const canceled = figureCount(all['canceled']);
66
- const services = figureRanked(table, 'service', 'booked', '', table.rows.filter((row)=>row !== all));
67
- return [
68
- `Bookings: ${figureWithChange(table, all, 'booked', 'change')}` + (canceled ? `, ${canceled.toLocaleString('en-US')} canceled` : '') + (services ? ` (${services})` : '')
69
- ];
70
- },
71
- headline: (table)=>{
72
- var _figureRow;
73
- const count = figureCount((_figureRow = figureRow(table, 'service', 'All services')) == null ? void 0 : _figureRow['booked']);
74
- return count ? {
75
- count,
76
- one: 'booking',
77
- many: 'bookings'
78
- } : null;
64
+ if (!all || !figureCount(all['booked']) && !figureCount(all['canceled'])) return null;
65
+ return {
66
+ title: 'Bookings',
67
+ stats: [
68
+ figureStat(table, all, 'booked', 'Booked', {
69
+ change: 'change',
70
+ total: {
71
+ one: 'booking',
72
+ many: 'bookings'
73
+ }
74
+ }),
75
+ ...figureCount(all['canceled']) ? [
76
+ figureStat(table, all, 'canceled', 'Canceled')
77
+ ] : []
78
+ ],
79
+ rows: figureRanked(table, 'service', 'booked', table.rows.filter((row)=>row !== all))
80
+ };
79
81
  }
80
82
  },
81
83
  read: async (request)=>{
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/bookings/src/lib/server/booking-figures.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n PLUGIN_FIGURE_MAX_ROWS,\n pluginFigureChange,\n pluginFigureWindows,\n registerPluginFigureReader,\n type PluginFigureReader,\n} from '@aglyn/aglyn/plugin-manager/plugin-figures'\nimport { figureCount, figureRanked, figureRow, figureWithChange } from '@aglyn/aglyn/app-utils/weekly-summary'\nimport { BUNDLE_ID } from '../constants/bundle-common'\n\n/**\n * A site's bookings as a figure table another plugin reads by id (AGL-2915).\n *\n * Counted by service: how many were booked in the window, how many of those\n * were canceled, and the change in bookings from the window before. A booking\n * names a person and says where they will be and when, so nothing of one\n * reaches a table but its service and its status.\n */\n\ntype Firestore = FirebaseFirestore.Firestore\n\n/** Bookings one window reads; a busier site is told its figures read low. */\nexport const BOOKING_FIGURES_WINDOW_CEILING = 1_000\n\nconst WINDOWS: readonly number[] = [7, 14, 30, 90]\n\n/** A payment hold that lapsed never became a booking, and is not counted as one. */\nconst NOT_BOOKED = new Set(['canceled', 'pendingPayment'])\n\ninterface ServiceCounts {\n booked: number\n canceled: number\n}\n\nasync function bookingsBetween(\n firestore: Firestore,\n hostId: string,\n startMs: number,\n endMs: number,\n): Promise<{ counts: Map<string, ServiceCounts>; truncated: boolean }> {\n // `createdAt` is stamped by the server on every booking the book route writes.\n const snapshot = await firestore\n .collection('hosts')\n .doc(hostId)\n .collection('bookings')\n .where('createdAt', '>=', new Date(startMs))\n .where('createdAt', '<', new Date(endMs))\n .orderBy('createdAt', 'desc')\n .limit(BOOKING_FIGURES_WINDOW_CEILING + 1)\n .get()\n const counts = new Map<string, ServiceCounts>()\n for (const doc of snapshot.docs.slice(0, BOOKING_FIGURES_WINDOW_CEILING)) {\n const data = doc.data() ?? {}\n const service = String(data['serviceName'] ?? '').trim() || 'A service with no name'\n const status = String(data['status'] ?? '')\n const entry = counts.get(service) ?? { booked: 0, canceled: 0 }\n if (status === 'canceled') entry.canceled += 1\n if (!NOT_BOOKED.has(status)) entry.booked += 1\n counts.set(service, entry)\n }\n return { counts, truncated: snapshot.docs.length > BOOKING_FIGURES_WINDOW_CEILING }\n}\n\nexport function bookingFigureReader(firestore: () => Firestore): PluginFigureReader {\n return {\n id: 'bookings.services',\n label: 'Bookings',\n description:\n 'Bookings made on the site over the window, by service: how many were booked, how many were canceled, and the change in bookings from the window before.',\n scope: 'site',\n windows: WINDOWS,\n feature: 'bookings',\n weeklySummary: {\n order: 60,\n lines: (table) => {\n const all = figureRow(table, 'service', 'All services')\n if (!all || (!figureCount(all['booked']) && !figureCount(all['canceled']))) return []\n const canceled = figureCount(all['canceled'])\n const services = figureRanked(table, 'service', 'booked', '', table.rows.filter((row) => row !== all))\n return [\n `Bookings: ${figureWithChange(table, all, 'booked', 'change')}` +\n (canceled ? `, ${canceled.toLocaleString('en-US')} canceled` : '') +\n (services ? ` (${services})` : ''),\n ]\n },\n headline: (table) => {\n const count = figureCount(figureRow(table, 'service', 'All services')?.['booked'])\n return count ? { count, one: 'booking', many: 'bookings' } : null\n },\n },\n read: async (request) => {\n if (!request.hostId) return { ok: false, status: 400, error: 'Open a site to read its bookings' }\n if (!WINDOWS.includes(request.days)) {\n return { ok: false, status: 400, error: 'That window is not one bookings are read over' }\n }\n const { current, previous } = pluginFigureWindows(request.now, request.days)\n const [now, before] = await Promise.all([\n bookingsBetween(firestore(), request.hostId, current.startMs, current.endMs),\n bookingsBetween(firestore(), request.hostId, previous.startMs, previous.endMs),\n ])\n const sum = (counts: Map<string, ServiceCounts>, key: keyof ServiceCounts) =>\n [...counts.values()].reduce((total, entry) => total + entry[key], 0)\n const services = [...now.counts.entries()].sort((a, b) => b[1].booked - a[1].booked || a[0].localeCompare(b[0]))\n const rows = [\n {\n service: 'All services',\n booked: sum(now.counts, 'booked'),\n canceled: sum(now.counts, 'canceled'),\n change: pluginFigureChange(sum(now.counts, 'booked'), sum(before.counts, 'booked')),\n },\n ...services.slice(0, PLUGIN_FIGURE_MAX_ROWS - 1).map(([service, counts]) => ({\n service,\n booked: counts.booked,\n canceled: counts.canceled,\n change: pluginFigureChange(counts.booked, before.counts.get(service)?.booked ?? null),\n })),\n ]\n return {\n ok: true,\n table: {\n title: 'Bookings',\n source: { label: 'Bookings', path: 'bookings' },\n period: { from: current.from, to: current.to, days: request.days },\n columns: [\n { key: 'service', label: 'Service', kind: 'text' },\n { key: 'booked', label: 'Booked', kind: 'count' },\n { key: 'canceled', label: 'Canceled', kind: 'count' },\n { key: 'change', label: 'Change in bookings', kind: 'change' },\n ],\n rows,\n omitted: Math.max(0, services.length - (PLUGIN_FIGURE_MAX_ROWS - 1)),\n notes: [\n 'A booking is counted in the window it was made in, not the day it is for.',\n ...(now.truncated || before.truncated\n ? [\n `More than ${BOOKING_FIGURES_WINDOW_CEILING.toLocaleString('en-US')} bookings were made in a window, so the figures count the most recent ${BOOKING_FIGURES_WINDOW_CEILING.toLocaleString('en-US')} and read low.`,\n ]\n : []),\n ],\n },\n }\n },\n }\n}\n\n/** Registers the bookings reader from the console surface, where insight jobs run. */\nexport function registerBookingFigureReader(firestore: () => Firestore): void {\n registerPluginFigureReader(bookingFigureReader(firestore), { pluginId: BUNDLE_ID })\n}\n"],"names":["PLUGIN_FIGURE_MAX_ROWS","pluginFigureChange","pluginFigureWindows","registerPluginFigureReader","figureCount","figureRanked","figureRow","figureWithChange","BUNDLE_ID","BOOKING_FIGURES_WINDOW_CEILING","WINDOWS","NOT_BOOKED","Set","bookingsBetween","firestore","hostId","startMs","endMs","snapshot","collection","doc","where","Date","orderBy","limit","get","counts","Map","docs","slice","data","service","String","trim","status","entry","booked","canceled","has","set","truncated","length","bookingFigureReader","id","label","description","scope","windows","feature","weeklySummary","order","lines","table","all","services","rows","filter","row","toLocaleString","headline","count","one","many","read","request","ok","error","includes","days","current","previous","now","before","Promise","sum","key","values","reduce","total","entries","sort","a","b","localeCompare","change","map","title","source","path","period","from","to","columns","kind","omitted","Math","max","notes","registerBookingFigureReader","pluginId"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/bookings/src/lib/server/booking-figures.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n PLUGIN_FIGURE_MAX_ROWS,\n pluginFigureChange,\n pluginFigureWindows,\n registerPluginFigureReader,\n type PluginFigureReader,\n} from '@aglyn/aglyn/plugin-manager/plugin-figures'\nimport { figureCount, figureRanked, figureRow, figureStat } from '@aglyn/aglyn/app-utils/weekly-summary'\nimport { BUNDLE_ID } from '../constants/bundle-common'\n\n/**\n * A site's bookings as a figure table another plugin reads by id (AGL-2915).\n *\n * Counted by service: how many were booked in the window, how many of those\n * were canceled, and the change in bookings from the window before. A booking\n * names a person and says where they will be and when, so nothing of one\n * reaches a table but its service and its status.\n */\n\ntype Firestore = FirebaseFirestore.Firestore\n\n/** Bookings one window reads; a busier site is told its figures read low. */\nexport const BOOKING_FIGURES_WINDOW_CEILING = 1_000\n\nconst WINDOWS: readonly number[] = [7, 14, 30, 90]\n\n/** A payment hold that lapsed never became a booking, and is not counted as one. */\nconst NOT_BOOKED = new Set(['canceled', 'pendingPayment'])\n\ninterface ServiceCounts {\n booked: number\n canceled: number\n}\n\nasync function bookingsBetween(\n firestore: Firestore,\n hostId: string,\n startMs: number,\n endMs: number,\n): Promise<{ counts: Map<string, ServiceCounts>; truncated: boolean }> {\n // `createdAt` is stamped by the server on every booking the book route writes.\n const snapshot = await firestore\n .collection('hosts')\n .doc(hostId)\n .collection('bookings')\n .where('createdAt', '>=', new Date(startMs))\n .where('createdAt', '<', new Date(endMs))\n .orderBy('createdAt', 'desc')\n .limit(BOOKING_FIGURES_WINDOW_CEILING + 1)\n .get()\n const counts = new Map<string, ServiceCounts>()\n for (const doc of snapshot.docs.slice(0, BOOKING_FIGURES_WINDOW_CEILING)) {\n const data = doc.data() ?? {}\n const service = String(data['serviceName'] ?? '').trim() || 'A service with no name'\n const status = String(data['status'] ?? '')\n const entry = counts.get(service) ?? { booked: 0, canceled: 0 }\n if (status === 'canceled') entry.canceled += 1\n if (!NOT_BOOKED.has(status)) entry.booked += 1\n counts.set(service, entry)\n }\n return { counts, truncated: snapshot.docs.length > BOOKING_FIGURES_WINDOW_CEILING }\n}\n\nexport function bookingFigureReader(firestore: () => Firestore): PluginFigureReader {\n return {\n id: 'bookings.services',\n label: 'Bookings',\n description:\n 'Bookings made on the site over the window, by service: how many were booked, how many were canceled, and the change in bookings from the window before.',\n scope: 'site',\n windows: WINDOWS,\n feature: 'bookings',\n weeklySummary: {\n order: 60,\n summarize: (table) => {\n const all = figureRow(table, 'service', 'All services')\n if (!all || (!figureCount(all['booked']) && !figureCount(all['canceled']))) return null\n return {\n title: 'Bookings',\n stats: [\n figureStat(table, all, 'booked', 'Booked', { change: 'change', total: { one: 'booking', many: 'bookings' } }),\n ...(figureCount(all['canceled']) ? [figureStat(table, all, 'canceled', 'Canceled')] : []),\n ],\n rows: figureRanked(table, 'service', 'booked', table.rows.filter((row) => row !== all)),\n }\n },\n },\n read: async (request) => {\n if (!request.hostId) return { ok: false, status: 400, error: 'Open a site to read its bookings' }\n if (!WINDOWS.includes(request.days)) {\n return { ok: false, status: 400, error: 'That window is not one bookings are read over' }\n }\n const { current, previous } = pluginFigureWindows(request.now, request.days)\n const [now, before] = await Promise.all([\n bookingsBetween(firestore(), request.hostId, current.startMs, current.endMs),\n bookingsBetween(firestore(), request.hostId, previous.startMs, previous.endMs),\n ])\n const sum = (counts: Map<string, ServiceCounts>, key: keyof ServiceCounts) =>\n [...counts.values()].reduce((total, entry) => total + entry[key], 0)\n const services = [...now.counts.entries()].sort((a, b) => b[1].booked - a[1].booked || a[0].localeCompare(b[0]))\n const rows = [\n {\n service: 'All services',\n booked: sum(now.counts, 'booked'),\n canceled: sum(now.counts, 'canceled'),\n change: pluginFigureChange(sum(now.counts, 'booked'), sum(before.counts, 'booked')),\n },\n ...services.slice(0, PLUGIN_FIGURE_MAX_ROWS - 1).map(([service, counts]) => ({\n service,\n booked: counts.booked,\n canceled: counts.canceled,\n change: pluginFigureChange(counts.booked, before.counts.get(service)?.booked ?? null),\n })),\n ]\n return {\n ok: true,\n table: {\n title: 'Bookings',\n source: { label: 'Bookings', path: 'bookings' },\n period: { from: current.from, to: current.to, days: request.days },\n columns: [\n { key: 'service', label: 'Service', kind: 'text' },\n { key: 'booked', label: 'Booked', kind: 'count' },\n { key: 'canceled', label: 'Canceled', kind: 'count' },\n { key: 'change', label: 'Change in bookings', kind: 'change' },\n ],\n rows,\n omitted: Math.max(0, services.length - (PLUGIN_FIGURE_MAX_ROWS - 1)),\n notes: [\n 'A booking is counted in the window it was made in, not the day it is for.',\n ...(now.truncated || before.truncated\n ? [\n `More than ${BOOKING_FIGURES_WINDOW_CEILING.toLocaleString('en-US')} bookings were made in a window, so the figures count the most recent ${BOOKING_FIGURES_WINDOW_CEILING.toLocaleString('en-US')} and read low.`,\n ]\n : []),\n ],\n },\n }\n },\n }\n}\n\n/** Registers the bookings reader from the console surface, where insight jobs run. */\nexport function registerBookingFigureReader(firestore: () => Firestore): void {\n registerPluginFigureReader(bookingFigureReader(firestore), { pluginId: BUNDLE_ID })\n}\n"],"names":["PLUGIN_FIGURE_MAX_ROWS","pluginFigureChange","pluginFigureWindows","registerPluginFigureReader","figureCount","figureRanked","figureRow","figureStat","BUNDLE_ID","BOOKING_FIGURES_WINDOW_CEILING","WINDOWS","NOT_BOOKED","Set","bookingsBetween","firestore","hostId","startMs","endMs","snapshot","collection","doc","where","Date","orderBy","limit","get","counts","Map","docs","slice","data","service","String","trim","status","entry","booked","canceled","has","set","truncated","length","bookingFigureReader","id","label","description","scope","windows","feature","weeklySummary","order","summarize","table","all","title","stats","change","total","one","many","rows","filter","row","read","request","ok","error","includes","days","current","previous","now","before","Promise","sum","key","values","reduce","services","entries","sort","a","b","localeCompare","map","source","path","period","from","to","columns","kind","omitted","Math","max","notes","toLocaleString","registerBookingFigureReader","pluginId"],"mappings":"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"}