@aglyn/plugins-bookings 1.0.0-beta.221 → 1.0.0-beta.223
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/README.md +3 -2
- package/package.json +11 -11
- package/src/lib/components/book-meeting-action.js +5 -3
- package/src/lib/components/book-meeting-action.js.map +1 -1
- package/src/lib/components/booking.d.ts +33 -0
- package/src/lib/components/booking.js +260 -54
- package/src/lib/components/booking.js.map +1 -1
- package/src/lib/components/bookings-console-page.js +139 -15
- package/src/lib/components/bookings-console-page.js.map +1 -1
- package/src/lib/declarations.console-server.d.ts +34 -0
- package/src/lib/declarations.console-server.js +84 -0
- package/src/lib/declarations.console-server.js.map +1 -0
- package/src/lib/model/booking-contact-fields.d.ts +88 -0
- package/src/lib/model/booking-contact-fields.js +130 -0
- package/src/lib/model/booking-contact-fields.js.map +1 -0
- package/src/lib/model/booking-price.d.ts +51 -0
- package/src/lib/model/booking-price.js +61 -0
- package/src/lib/model/booking-price.js.map +1 -0
- package/src/lib/model/booking-record.d.ts +7 -0
- package/src/lib/model/booking-record.js +11 -2
- package/src/lib/model/booking-record.js.map +1 -1
- package/src/lib/model/bookings-record-counts.d.ts +25 -0
- package/src/lib/model/bookings-record-counts.js +34 -0
- package/src/lib/model/bookings-record-counts.js.map +1 -0
- package/src/lib/model/bookings.d.ts +61 -4
- package/src/lib/model/bookings.js +81 -15
- package/src/lib/model/bookings.js.map +1 -1
- package/src/lib/model/index.d.ts +2 -0
- package/src/lib/model/index.js +2 -0
- package/src/lib/model/index.js.map +1 -1
- package/src/lib/plugin.js +14 -0
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/server/billing-webhook.js +33 -15
- package/src/lib/server/billing-webhook.js.map +1 -1
- package/src/lib/server/booking-crm.js +4 -1
- package/src/lib/server/booking-crm.js.map +1 -1
- package/src/lib/server/person-eraser.d.ts +36 -0
- package/src/lib/server/person-eraser.js +48 -0
- package/src/lib/server/person-eraser.js.map +1 -0
- package/src/lib/server/refund.js +2 -2
- package/src/lib/server/refund.js.map +1 -1
- package/src/lib/server.js +97 -36
- package/src/lib/server.js.map +1 -1
- package/src/lib/transfer/bookings-transfer-common.d.ts +68 -0
- package/src/lib/transfer/bookings-transfer-common.js +130 -0
- package/src/lib/transfer/bookings-transfer-common.js.map +1 -0
- package/src/lib/transfer/bookings-transfer.d.ts +86 -0
- package/src/lib/transfer/bookings-transfer.js +613 -0
- package/src/lib/transfer/bookings-transfer.js.map +1 -0
- package/src/lib/transfer/bookings-transfer.server.d.ts +26 -0
- package/src/lib/transfer/bookings-transfer.server.js +31 -0
- package/src/lib/transfer/bookings-transfer.server.js.map +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/bookings/src/lib/transfer/bookings-transfer-common.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 type { MatchKeySpec } from '@aglyn/aglyn/data-transfer'\n\n/**\n * WHAT BOTH HALVES OF THE `bookings` TRANSFER RESOURCE SHARE, in a module\n * with no server dependency: the key, the match keys the declarations\n * register synchronously, and the list's filters.\n *\n * The Bookings page and the export's `readPage` must name a filter the same\n * way: the page builds the value it hands the export dialog, and the server\n * half parses it back into one Firestore query.\n *\n * One filter at a time, each a query an index serves:\n *\n * - `{ upcoming: true, asOfMs? }` — the page's default list: every booking\n * that has not ended by `asOfMs` (the moment the page drew the list; now\n * when absent), canceled ones included, as the list shows them.\n * - `{ email }` — one booker's bookings, past and upcoming, the list a\n * contact's record links to.\n * - `{ serviceId }` — one service's bookings.\n *\n * Anything else is refused rather than read as \"everything\": a file that\n * says it holds a filtered list and holds the whole site would be worse than\n * no file.\n */\n\n/** The resource key `plugins.config.json` declares under `transferResources`. */\nexport const BOOKINGS_TRANSFER_RESOURCE = 'bookings'\n\n/** The one key a row is named by: its Aglyn ID. Nothing is ever matched to import. */\nexport const BOOKINGS_TRANSFER_MATCH_KEYS: readonly MatchKeySpec[] = [\n { fieldId: 'id', normalizer: 'aglynId' },\n]\n\n/** One of the bookings list's filters, in the export's terms. */\nexport type BookingsTransferFilter =\n | { kind: 'upcoming'; asOfMs: number | null }\n | { kind: 'email'; email: string }\n | { kind: 'service'; serviceId: string }\n\n/** Why a filter could not be read, in a sentence the person reads. */\nexport class BookingsTransferFilterError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'BookingsTransferFilterError'\n }\n}\n\n/** The value the page hands the export dialog for its default list. */\nexport function upcomingBookingsFilterValue(\n asOfMs: number,\n): Record<string, unknown> {\n return { upcoming: true, asOfMs }\n}\n\n/** The value the page hands the export dialog when narrowed to one booker. */\nexport function bookerBookingsFilterValue(\n email: string,\n): Record<string, unknown> {\n return { email }\n}\n\n/**\n * The filter `value` names, or `null` for none (an empty object). Throws\n * {@link BookingsTransferFilterError} for anything else: an unknown key, two\n * filters at once, or a value of the wrong shape.\n */\nexport function parseBookingsTransferFilter(\n value: Readonly<Record<string, unknown>> | null | undefined,\n): BookingsTransferFilter | null {\n if (!value) return null\n const keys = Object.keys(value).filter((key) => value[key] !== undefined)\n if (!keys.length) return null\n const unknown = keys.filter(\n (key) => !['upcoming', 'asOfMs', 'email', 'serviceId'].includes(key),\n )\n if (unknown.length) {\n throw new BookingsTransferFilterError(\n `Bookings can be filtered by upcoming, email or service, not by ${unknown.join(', ')}.`,\n )\n }\n const named = ['upcoming', 'email', 'serviceId'].filter((key) =>\n keys.includes(key),\n )\n if (named.length > 1) {\n throw new BookingsTransferFilterError(\n 'Bookings are exported with one filter at a time.',\n )\n }\n if (keys.includes('asOfMs') && !keys.includes('upcoming')) {\n throw new BookingsTransferFilterError(\n 'A start time only narrows the upcoming bookings.',\n )\n }\n if (keys.includes('upcoming')) {\n if (value['upcoming'] !== true) {\n throw new BookingsTransferFilterError(\n 'The upcoming filter is on or absent.',\n )\n }\n const asOf = value['asOfMs']\n if (asOf === undefined || asOf === null)\n return { kind: 'upcoming', asOfMs: null }\n if (typeof asOf !== 'number' || !Number.isFinite(asOf)) {\n throw new BookingsTransferFilterError(\n 'The upcoming filter’s start time is not a time.',\n )\n }\n return { kind: 'upcoming', asOfMs: Math.floor(asOf) }\n }\n if (keys.includes('email')) {\n // Stored lowercased and trimmed by the booking route, so that is the\n // form an equality has to ask in.\n const email = String(value['email'] ?? '')\n .trim()\n .toLowerCase()\n if (!email || !email.includes('@')) {\n throw new BookingsTransferFilterError(\n 'The email filter needs an email address.',\n )\n }\n return { kind: 'email', email }\n }\n const serviceId = String(value['serviceId'] ?? '').trim()\n if (!serviceId || serviceId.includes('/')) {\n throw new BookingsTransferFilterError('The service filter needs a service.')\n }\n return { kind: 'service', serviceId }\n}\n"],"names":["BOOKINGS_TRANSFER_RESOURCE","BOOKINGS_TRANSFER_MATCH_KEYS","fieldId","normalizer","BookingsTransferFilterError","Error","message","name","upcomingBookingsFilterValue","asOfMs","upcoming","bookerBookingsFilterValue","email","parseBookingsTransferFilter","value","keys","Object","filter","key","undefined","length","unknown","includes","join","named","asOf","kind","Number","isFinite","Math","floor","String","trim","toLowerCase","serviceId"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAID;;;;;;;;;;;;;;;;;;;;;CAqBC,GAED,+EAA+E,GAC/E,OAAO,MAAMA,6BAA6B,WAAU;AAEpD,oFAAoF,GACpF,OAAO,MAAMC,+BAAwD;IACnE;QAAEC,SAAS;QAAMC,YAAY;IAAU;CACxC,CAAA;AAQD,oEAAoE,GACpE,OAAO,MAAMC,oCAAoCC;IAC/C,YAAYC,OAAe,CAAE;QAC3B,KAAK,CAACA;QACN,IAAI,CAACC,IAAI,GAAG;IACd;AACF;AAEA,qEAAqE,GACrE,OAAO,SAASC,4BACdC,MAAc;IAEd,OAAO;QAAEC,UAAU;QAAMD;IAAO;AAClC;AAEA,4EAA4E,GAC5E,OAAO,SAASE,0BACdC,KAAa;IAEb,OAAO;QAAEA;IAAM;AACjB;AAEA;;;;CAIC,GACD,OAAO,SAASC,4BACdC,KAA2D;QAuDlCA;IArDzB,IAAI,CAACA,OAAO,OAAO;IACnB,MAAMC,OAAOC,OAAOD,IAAI,CAACD,OAAOG,MAAM,CAAC,CAACC,MAAQJ,KAAK,CAACI,IAAI,KAAKC;IAC/D,IAAI,CAACJ,KAAKK,MAAM,EAAE,OAAO;IACzB,MAAMC,UAAUN,KAAKE,MAAM,CACzB,CAACC,MAAQ,CAAC;YAAC;YAAY;YAAU;YAAS;SAAY,CAACI,QAAQ,CAACJ;IAElE,IAAIG,QAAQD,MAAM,EAAE;QAClB,MAAM,IAAIhB,4BACR,CAAC,+DAA+D,EAAEiB,QAAQE,IAAI,CAAC,MAAM,CAAC,CAAC;IAE3F;IACA,MAAMC,QAAQ;QAAC;QAAY;QAAS;KAAY,CAACP,MAAM,CAAC,CAACC,MACvDH,KAAKO,QAAQ,CAACJ;IAEhB,IAAIM,MAAMJ,MAAM,GAAG,GAAG;QACpB,MAAM,IAAIhB,4BACR;IAEJ;IACA,IAAIW,KAAKO,QAAQ,CAAC,aAAa,CAACP,KAAKO,QAAQ,CAAC,aAAa;QACzD,MAAM,IAAIlB,4BACR;IAEJ;IACA,IAAIW,KAAKO,QAAQ,CAAC,aAAa;QAC7B,IAAIR,KAAK,CAAC,WAAW,KAAK,MAAM;YAC9B,MAAM,IAAIV,4BACR;QAEJ;QACA,MAAMqB,OAAOX,KAAK,CAAC,SAAS;QAC5B,IAAIW,SAASN,aAAaM,SAAS,MACjC,OAAO;YAAEC,MAAM;YAAYjB,QAAQ;QAAK;QAC1C,IAAI,OAAOgB,SAAS,YAAY,CAACE,OAAOC,QAAQ,CAACH,OAAO;YACtD,MAAM,IAAIrB,4BACR;QAEJ;QACA,OAAO;YAAEsB,MAAM;YAAYjB,QAAQoB,KAAKC,KAAK,CAACL;QAAM;IACtD;IACA,IAAIV,KAAKO,QAAQ,CAAC,UAAU;YAGLR;QAFrB,qEAAqE;QACrE,kCAAkC;QAClC,MAAMF,QAAQmB,QAAOjB,eAAAA,KAAK,CAAC,QAAQ,YAAdA,eAAkB,IACpCkB,IAAI,GACJC,WAAW;QACd,IAAI,CAACrB,SAAS,CAACA,MAAMU,QAAQ,CAAC,MAAM;YAClC,MAAM,IAAIlB,4BACR;QAEJ;QACA,OAAO;YAAEsB,MAAM;YAASd;QAAM;IAChC;IACA,MAAMsB,YAAYH,QAAOjB,mBAAAA,KAAK,CAAC,YAAY,YAAlBA,mBAAsB,IAAIkB,IAAI;IACvD,IAAI,CAACE,aAAaA,UAAUZ,QAAQ,CAAC,MAAM;QACzC,MAAM,IAAIlB,4BAA4B;IACxC;IACA,OAAO;QAAEsB,MAAM;QAAWQ;IAAU;AACtC"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
import { type TransferCatalogInput } from '@aglyn/aglyn/data-transfer';
|
|
18
|
+
import type { PluginTransferResource } from '@aglyn/aglyn/plugin-manager/plugin-transfer-resources';
|
|
19
|
+
/**
|
|
20
|
+
* BOOKINGS, EXPORTED (the server half of the `bookings` transfer resource).
|
|
21
|
+
*
|
|
22
|
+
* Every booking of one site, `hosts/{hostId}/bookings/{id}`, as rows of the
|
|
23
|
+
* fields a person picks: the service and the slot, the customer, what was
|
|
24
|
+
* paid, and when the platform did what to it.
|
|
25
|
+
*
|
|
26
|
+
* ## Exported, never imported
|
|
27
|
+
*
|
|
28
|
+
* The resource declares `exportOnly`, so it registers no `apply` or
|
|
29
|
+
* `revert` (its `lookup` answers the Aglyn ID only) and the upload route
|
|
30
|
+
* refuses it. There is no safe
|
|
31
|
+
* write path to give it: the only thing that creates a booking is the
|
|
32
|
+
* booking route, which holds the slot, may start a Stripe charge, emails the
|
|
33
|
+
* customer and files the meeting in the CRM — and the hourly reminder job
|
|
34
|
+
* would email any confirmed booking it found a day out. A booking written
|
|
35
|
+
* from a file would be an appointment nobody booked.
|
|
36
|
+
*
|
|
37
|
+
* ## Which bookings a page reads
|
|
38
|
+
*
|
|
39
|
+
* - `ids`: the selection, read by id in the order given; ids that name no
|
|
40
|
+
* booking are skipped. The cursor is the offset into the list.
|
|
41
|
+
* - `filter`: one of the list's filters (`bookings-transfer-common.ts`), as
|
|
42
|
+
* one Firestore query:
|
|
43
|
+
* - none: every booking, by start, `startsAtMs` ascending — the
|
|
44
|
+
* single-field index;
|
|
45
|
+
* - upcoming: `endsAtMs >= asOf`, by end ascending — the single-field
|
|
46
|
+
* index on `endsAtMs` (the range field has to lead the order);
|
|
47
|
+
* - one service: `serviceId ==`, by start — the declared composite
|
|
48
|
+
* `serviceId ASC, startsAtMs ASC`;
|
|
49
|
+
* - one booker: `email ==`, by start — the composite
|
|
50
|
+
* `email ASC, startsAtMs ASC`, which `bookings-export-indexes.spec.ts`
|
|
51
|
+
* holds the index file to.
|
|
52
|
+
* Every order breaks ties by document id, and the cursor is the last row's
|
|
53
|
+
* order value and id, so a page never repeats or skips a row.
|
|
54
|
+
* - `scopeTokens` narrows nothing here. Bookings are a SITE's records and
|
|
55
|
+
* carry no `visibleTo`; the export route admits a reader only with
|
|
56
|
+
* `data.manage` on the site named, so a collaborator who reaches this read
|
|
57
|
+
* reaches every booking of that site, the way the Bookings page shows them.
|
|
58
|
+
*
|
|
59
|
+
* Times go out as ISO instants in UTC, beside the booking's own zone and
|
|
60
|
+
* the start and end as a wall clock in it. Amounts are dollars (every
|
|
61
|
+
* booking is charged in USD), from the stored cents.
|
|
62
|
+
*/
|
|
63
|
+
type Firestore = FirebaseFirestore.Firestore;
|
|
64
|
+
export interface BookingsTransferDeps {
|
|
65
|
+
firestore(): Firestore;
|
|
66
|
+
/** The clock the upcoming filter reads when it names no moment. */
|
|
67
|
+
now?: () => number;
|
|
68
|
+
}
|
|
69
|
+
export declare const BOOKINGS_TRANSFER_GROUPS: readonly [{
|
|
70
|
+
readonly id: "booking";
|
|
71
|
+
readonly label: "Booking";
|
|
72
|
+
}, {
|
|
73
|
+
readonly id: "customer";
|
|
74
|
+
readonly label: "Customer";
|
|
75
|
+
}, {
|
|
76
|
+
readonly id: "payment";
|
|
77
|
+
readonly label: "Payment";
|
|
78
|
+
}, {
|
|
79
|
+
readonly id: "system";
|
|
80
|
+
readonly label: "System";
|
|
81
|
+
}];
|
|
82
|
+
/** The catalog, the same for every site. */
|
|
83
|
+
export declare function bookingsTransferCatalog(): TransferCatalogInput;
|
|
84
|
+
/** The `bookings` resource over `deps.firestore()`. Export only: a lookup by id, and no apply or revert. */
|
|
85
|
+
export declare function createBookingsTransferResource(deps: BookingsTransferDeps): PluginTransferResource;
|
|
86
|
+
export {};
|