@aglyn/plugins-bookings 1.0.0-beta.230 → 1.0.0-beta.231
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 +1 -1
- package/package.json +11 -11
- package/src/lib/model/booking-in-person.d.ts +72 -0
- package/src/lib/model/booking-in-person.js +71 -0
- package/src/lib/model/booking-in-person.js.map +1 -0
- package/src/lib/model/booking-manage.d.ts +66 -0
- package/src/lib/model/booking-manage.js +90 -0
- package/src/lib/model/booking-manage.js.map +1 -0
- package/src/lib/server/booking-manage.d.ts +77 -0
- package/src/lib/server/booking-manage.js +306 -0
- package/src/lib/server/booking-manage.js.map +1 -0
- package/src/lib/server/in-person-payment.d.ts +20 -0
- package/src/lib/server/in-person-payment.js +469 -0
- package/src/lib/server/in-person-payment.js.map +1 -0
- package/src/lib/server.js +9 -0
- package/src/lib/server.js.map +1 -1
- package/src/lib/tenant-emails.js +59 -0
- package/src/lib/tenant-emails.js.map +1 -1
package/README.md
CHANGED
|
@@ -40,7 +40,7 @@ This package is a first-party Aglyn plugin. It is loaded through Aglyn's plugin
|
|
|
40
40
|
`@aglyn/plugins-bookings/server` imports `firebase-admin` and is kept out of the client entry point.
|
|
41
41
|
|
|
42
42
|
- `registerBookingsApi()` (the `tenantApi` surface) registers the site-facing routes `bookings/slots` (open slots for a service, a page of `BOOKING_SLOT_PAGE_DAYS` whole days at a time; `from` asks for the page its `nextFromMs` named) and `bookings/book` (take a booking; a paid service goes through Stripe Checkout).
|
|
43
|
-
- `registerBookingsConsoleApi()` (the `consoleApi` surface) registers `bookings/reminders`, `bookings/refund` and `bookings/booking-analytics`, a handler on the platform billing webhook that confirms paid bookings, and a bookings-by-service figure reader.
|
|
43
|
+
- `registerBookingsConsoleApi()` (the `consoleApi` surface) registers `bookings/reminders`, `bookings/refund`, `bookings/check-in`, `bookings/reschedule`, `bookings/in-person-payment` (a booking paid at the counter on the native Aglyn POS app's card reader) and `bookings/booking-analytics`, a handler on the platform billing webhook that confirms paid bookings, and a bookings-by-service figure reader.
|
|
44
44
|
- Loading the module registers two scheduled plugin jobs: `expire-stale-holds` and `booking-reminders` (a reminder email about a day before a confirmed booking). This is why `package.json` lists `./src/lib/server.*` under `sideEffects`.
|
|
45
45
|
|
|
46
46
|
Routes are served by the host app's API dispatcher under `/api/`, for example `/api/bookings/slots`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aglyn/plugins-bookings",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.231",
|
|
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.
|
|
29
|
-
"@aglyn/shared-data-mdi": "1.0.0-beta.
|
|
30
|
-
"@aglyn/shared-ui-jsx": "1.0.0-beta.
|
|
31
|
-
"@aglyn/shared-ui-snackstack": "1.0.0-beta.
|
|
32
|
-
"@aglyn/shared-util-email": "1.0.0-beta.
|
|
33
|
-
"@aglyn/shared-util-http": "1.0.0-beta.
|
|
34
|
-
"@aglyn/shared-util-timestamp": "1.0.0-beta.
|
|
35
|
-
"@aglyn/tenant-data-admin": "1.0.0-beta.
|
|
36
|
-
"@aglyn/tenant-feature-instance": "1.0.0-beta.
|
|
37
|
-
"@aglyn/tenant-runtime": "1.0.0-beta.
|
|
28
|
+
"@aglyn/aglyn": "1.0.0-beta.231",
|
|
29
|
+
"@aglyn/shared-data-mdi": "1.0.0-beta.231",
|
|
30
|
+
"@aglyn/shared-ui-jsx": "1.0.0-beta.231",
|
|
31
|
+
"@aglyn/shared-ui-snackstack": "1.0.0-beta.231",
|
|
32
|
+
"@aglyn/shared-util-email": "1.0.0-beta.231",
|
|
33
|
+
"@aglyn/shared-util-http": "1.0.0-beta.231",
|
|
34
|
+
"@aglyn/shared-util-timestamp": "1.0.0-beta.231",
|
|
35
|
+
"@aglyn/tenant-data-admin": "1.0.0-beta.231",
|
|
36
|
+
"@aglyn/tenant-feature-instance": "1.0.0-beta.231",
|
|
37
|
+
"@aglyn/tenant-runtime": "1.0.0-beta.231",
|
|
38
38
|
"@swc/helpers": "0.5.23"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
@@ -0,0 +1,72 @@
|
|
|
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
|
+
/** The in-person payment a booking carries while it is being taken. */
|
|
18
|
+
export interface BookingInPersonPayment {
|
|
19
|
+
paymentIntentId: string;
|
|
20
|
+
/** The whole charge: service plus tax. */
|
|
21
|
+
amountCents: number;
|
|
22
|
+
/** The service amount staff typed, before tax. */
|
|
23
|
+
serviceCents: number;
|
|
24
|
+
taxCents: number;
|
|
25
|
+
feeCents: number;
|
|
26
|
+
status: 'pending' | 'paid' | 'canceled';
|
|
27
|
+
startedAtMs: number;
|
|
28
|
+
startedBy: string;
|
|
29
|
+
paidAtMs?: number;
|
|
30
|
+
/** Which start request made it, so a retried start returns it again. */
|
|
31
|
+
attempt?: string;
|
|
32
|
+
}
|
|
33
|
+
/** The fields of a booking this reads; any booking document has them. */
|
|
34
|
+
export interface BookingPaymentFields {
|
|
35
|
+
status?: string;
|
|
36
|
+
expiresAtMs?: number;
|
|
37
|
+
paidAmountCents?: number;
|
|
38
|
+
refundedCents?: number;
|
|
39
|
+
paymentIntentId?: string;
|
|
40
|
+
inPersonPayment?: Partial<BookingInPersonPayment> | null;
|
|
41
|
+
}
|
|
42
|
+
export type BookingInPersonState =
|
|
43
|
+
/** Paid already, online or in person. */
|
|
44
|
+
'paid'
|
|
45
|
+
/** Canceled: nothing to pay. */
|
|
46
|
+
| 'canceled'
|
|
47
|
+
/** Still holding for its online Checkout: the customer is paying there. */
|
|
48
|
+
| 'awaiting-online'
|
|
49
|
+
/** A card is being taken for it now. */
|
|
50
|
+
| 'collecting'
|
|
51
|
+
/** Can be paid at the counter. */
|
|
52
|
+
| 'payable';
|
|
53
|
+
export declare function bookingInPersonState(booking: BookingPaymentFields, nowMs?: number): BookingInPersonState;
|
|
54
|
+
/** The most one in-person booking charge may be: $10,000. */
|
|
55
|
+
export declare const BOOKING_IN_PERSON_MAX_CENTS = 1000000;
|
|
56
|
+
/** Stripe's card minimum in USD. */
|
|
57
|
+
export declare const BOOKING_IN_PERSON_MIN_CENTS = 50;
|
|
58
|
+
/** Null when `serviceCents` is an amount staff may charge; otherwise why not. */
|
|
59
|
+
export declare function bookingInPersonAmountProblem(serviceCents: unknown): string | null;
|
|
60
|
+
/**
|
|
61
|
+
* The amount to suggest: the service's fixed price, when it has one. A
|
|
62
|
+
* `varies`, `estimate` or `contact` price suggests nothing; staff type it.
|
|
63
|
+
*/
|
|
64
|
+
export declare function bookingSuggestedCents(service: {
|
|
65
|
+
priceUsd?: number;
|
|
66
|
+
priceDisplay?: string;
|
|
67
|
+
} | null | undefined): number | null;
|
|
68
|
+
/** The start and end of the local day that holds `atMs`, in milliseconds. */
|
|
69
|
+
export declare function bookingDayRange(atMs: number, offsetMinutes?: number): {
|
|
70
|
+
startMs: number;
|
|
71
|
+
endMs: number;
|
|
72
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
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
|
+
* TAKING PAYMENT FOR A BOOKING IN PERSON (AGL-3618).
|
|
18
|
+
*
|
|
19
|
+
* A booking whose price was not charged online (a free booking, or one whose
|
|
20
|
+
* price `varies`, is an `estimate` or is `contact`) is paid at the counter
|
|
21
|
+
* once the work is done: staff type the amount and the customer taps or
|
|
22
|
+
* inserts a card on the register's reader. Pure: the server route, the
|
|
23
|
+
* console and the native Aglyn POS app all read a booking through these.
|
|
24
|
+
*
|
|
25
|
+
* Imports nothing, so client and server code alike may import it.
|
|
26
|
+
*=========================================*/ /** The in-person payment a booking carries while it is being taken. */ export function bookingInPersonState(booking, nowMs = Date.now()) {
|
|
27
|
+
var _booking_paidAmountCents;
|
|
28
|
+
var _booking_inPersonPayment, _booking_inPersonPayment1;
|
|
29
|
+
if (Math.round(Number((_booking_paidAmountCents = booking.paidAmountCents) != null ? _booking_paidAmountCents : 0)) > 0 || Boolean(booking.paymentIntentId) || ((_booking_inPersonPayment = booking.inPersonPayment) == null ? void 0 : _booking_inPersonPayment.status) === 'paid') {
|
|
30
|
+
return 'paid';
|
|
31
|
+
}
|
|
32
|
+
if (booking.status === 'canceled') return 'canceled';
|
|
33
|
+
if (booking.status === 'pendingPayment') {
|
|
34
|
+
var _booking_expiresAtMs;
|
|
35
|
+
// A lapsed online hold released its slot; it is not an appointment.
|
|
36
|
+
return Number((_booking_expiresAtMs = booking.expiresAtMs) != null ? _booking_expiresAtMs : 0) < nowMs ? 'canceled' : 'awaiting-online';
|
|
37
|
+
}
|
|
38
|
+
if (((_booking_inPersonPayment1 = booking.inPersonPayment) == null ? void 0 : _booking_inPersonPayment1.status) === 'pending') return 'collecting';
|
|
39
|
+
return 'payable';
|
|
40
|
+
}
|
|
41
|
+
/** The most one in-person booking charge may be: $10,000. */ export const BOOKING_IN_PERSON_MAX_CENTS = 1000000;
|
|
42
|
+
/** Stripe's card minimum in USD. */ export const BOOKING_IN_PERSON_MIN_CENTS = 50;
|
|
43
|
+
/** Null when `serviceCents` is an amount staff may charge; otherwise why not. */ export function bookingInPersonAmountProblem(serviceCents) {
|
|
44
|
+
if (typeof serviceCents !== 'number' || !Number.isInteger(serviceCents)) {
|
|
45
|
+
return 'Enter the amount to charge.';
|
|
46
|
+
}
|
|
47
|
+
if (serviceCents < BOOKING_IN_PERSON_MIN_CENTS) return 'Card payments start at $0.50.';
|
|
48
|
+
if (serviceCents > BOOKING_IN_PERSON_MAX_CENTS) return 'Charge at most $10,000 at a time.';
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The amount to suggest: the service's fixed price, when it has one. A
|
|
53
|
+
* `varies`, `estimate` or `contact` price suggests nothing; staff type it.
|
|
54
|
+
*/ export function bookingSuggestedCents(service) {
|
|
55
|
+
var _service_priceUsd;
|
|
56
|
+
if (!service) return null;
|
|
57
|
+
if (service.priceDisplay && service.priceDisplay !== 'fixed') return null;
|
|
58
|
+
const cents = Math.round(Number((_service_priceUsd = service.priceUsd) != null ? _service_priceUsd : 0) * 100);
|
|
59
|
+
return Number.isFinite(cents) && cents >= BOOKING_IN_PERSON_MIN_CENTS ? cents : null;
|
|
60
|
+
}
|
|
61
|
+
/** The start and end of the local day that holds `atMs`, in milliseconds. */ export function bookingDayRange(atMs, offsetMinutes = new Date(atMs).getTimezoneOffset()) {
|
|
62
|
+
const local = atMs - offsetMinutes * 60000;
|
|
63
|
+
const dayStart = Math.floor(local / 86400000) * 86400000;
|
|
64
|
+
const startMs = dayStart + offsetMinutes * 60000;
|
|
65
|
+
return {
|
|
66
|
+
startMs,
|
|
67
|
+
endMs: startMs + 86400000
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
//# sourceMappingURL=booking-in-person.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/bookings/src/lib/model/booking-in-person.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\n/*==========================================\n * TAKING PAYMENT FOR A BOOKING IN PERSON (AGL-3618).\n *\n * A booking whose price was not charged online (a free booking, or one whose\n * price `varies`, is an `estimate` or is `contact`) is paid at the counter\n * once the work is done: staff type the amount and the customer taps or\n * inserts a card on the register's reader. Pure: the server route, the\n * console and the native Aglyn POS app all read a booking through these.\n *\n * Imports nothing, so client and server code alike may import it.\n *=========================================*/\n\n/** The in-person payment a booking carries while it is being taken. */\nexport interface BookingInPersonPayment {\n paymentIntentId: string\n /** The whole charge: service plus tax. */\n amountCents: number\n /** The service amount staff typed, before tax. */\n serviceCents: number\n taxCents: number\n feeCents: number\n status: 'pending' | 'paid' | 'canceled'\n startedAtMs: number\n startedBy: string\n paidAtMs?: number\n /** Which start request made it, so a retried start returns it again. */\n attempt?: string\n}\n\n/** The fields of a booking this reads; any booking document has them. */\nexport interface BookingPaymentFields {\n status?: string\n expiresAtMs?: number\n paidAmountCents?: number\n refundedCents?: number\n paymentIntentId?: string\n inPersonPayment?: Partial<BookingInPersonPayment> | null\n}\n\nexport type BookingInPersonState =\n /** Paid already, online or in person. */\n | 'paid'\n /** Canceled: nothing to pay. */\n | 'canceled'\n /** Still holding for its online Checkout: the customer is paying there. */\n | 'awaiting-online'\n /** A card is being taken for it now. */\n | 'collecting'\n /** Can be paid at the counter. */\n | 'payable'\n\nexport function bookingInPersonState(\n booking: BookingPaymentFields,\n nowMs = Date.now(),\n): BookingInPersonState {\n if (\n Math.round(Number(booking.paidAmountCents ?? 0)) > 0 ||\n Boolean(booking.paymentIntentId) ||\n booking.inPersonPayment?.status === 'paid'\n ) {\n return 'paid'\n }\n if (booking.status === 'canceled') return 'canceled'\n if (booking.status === 'pendingPayment') {\n // A lapsed online hold released its slot; it is not an appointment.\n return Number(booking.expiresAtMs ?? 0) < nowMs ? 'canceled' : 'awaiting-online'\n }\n if (booking.inPersonPayment?.status === 'pending') return 'collecting'\n return 'payable'\n}\n\n/** The most one in-person booking charge may be: $10,000. */\nexport const BOOKING_IN_PERSON_MAX_CENTS = 1_000_000\n/** Stripe's card minimum in USD. */\nexport const BOOKING_IN_PERSON_MIN_CENTS = 50\n\n/** Null when `serviceCents` is an amount staff may charge; otherwise why not. */\nexport function bookingInPersonAmountProblem(serviceCents: unknown): string | null {\n if (typeof serviceCents !== 'number' || !Number.isInteger(serviceCents)) {\n return 'Enter the amount to charge.'\n }\n if (serviceCents < BOOKING_IN_PERSON_MIN_CENTS) return 'Card payments start at $0.50.'\n if (serviceCents > BOOKING_IN_PERSON_MAX_CENTS) return 'Charge at most $10,000 at a time.'\n return null\n}\n\n/**\n * The amount to suggest: the service's fixed price, when it has one. A\n * `varies`, `estimate` or `contact` price suggests nothing; staff type it.\n */\nexport function bookingSuggestedCents(service: {\n priceUsd?: number\n priceDisplay?: string\n} | null | undefined): number | null {\n if (!service) return null\n if (service.priceDisplay && service.priceDisplay !== 'fixed') return null\n const cents = Math.round(Number(service.priceUsd ?? 0) * 100)\n return Number.isFinite(cents) && cents >= BOOKING_IN_PERSON_MIN_CENTS ? cents : null\n}\n\n/** The start and end of the local day that holds `atMs`, in milliseconds. */\nexport function bookingDayRange(atMs: number, offsetMinutes = new Date(atMs).getTimezoneOffset()): {\n startMs: number\n endMs: number\n} {\n const local = atMs - offsetMinutes * 60_000\n const dayStart = Math.floor(local / 86_400_000) * 86_400_000\n const startMs = dayStart + offsetMinutes * 60_000\n return { startMs, endMs: startMs + 86_400_000 }\n}\n"],"names":["bookingInPersonState","booking","nowMs","Date","now","Math","round","Number","paidAmountCents","Boolean","paymentIntentId","inPersonPayment","status","expiresAtMs","BOOKING_IN_PERSON_MAX_CENTS","BOOKING_IN_PERSON_MIN_CENTS","bookingInPersonAmountProblem","serviceCents","isInteger","bookingSuggestedCents","service","priceDisplay","cents","priceUsd","isFinite","bookingDayRange","atMs","offsetMinutes","getTimezoneOffset","local","dayStart","floor","startMs","endMs"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;2CAU2C,GAE3C,qEAAqE,GAuCrE,OAAO,SAASA,qBACdC,OAA6B,EAC7BC,QAAQC,KAAKC,GAAG,EAAE;QAGEH;QAElBA,0BASEA;IAZJ,IACEI,KAAKC,KAAK,CAACC,QAAON,2BAAAA,QAAQO,eAAe,YAAvBP,2BAA2B,MAAM,KACnDQ,QAAQR,QAAQS,eAAe,KAC/BT,EAAAA,2BAAAA,QAAQU,eAAe,qBAAvBV,yBAAyBW,MAAM,MAAK,QACpC;QACA,OAAO;IACT;IACA,IAAIX,QAAQW,MAAM,KAAK,YAAY,OAAO;IAC1C,IAAIX,QAAQW,MAAM,KAAK,kBAAkB;YAEzBX;QADd,oEAAoE;QACpE,OAAOM,QAAON,uBAAAA,QAAQY,WAAW,YAAnBZ,uBAAuB,KAAKC,QAAQ,aAAa;IACjE;IACA,IAAID,EAAAA,4BAAAA,QAAQU,eAAe,qBAAvBV,0BAAyBW,MAAM,MAAK,WAAW,OAAO;IAC1D,OAAO;AACT;AAEA,2DAA2D,GAC3D,OAAO,MAAME,8BAA8B,QAAS;AACpD,kCAAkC,GAClC,OAAO,MAAMC,8BAA8B,GAAE;AAE7C,+EAA+E,GAC/E,OAAO,SAASC,6BAA6BC,YAAqB;IAChE,IAAI,OAAOA,iBAAiB,YAAY,CAACV,OAAOW,SAAS,CAACD,eAAe;QACvE,OAAO;IACT;IACA,IAAIA,eAAeF,6BAA6B,OAAO;IACvD,IAAIE,eAAeH,6BAA6B,OAAO;IACvD,OAAO;AACT;AAEA;;;CAGC,GACD,OAAO,SAASK,sBAAsBC,OAGlB;QAGcA;IAFhC,IAAI,CAACA,SAAS,OAAO;IACrB,IAAIA,QAAQC,YAAY,IAAID,QAAQC,YAAY,KAAK,SAAS,OAAO;IACrE,MAAMC,QAAQjB,KAAKC,KAAK,CAACC,QAAOa,oBAAAA,QAAQG,QAAQ,YAAhBH,oBAAoB,KAAK;IACzD,OAAOb,OAAOiB,QAAQ,CAACF,UAAUA,SAASP,8BAA8BO,QAAQ;AAClF;AAEA,2EAA2E,GAC3E,OAAO,SAASG,gBAAgBC,IAAY,EAAEC,gBAAgB,IAAIxB,KAAKuB,MAAME,iBAAiB,EAAE;IAI9F,MAAMC,QAAQH,OAAOC,gBAAgB;IACrC,MAAMG,WAAWzB,KAAK0B,KAAK,CAACF,QAAQ,YAAc;IAClD,MAAMG,UAAUF,WAAWH,gBAAgB;IAC3C,OAAO;QAAEK;QAASC,OAAOD,UAAU;IAAW;AAChD"}
|
|
@@ -0,0 +1,66 @@
|
|
|
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
|
+
/**
|
|
18
|
+
* What a site's team may do to a booking once it exists (AGL-3621): check
|
|
19
|
+
* the guest in, move it, cancel it. One pure rule set, read by the routes
|
|
20
|
+
* that do it (`bookings/check-in`, `bookings/reschedule`) under their own
|
|
21
|
+
* transaction, and the rules a client mirrors to decide what to OFFER (the
|
|
22
|
+
* native Aglyn app's bookings screens port them, cased by this file's spec),
|
|
23
|
+
* so a screen can only offer what the route would then do.
|
|
24
|
+
*
|
|
25
|
+
* No I/O and no React, so client and server code alike may import it.
|
|
26
|
+
*/
|
|
27
|
+
/** `hosts/{hostId}/bookings/{id}`, the fields these rules read. */
|
|
28
|
+
export interface ManagedBooking {
|
|
29
|
+
status?: string;
|
|
30
|
+
startsAtMs?: number;
|
|
31
|
+
endsAtMs?: number;
|
|
32
|
+
expiresAtMs?: number;
|
|
33
|
+
paidAmountCents?: number;
|
|
34
|
+
refundedCents?: number;
|
|
35
|
+
checkedInAtMs?: number;
|
|
36
|
+
}
|
|
37
|
+
export type BookingState = 'confirmed' | 'pendingPayment' | 'expired' | 'canceled';
|
|
38
|
+
/**
|
|
39
|
+
* The booking's state as a reader should treat it. A payment hold whose
|
|
40
|
+
* fifteen minutes ran out released its slot (AGL-170) even while the
|
|
41
|
+
* document still says `pendingPayment`.
|
|
42
|
+
*/
|
|
43
|
+
export declare function bookingState(booking: ManagedBooking, nowMs: number): BookingState;
|
|
44
|
+
export declare const BOOKING_STATE_LABELS: Readonly<Record<BookingState, string>>;
|
|
45
|
+
/** The paid amount not yet refunded, in cents. */
|
|
46
|
+
export declare function bookingOutstandingCents(booking: ManagedBooking): number;
|
|
47
|
+
export interface BookingActions {
|
|
48
|
+
checkIn: boolean;
|
|
49
|
+
undoCheckIn: boolean;
|
|
50
|
+
reschedule: boolean;
|
|
51
|
+
/** Cancel; with `refundCents` above zero the cancel goes through the refund route. */
|
|
52
|
+
cancel: boolean;
|
|
53
|
+
refundCents: number;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* What may be done to a booking now. A guest is checked in only on a
|
|
57
|
+
* confirmed booking; a booking moves only while it is confirmed and has not
|
|
58
|
+
* ended; and nothing is done to a canceled one.
|
|
59
|
+
*/
|
|
60
|
+
export declare function bookingActions(booking: ManagedBooking, nowMs: number): BookingActions;
|
|
61
|
+
/** Why a check-in (or its undo) cannot be written, or null. The route's own refusal. */
|
|
62
|
+
export declare function checkInRefusal(booking: ManagedBooking, checkedIn: boolean, nowMs: number): string | null;
|
|
63
|
+
/** Why a booking cannot move to `startsAtMs`, or null. The slot itself is checked apart. */
|
|
64
|
+
export declare function rescheduleRefusal(booking: ManagedBooking, startsAtMs: number, nowMs: number): string | null;
|
|
65
|
+
/** The booking's length, kept when it moves: what the guest booked, not what the service says today. */
|
|
66
|
+
export declare function bookingDurationMs(booking: ManagedBooking, fallbackMinutes: number): number;
|
|
@@ -0,0 +1,90 @@
|
|
|
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
|
+
* What a site's team may do to a booking once it exists (AGL-3621): check
|
|
18
|
+
* the guest in, move it, cancel it. One pure rule set, read by the routes
|
|
19
|
+
* that do it (`bookings/check-in`, `bookings/reschedule`) under their own
|
|
20
|
+
* transaction, and the rules a client mirrors to decide what to OFFER (the
|
|
21
|
+
* native Aglyn app's bookings screens port them, cased by this file's spec),
|
|
22
|
+
* so a screen can only offer what the route would then do.
|
|
23
|
+
*
|
|
24
|
+
* No I/O and no React, so client and server code alike may import it.
|
|
25
|
+
*/ /** `hosts/{hostId}/bookings/{id}`, the fields these rules read. */ /**
|
|
26
|
+
* The booking's state as a reader should treat it. A payment hold whose
|
|
27
|
+
* fifteen minutes ran out released its slot (AGL-170) even while the
|
|
28
|
+
* document still says `pendingPayment`.
|
|
29
|
+
*/ export function bookingState(booking, nowMs) {
|
|
30
|
+
if (booking.status === 'canceled') return 'canceled';
|
|
31
|
+
if (booking.status === 'pendingPayment') {
|
|
32
|
+
var _booking_expiresAtMs;
|
|
33
|
+
return Number((_booking_expiresAtMs = booking.expiresAtMs) != null ? _booking_expiresAtMs : 0) < nowMs ? 'expired' : 'pendingPayment';
|
|
34
|
+
}
|
|
35
|
+
return 'confirmed';
|
|
36
|
+
}
|
|
37
|
+
export const BOOKING_STATE_LABELS = {
|
|
38
|
+
confirmed: 'Confirmed',
|
|
39
|
+
pendingPayment: 'Awaiting payment',
|
|
40
|
+
expired: 'Payment not finished',
|
|
41
|
+
canceled: 'Canceled'
|
|
42
|
+
};
|
|
43
|
+
/** The paid amount not yet refunded, in cents. */ export function bookingOutstandingCents(booking) {
|
|
44
|
+
var _booking_paidAmountCents, _booking_refundedCents;
|
|
45
|
+
const paid = Math.max(0, Math.round(Number((_booking_paidAmountCents = booking.paidAmountCents) != null ? _booking_paidAmountCents : 0)));
|
|
46
|
+
const refunded = Math.max(0, Math.round(Number((_booking_refundedCents = booking.refundedCents) != null ? _booking_refundedCents : 0)));
|
|
47
|
+
return Math.max(0, paid - refunded);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* What may be done to a booking now. A guest is checked in only on a
|
|
51
|
+
* confirmed booking; a booking moves only while it is confirmed and has not
|
|
52
|
+
* ended; and nothing is done to a canceled one.
|
|
53
|
+
*/ export function bookingActions(booking, nowMs) {
|
|
54
|
+
var _booking_checkedInAtMs, _booking_endsAtMs;
|
|
55
|
+
const state = bookingState(booking, nowMs);
|
|
56
|
+
const confirmed = state === 'confirmed';
|
|
57
|
+
const checkedIn = Number((_booking_checkedInAtMs = booking.checkedInAtMs) != null ? _booking_checkedInAtMs : 0) > 0;
|
|
58
|
+
const ended = Number((_booking_endsAtMs = booking.endsAtMs) != null ? _booking_endsAtMs : 0) <= nowMs;
|
|
59
|
+
return {
|
|
60
|
+
checkIn: confirmed && !checkedIn,
|
|
61
|
+
undoCheckIn: confirmed && checkedIn,
|
|
62
|
+
reschedule: confirmed && !checkedIn && !ended,
|
|
63
|
+
cancel: state !== 'canceled' && !checkedIn,
|
|
64
|
+
refundCents: bookingOutstandingCents(booking)
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/** Why a check-in (or its undo) cannot be written, or null. The route's own refusal. */ export function checkInRefusal(booking, checkedIn, nowMs) {
|
|
68
|
+
var _booking_checkedInAtMs;
|
|
69
|
+
const state = bookingState(booking, nowMs);
|
|
70
|
+
if (state === 'canceled') return 'This booking was canceled';
|
|
71
|
+
if (state !== 'confirmed') return 'This booking is not paid yet';
|
|
72
|
+
return checkedIn || Number((_booking_checkedInAtMs = booking.checkedInAtMs) != null ? _booking_checkedInAtMs : 0) > 0 ? null : 'This guest is not checked in';
|
|
73
|
+
}
|
|
74
|
+
/** Why a booking cannot move to `startsAtMs`, or null. The slot itself is checked apart. */ export function rescheduleRefusal(booking, startsAtMs, nowMs) {
|
|
75
|
+
var _booking_checkedInAtMs, _booking_endsAtMs;
|
|
76
|
+
const state = bookingState(booking, nowMs);
|
|
77
|
+
if (state === 'canceled') return 'This booking was canceled';
|
|
78
|
+
if (state !== 'confirmed') return 'This booking is not paid yet';
|
|
79
|
+
if (Number((_booking_checkedInAtMs = booking.checkedInAtMs) != null ? _booking_checkedInAtMs : 0) > 0) return 'This guest is already checked in';
|
|
80
|
+
if (Number((_booking_endsAtMs = booking.endsAtMs) != null ? _booking_endsAtMs : 0) <= nowMs) return 'This booking has already ended';
|
|
81
|
+
if (!Number.isFinite(startsAtMs) || startsAtMs <= nowMs) return 'Pick a time that has not passed';
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
/** The booking's length, kept when it moves: what the guest booked, not what the service says today. */ export function bookingDurationMs(booking, fallbackMinutes) {
|
|
85
|
+
var _booking_endsAtMs, _booking_startsAtMs;
|
|
86
|
+
const stored = Number((_booking_endsAtMs = booking.endsAtMs) != null ? _booking_endsAtMs : 0) - Number((_booking_startsAtMs = booking.startsAtMs) != null ? _booking_startsAtMs : 0);
|
|
87
|
+
return stored > 0 ? stored : Math.max(5, Math.round(fallbackMinutes || 30)) * 60000;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
//# sourceMappingURL=booking-manage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/bookings/src/lib/model/booking-manage.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\n/**\n * What a site's team may do to a booking once it exists (AGL-3621): check\n * the guest in, move it, cancel it. One pure rule set, read by the routes\n * that do it (`bookings/check-in`, `bookings/reschedule`) under their own\n * transaction, and the rules a client mirrors to decide what to OFFER (the\n * native Aglyn app's bookings screens port them, cased by this file's spec),\n * so a screen can only offer what the route would then do.\n *\n * No I/O and no React, so client and server code alike may import it.\n */\n\n/** `hosts/{hostId}/bookings/{id}`, the fields these rules read. */\nexport interface ManagedBooking {\n status?: string\n startsAtMs?: number\n endsAtMs?: number\n expiresAtMs?: number\n paidAmountCents?: number\n refundedCents?: number\n checkedInAtMs?: number\n}\n\nexport type BookingState = 'confirmed' | 'pendingPayment' | 'expired' | 'canceled'\n\n/**\n * The booking's state as a reader should treat it. A payment hold whose\n * fifteen minutes ran out released its slot (AGL-170) even while the\n * document still says `pendingPayment`.\n */\nexport function bookingState(booking: ManagedBooking, nowMs: number): BookingState {\n if (booking.status === 'canceled') return 'canceled'\n if (booking.status === 'pendingPayment') {\n return Number(booking.expiresAtMs ?? 0) < nowMs ? 'expired' : 'pendingPayment'\n }\n return 'confirmed'\n}\n\nexport const BOOKING_STATE_LABELS: Readonly<Record<BookingState, string>> = {\n confirmed: 'Confirmed',\n pendingPayment: 'Awaiting payment',\n expired: 'Payment not finished',\n canceled: 'Canceled',\n}\n\n/** The paid amount not yet refunded, in cents. */\nexport function bookingOutstandingCents(booking: ManagedBooking): number {\n const paid = Math.max(0, Math.round(Number(booking.paidAmountCents ?? 0)))\n const refunded = Math.max(0, Math.round(Number(booking.refundedCents ?? 0)))\n return Math.max(0, paid - refunded)\n}\n\nexport interface BookingActions {\n checkIn: boolean\n undoCheckIn: boolean\n reschedule: boolean\n /** Cancel; with `refundCents` above zero the cancel goes through the refund route. */\n cancel: boolean\n refundCents: number\n}\n\n/**\n * What may be done to a booking now. A guest is checked in only on a\n * confirmed booking; a booking moves only while it is confirmed and has not\n * ended; and nothing is done to a canceled one.\n */\nexport function bookingActions(booking: ManagedBooking, nowMs: number): BookingActions {\n const state = bookingState(booking, nowMs)\n const confirmed = state === 'confirmed'\n const checkedIn = Number(booking.checkedInAtMs ?? 0) > 0\n const ended = Number(booking.endsAtMs ?? 0) <= nowMs\n return {\n checkIn: confirmed && !checkedIn,\n undoCheckIn: confirmed && checkedIn,\n reschedule: confirmed && !checkedIn && !ended,\n cancel: state !== 'canceled' && !checkedIn,\n refundCents: bookingOutstandingCents(booking),\n }\n}\n\n/** Why a check-in (or its undo) cannot be written, or null. The route's own refusal. */\nexport function checkInRefusal(booking: ManagedBooking, checkedIn: boolean, nowMs: number): string | null {\n const state = bookingState(booking, nowMs)\n if (state === 'canceled') return 'This booking was canceled'\n if (state !== 'confirmed') return 'This booking is not paid yet'\n return checkedIn || Number(booking.checkedInAtMs ?? 0) > 0 ? null : 'This guest is not checked in'\n}\n\n/** Why a booking cannot move to `startsAtMs`, or null. The slot itself is checked apart. */\nexport function rescheduleRefusal(booking: ManagedBooking, startsAtMs: number, nowMs: number): string | null {\n const state = bookingState(booking, nowMs)\n if (state === 'canceled') return 'This booking was canceled'\n if (state !== 'confirmed') return 'This booking is not paid yet'\n if (Number(booking.checkedInAtMs ?? 0) > 0) return 'This guest is already checked in'\n if (Number(booking.endsAtMs ?? 0) <= nowMs) return 'This booking has already ended'\n if (!Number.isFinite(startsAtMs) || startsAtMs <= nowMs) return 'Pick a time that has not passed'\n return null\n}\n\n/** The booking's length, kept when it moves: what the guest booked, not what the service says today. */\nexport function bookingDurationMs(booking: ManagedBooking, fallbackMinutes: number): number {\n const stored = Number(booking.endsAtMs ?? 0) - Number(booking.startsAtMs ?? 0)\n return stored > 0 ? stored : Math.max(5, Math.round(fallbackMinutes || 30)) * 60_000\n}\n"],"names":["bookingState","booking","nowMs","status","Number","expiresAtMs","BOOKING_STATE_LABELS","confirmed","pendingPayment","expired","canceled","bookingOutstandingCents","paid","Math","max","round","paidAmountCents","refunded","refundedCents","bookingActions","state","checkedIn","checkedInAtMs","ended","endsAtMs","checkIn","undoCheckIn","reschedule","cancel","refundCents","checkInRefusal","rescheduleRefusal","startsAtMs","isFinite","bookingDurationMs","fallbackMinutes","stored"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;CASC,GAED,iEAAiE,GAajE;;;;CAIC,GACD,OAAO,SAASA,aAAaC,OAAuB,EAAEC,KAAa;IACjE,IAAID,QAAQE,MAAM,KAAK,YAAY,OAAO;IAC1C,IAAIF,QAAQE,MAAM,KAAK,kBAAkB;YACzBF;QAAd,OAAOG,QAAOH,uBAAAA,QAAQI,WAAW,YAAnBJ,uBAAuB,KAAKC,QAAQ,YAAY;IAChE;IACA,OAAO;AACT;AAEA,OAAO,MAAMI,uBAA+D;IAC1EC,WAAW;IACXC,gBAAgB;IAChBC,SAAS;IACTC,UAAU;AACZ,EAAC;AAED,gDAAgD,GAChD,OAAO,SAASC,wBAAwBV,OAAuB;QAClBA,0BACIA;IAD/C,MAAMW,OAAOC,KAAKC,GAAG,CAAC,GAAGD,KAAKE,KAAK,CAACX,QAAOH,2BAAAA,QAAQe,eAAe,YAAvBf,2BAA2B;IACtE,MAAMgB,WAAWJ,KAAKC,GAAG,CAAC,GAAGD,KAAKE,KAAK,CAACX,QAAOH,yBAAAA,QAAQiB,aAAa,YAArBjB,yBAAyB;IACxE,OAAOY,KAAKC,GAAG,CAAC,GAAGF,OAAOK;AAC5B;AAWA;;;;CAIC,GACD,OAAO,SAASE,eAAelB,OAAuB,EAAEC,KAAa;QAG1CD,wBACJA;IAHrB,MAAMmB,QAAQpB,aAAaC,SAASC;IACpC,MAAMK,YAAYa,UAAU;IAC5B,MAAMC,YAAYjB,QAAOH,yBAAAA,QAAQqB,aAAa,YAArBrB,yBAAyB,KAAK;IACvD,MAAMsB,QAAQnB,QAAOH,oBAAAA,QAAQuB,QAAQ,YAAhBvB,oBAAoB,MAAMC;IAC/C,OAAO;QACLuB,SAASlB,aAAa,CAACc;QACvBK,aAAanB,aAAac;QAC1BM,YAAYpB,aAAa,CAACc,aAAa,CAACE;QACxCK,QAAQR,UAAU,cAAc,CAACC;QACjCQ,aAAalB,wBAAwBV;IACvC;AACF;AAEA,sFAAsF,GACtF,OAAO,SAAS6B,eAAe7B,OAAuB,EAAEoB,SAAkB,EAAEnB,KAAa;QAI5DD;IAH3B,MAAMmB,QAAQpB,aAAaC,SAASC;IACpC,IAAIkB,UAAU,YAAY,OAAO;IACjC,IAAIA,UAAU,aAAa,OAAO;IAClC,OAAOC,aAAajB,QAAOH,yBAAAA,QAAQqB,aAAa,YAArBrB,yBAAyB,KAAK,IAAI,OAAO;AACtE;AAEA,0FAA0F,GAC1F,OAAO,SAAS8B,kBAAkB9B,OAAuB,EAAE+B,UAAkB,EAAE9B,KAAa;QAI/ED,wBACAA;IAJX,MAAMmB,QAAQpB,aAAaC,SAASC;IACpC,IAAIkB,UAAU,YAAY,OAAO;IACjC,IAAIA,UAAU,aAAa,OAAO;IAClC,IAAIhB,QAAOH,yBAAAA,QAAQqB,aAAa,YAArBrB,yBAAyB,KAAK,GAAG,OAAO;IACnD,IAAIG,QAAOH,oBAAAA,QAAQuB,QAAQ,YAAhBvB,oBAAoB,MAAMC,OAAO,OAAO;IACnD,IAAI,CAACE,OAAO6B,QAAQ,CAACD,eAAeA,cAAc9B,OAAO,OAAO;IAChE,OAAO;AACT;AAEA,sGAAsG,GACtG,OAAO,SAASgC,kBAAkBjC,OAAuB,EAAEkC,eAAuB;QAC1DlC,mBAAgCA;IAAtD,MAAMmC,SAAShC,QAAOH,oBAAAA,QAAQuB,QAAQ,YAAhBvB,oBAAoB,KAAKG,QAAOH,sBAAAA,QAAQ+B,UAAU,YAAlB/B,sBAAsB;IAC5E,OAAOmC,SAAS,IAAIA,SAASvB,KAAKC,GAAG,CAAC,GAAGD,KAAKE,KAAK,CAACoB,mBAAmB,OAAO;AAChF"}
|
|
@@ -0,0 +1,77 @@
|
|
|
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 { PluginApiHandler } from '@aglyn/aglyn/app-utils/api-plugins';
|
|
18
|
+
import { type HostBookingService } from '../model/bookings';
|
|
19
|
+
/** The Firestore surface these routes use: the Admin SDK's, narrowed so a spec can hand in a double. */
|
|
20
|
+
export interface ManageDocSnapshot {
|
|
21
|
+
readonly exists: boolean;
|
|
22
|
+
readonly id?: string;
|
|
23
|
+
get(field: string): unknown;
|
|
24
|
+
data(): Record<string, unknown> | undefined;
|
|
25
|
+
}
|
|
26
|
+
export interface ManageDocRef {
|
|
27
|
+
readonly id: string;
|
|
28
|
+
}
|
|
29
|
+
export interface ManageQuery {
|
|
30
|
+
where(field: string, op: '==' | '>=' | '<', value: unknown): ManageQuery;
|
|
31
|
+
limit(count: number): ManageQuery;
|
|
32
|
+
}
|
|
33
|
+
export interface ManageTransaction {
|
|
34
|
+
get(ref: ManageDocRef): Promise<ManageDocSnapshot>;
|
|
35
|
+
get(query: ManageQuery): Promise<{
|
|
36
|
+
docs: ManageDocSnapshot[];
|
|
37
|
+
}>;
|
|
38
|
+
update(ref: ManageDocRef, data: Record<string, unknown>): unknown;
|
|
39
|
+
}
|
|
40
|
+
export interface ManageFirestore {
|
|
41
|
+
doc(path: string): ManageDocRef & {
|
|
42
|
+
get(): Promise<ManageDocSnapshot>;
|
|
43
|
+
};
|
|
44
|
+
collection(path: string): ManageQuery;
|
|
45
|
+
runTransaction<T>(fn: (transaction: ManageTransaction) => Promise<T>): Promise<T>;
|
|
46
|
+
}
|
|
47
|
+
export interface RescheduleNotice {
|
|
48
|
+
hostId: string;
|
|
49
|
+
bookingId: string;
|
|
50
|
+
to: string;
|
|
51
|
+
name: string;
|
|
52
|
+
serviceName: string;
|
|
53
|
+
when: string;
|
|
54
|
+
previousWhen: string;
|
|
55
|
+
timezone: string;
|
|
56
|
+
}
|
|
57
|
+
export interface BookingManageDeps {
|
|
58
|
+
firestore: () => ManageFirestore;
|
|
59
|
+
verifyIdToken: (token: string) => Promise<{
|
|
60
|
+
uid: string;
|
|
61
|
+
}>;
|
|
62
|
+
/** True when the site is locked down and must not take writes. */
|
|
63
|
+
siteLocked: (hostId: string) => Promise<boolean>;
|
|
64
|
+
/** The zone a booking is told in when the document carries none. */
|
|
65
|
+
timeZoneFor: (hostId: string, service: HostBookingService | undefined) => Promise<string>;
|
|
66
|
+
notifyRescheduled: (notice: RescheduleNotice) => Promise<void>;
|
|
67
|
+
now: () => number;
|
|
68
|
+
}
|
|
69
|
+
/** Field the booking stamps when the guest arrives. */
|
|
70
|
+
export declare const CHECKED_IN_AT = "checkedInAtMs";
|
|
71
|
+
export declare const CHECKED_IN_BY = "checkedInBy";
|
|
72
|
+
export declare function createBookingCheckInHandler(deps: BookingManageDeps): PluginApiHandler;
|
|
73
|
+
export declare function createBookingRescheduleHandler(deps: BookingManageDeps): PluginApiHandler;
|
|
74
|
+
/** The email that tells a guest their booking moved, as the confirmation is sent. */
|
|
75
|
+
export declare function sendRescheduledNotice(notice: RescheduleNotice): Promise<void>;
|
|
76
|
+
/** The production wiring: the Admin SDK, the site lockdown verdict and the real send. */
|
|
77
|
+
export declare function bookingManageDeps(): BookingManageDeps;
|