@final-commerce/command-frame 0.8.0-online.4 → 0.8.0-preprod.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/dist/CommonTypes.d.ts +26 -1
- package/dist/actions/add-booking-to-cart/action.d.ts +6 -0
- package/dist/actions/add-booking-to-cart/action.js +8 -0
- package/dist/actions/add-booking-to-cart/mock.d.ts +2 -0
- package/dist/actions/add-booking-to-cart/mock.js +66 -0
- package/dist/actions/add-booking-to-cart/types.d.ts +27 -0
- package/dist/actions/cancel-booking/action.d.ts +6 -0
- package/dist/actions/cancel-booking/action.js +8 -0
- package/dist/actions/cancel-booking/mock.d.ts +2 -0
- package/dist/actions/cancel-booking/mock.js +31 -0
- package/dist/actions/cancel-booking/types.d.ts +14 -0
- package/dist/actions/get-booking-availability/action.d.ts +6 -0
- package/dist/actions/get-booking-availability/action.js +8 -0
- package/dist/actions/get-booking-availability/mock.d.ts +2 -0
- package/dist/actions/get-booking-availability/mock.js +12 -0
- package/dist/actions/get-booking-availability/types.d.ts +37 -0
- package/dist/actions/get-booking-resources/action.d.ts +6 -0
- package/dist/actions/get-booking-resources/action.js +8 -0
- package/dist/actions/get-booking-resources/mock.d.ts +2 -0
- package/dist/actions/get-booking-resources/mock.js +7 -0
- package/dist/actions/get-booking-resources/types.d.ts +16 -0
- package/dist/actions/get-booking-resources/types.js +1 -0
- package/dist/actions/get-bookings/action.d.ts +6 -0
- package/dist/actions/get-bookings/action.js +8 -0
- package/dist/actions/get-bookings/mock.d.ts +2 -0
- package/dist/actions/get-bookings/mock.js +20 -0
- package/dist/actions/get-bookings/types.d.ts +22 -0
- package/dist/actions/get-bookings/types.js +1 -0
- package/dist/actions/get-context/mock.js +4 -0
- package/dist/actions/get-product-modifier-selections/action.d.ts +1 -1
- package/dist/actions/get-product-modifier-selections/action.js +2 -2
- package/dist/actions/get-product-modifier-selections/mock.d.ts +1 -1
- package/dist/actions/get-product-modifier-selections/mock.js +5 -5
- package/dist/actions/get-product-modifier-selections/types.d.ts +1 -1
- package/dist/actions/get-products/mock.js +6 -0
- package/dist/actions/get-products/types.d.ts +25 -3
- package/dist/actions/get-refund-plan/types.d.ts +5 -2
- package/dist/actions/get-remaining-refundable-quantities/mock.js +1 -0
- package/dist/actions/get-remaining-refundable-quantities/types.d.ts +8 -0
- package/dist/actions/hold-booking/action.d.ts +6 -0
- package/dist/actions/hold-booking/action.js +8 -0
- package/dist/actions/hold-booking/mock.d.ts +2 -0
- package/dist/actions/hold-booking/mock.js +37 -0
- package/dist/actions/hold-booking/types.d.ts +28 -0
- package/dist/actions/hold-booking/types.js +1 -0
- package/dist/actions/process-partial-refund/types.d.ts +2 -2
- package/dist/actions/remove-booking-from-cart/action.d.ts +6 -0
- package/dist/actions/remove-booking-from-cart/action.js +8 -0
- package/dist/actions/remove-booking-from-cart/mock.d.ts +2 -0
- package/dist/actions/remove-booking-from-cart/mock.js +40 -0
- package/dist/actions/remove-booking-from-cart/types.d.ts +16 -0
- package/dist/actions/remove-booking-from-cart/types.js +1 -0
- package/dist/actions/set-product-modifier-selections/action.d.ts +1 -1
- package/dist/actions/set-product-modifier-selections/action.js +2 -2
- package/dist/actions/set-product-modifier-selections/mock.d.ts +1 -1
- package/dist/actions/set-product-modifier-selections/mock.js +7 -7
- package/dist/actions/set-product-modifier-selections/types.d.ts +2 -2
- package/dist/demo/database.d.ts +18 -1
- package/dist/demo/database.js +183 -0
- package/dist/index.d.ts +16 -6
- package/dist/index.js +15 -5
- package/dist/projects/render/mocks.js +14 -4
- package/dist/projects/render/types.d.ts +8 -13
- package/dist/pubsub/topics/bookings/booking-created/types.d.ts +6 -0
- package/dist/pubsub/topics/bookings/booking-created/types.js +1 -0
- package/dist/pubsub/topics/bookings/booking-setup-changed/types.d.ts +15 -0
- package/dist/pubsub/topics/bookings/booking-setup-changed/types.js +1 -0
- package/dist/pubsub/topics/bookings/booking-updated/types.d.ts +6 -0
- package/dist/pubsub/topics/bookings/booking-updated/types.js +1 -0
- package/dist/pubsub/topics/bookings/index.d.ts +3 -0
- package/dist/pubsub/topics/bookings/index.js +27 -0
- package/dist/pubsub/topics/bookings/types.d.ts +8 -0
- package/dist/pubsub/topics/bookings/types.js +3 -0
- package/dist/pubsub/topics/cart/product-modifiers-updated/types.d.ts +2 -2
- package/dist/pubsub/topics/cart/types.d.ts +33 -33
- package/dist/pubsub/topics/cart/types.js +16 -16
- package/dist/pubsub/topics/index.d.ts +17 -17
- package/dist/pubsub/topics/index.js +17 -17
- package/dist/pubsub/topics/types.d.ts +2 -8
- package/package.json +5 -2
- package/dist/actions/resume-checkout/action.d.ts +0 -6
- package/dist/actions/resume-checkout/action.js +0 -8
- package/dist/actions/resume-checkout/mock.d.ts +0 -16
- package/dist/actions/resume-checkout/mock.js +0 -51
- package/dist/actions/resume-checkout/types.d.ts +0 -59
- package/dist/actions/resume-checkout/types.js +0 -13
- package/dist/actions/start-checkout/action.d.ts +0 -6
- package/dist/actions/start-checkout/action.js +0 -8
- package/dist/actions/start-checkout/mock.d.ts +0 -16
- package/dist/actions/start-checkout/mock.js +0 -80
- package/dist/actions/start-checkout/types.d.ts +0 -81
- package/dist/actions/start-checkout/types.js +0 -10
- package/dist/pubsub/topics/checkout/checkout-started/types.d.ts +0 -26
- package/dist/pubsub/topics/checkout/index.d.ts +0 -7
- package/dist/pubsub/topics/checkout/index.js +0 -28
- package/dist/pubsub/topics/checkout/payment-completed/types.d.ts +0 -31
- package/dist/pubsub/topics/checkout/payment-failed/types.d.ts +0 -20
- package/dist/pubsub/topics/checkout/types.d.ts +0 -12
- package/dist/pubsub/topics/checkout/types.js +0 -8
- /package/dist/{pubsub/topics/checkout/checkout-started → actions/add-booking-to-cart}/types.js +0 -0
- /package/dist/{pubsub/topics/checkout/payment-completed → actions/cancel-booking}/types.js +0 -0
- /package/dist/{pubsub/topics/checkout/payment-failed → actions/get-booking-availability}/types.js +0 -0
package/dist/CommonTypes.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export * from './common-types';
|
|
2
|
-
import type { Tax, Tip, Address, MetadataItem, PosDataItem, CartFeeTaxEntry, CartFeeItem, CartDiscountItem, OrderNote, NonRevenueItem, DiscountDetail, FeeDetail, DiscountLineItem, FeeLineItem, LineItem, TipPayment, RefundedTipPayment, PaymentMethod, Discount, CustomFee, Summary, CustomSale, RefundedCustomSale, RefundItem, RefundedLineItem, ActiveStation, ActiveSession, ActiveOutlet, ActiveUser, ActiveUserRole, ActiveOrder, ActiveCart, ActivePark, ActiveCompany, ActiveCustomSales, ActiveProduct, ActiveCustomer, FullProduct, ProductVariant, Inventory, ResolvedModifier, ResolvedModifierChoice, ModifierSelectionType, ModifierSelection, ModifierChoiceSelection, CartLineModifier, ProdModifierBreakdown, OrderLineItemModifier, CustomerNote, Attribute, AttributeOption, Category, Transaction, ActiveSplitPayment } from '@final-commerce/common/pos-types';
|
|
2
|
+
import type { Tax, Tip, Address, MetadataItem, PosDataItem, CartFeeTaxEntry, CartFeeItem, CartDiscountItem, OrderNote, NonRevenueItem, DiscountDetail, FeeDetail, DiscountLineItem, FeeLineItem, LineItem, TipPayment, RefundedTipPayment, PaymentMethod, Discount, CustomFee, Summary, CustomSale, RefundedCustomSale, RefundItem, RefundedLineItem, ActiveStation, ActiveSession, ActiveOutlet, ActiveUser, ActiveUserRole, ActiveOrder, ActiveCart, ActivePark, ActiveCompany, ActiveCustomSales, ActiveProduct, ActiveCustomer, FullProduct, ProductVariant, Inventory, ResolvedModifier, ResolvedModifierChoice, ModifierSelectionType, ModifierSelection, ModifierChoiceSelection, CartLineModifier, ProdModifierBreakdown, OrderLineItemModifier, CustomerNote, Attribute, AttributeOption, Category, Transaction, ActiveSplitPayment, BookingAvailability, BookingSlot, BookingSlotResource, BookingResource, Booking, CartReservation, OrderReservation } from '@final-commerce/common/pos-types';
|
|
3
3
|
export { CurrencyCode, ProductType as CFProductType, UserTypes as CFUserTypes } from '@final-commerce/common';
|
|
4
4
|
/** Open record used as a base for active entities. */
|
|
5
5
|
export type CFActiveEntity = Record<string, unknown>;
|
|
@@ -18,6 +18,16 @@ export type CFTransaction = Transaction;
|
|
|
18
18
|
export type CFCategory = Category;
|
|
19
19
|
export type CFProductVariant = ProductVariant;
|
|
20
20
|
export type CFProduct = FullProduct;
|
|
21
|
+
export type CFBookingAvailability = BookingAvailability;
|
|
22
|
+
export type CFBookingSlot = BookingSlot;
|
|
23
|
+
export type CFBookingSlotResource = BookingSlotResource;
|
|
24
|
+
export type CFBookingResource = BookingResource;
|
|
25
|
+
export type CFBooking = Booking;
|
|
26
|
+
/** A service sitting in the cart, and the same sale once it is on an order. A flow that draws a
|
|
27
|
+
* cart or a receipt needs both by name — reaching into the package's dist for them is not an
|
|
28
|
+
* interface. */
|
|
29
|
+
export type CFCartReservation = CartReservation;
|
|
30
|
+
export type CFOrderReservation = OrderReservation;
|
|
21
31
|
/** Modifier (the till question) resolved onto a product — `CFProduct.modifiers`. */
|
|
22
32
|
export type CFResolvedModifier = ResolvedModifier;
|
|
23
33
|
export type CFResolvedModifierChoice = ResolvedModifierChoice;
|
|
@@ -119,6 +129,21 @@ export interface CFContextRender {
|
|
|
119
129
|
stationName: string | null;
|
|
120
130
|
outletId: string | null;
|
|
121
131
|
outletName: string | null;
|
|
132
|
+
/**
|
|
133
|
+
* IANA zone the SHOP keeps, resolved as the outlet's own zone, else the
|
|
134
|
+
* company's — never the device's. A booking is stored as an instant, so which
|
|
135
|
+
* hour and which DAY it reads as is decided entirely by the zone it is printed
|
|
136
|
+
* in, and the screen's own machine is the one zone that is certainly wrong: a
|
|
137
|
+
* 17:00 Monday appointment in Vancouver is 21:30 for a till in St John's, and
|
|
138
|
+
* for some viewers it lands on Tuesday. Without this an app cannot label a
|
|
139
|
+
* slot or an appointment honestly — it can only guess, and it guesses wrong
|
|
140
|
+
* for every business that does not sit in the same zone as its screen.
|
|
141
|
+
*
|
|
142
|
+
* `null` when neither the outlet nor the company has one configured. Say so
|
|
143
|
+
* rather than falling back to the device: a missing timezone is a setup
|
|
144
|
+
* problem, and printing the machine's clock hides it.
|
|
145
|
+
*/
|
|
146
|
+
timeZone: string | null;
|
|
122
147
|
buildId: string | null;
|
|
123
148
|
buildName: string | null;
|
|
124
149
|
buildVersion: string | null;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Add booking to cart action
|
|
3
|
+
* Calls the addBookingToCart action on the parent window
|
|
4
|
+
*/
|
|
5
|
+
import { commandFrameClient } from '../../client';
|
|
6
|
+
export const addBookingToCart = async (params) => {
|
|
7
|
+
return await commandFrameClient.call('addBookingToCart', params);
|
|
8
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { MOCK_CART, MOCK_PRODUCTS, mockPublishEvent } from '../../demo/database';
|
|
2
|
+
import { mockHoldBooking } from '../hold-booking/mock';
|
|
3
|
+
export const mockAddBookingToCart = async (params) => {
|
|
4
|
+
console.log('[Mock] addBookingToCart called', params);
|
|
5
|
+
// One call, two effects — the window is claimed AND the service is in the cart. The claim goes
|
|
6
|
+
// first and its refusal propagates: a cart line for a window somebody else took is worse than
|
|
7
|
+
// no line at all.
|
|
8
|
+
const held = await mockHoldBooking(params);
|
|
9
|
+
if (!held.success || !held.booking) {
|
|
10
|
+
return { success: false, reason: held.reason, timestamp: new Date().toISOString() };
|
|
11
|
+
}
|
|
12
|
+
const booking = held.booking;
|
|
13
|
+
// An unknown product used to be sold as "Booking" at zero. The host refuses it, and a mock that
|
|
14
|
+
// quietly sells something no catalogue contains teaches a screen a case that cannot happen.
|
|
15
|
+
const product = MOCK_PRODUCTS.find(({ _id }) => _id === params.productId);
|
|
16
|
+
if (!product) {
|
|
17
|
+
return { success: false, reason: `No product ${params.productId}`, timestamp: new Date().toISOString() };
|
|
18
|
+
}
|
|
19
|
+
// A named variant is honoured or refused, never substituted — the host does the same.
|
|
20
|
+
const variant = params.variantId
|
|
21
|
+
? product.variants?.find(({ _id }) => _id === params.variantId)
|
|
22
|
+
: product.variants?.[0];
|
|
23
|
+
if (params.variantId && !variant) {
|
|
24
|
+
return {
|
|
25
|
+
success: false,
|
|
26
|
+
reason: `${product.name} has no variant ${params.variantId}`,
|
|
27
|
+
timestamp: new Date().toISOString(),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
const price = variant?.price ?? product.minPrice ?? 0;
|
|
31
|
+
const reservation = {
|
|
32
|
+
internalId: `res_${booking.id}`,
|
|
33
|
+
bookingId: booking.id,
|
|
34
|
+
productId: params.productId,
|
|
35
|
+
variantId: variant?._id ?? null,
|
|
36
|
+
resourceId: params.resourceId,
|
|
37
|
+
name: product.name,
|
|
38
|
+
resourceName: booking.resourceName,
|
|
39
|
+
price,
|
|
40
|
+
quantity: 1,
|
|
41
|
+
total: price,
|
|
42
|
+
taxTableId: product.taxTable,
|
|
43
|
+
startAt: booking.startAt,
|
|
44
|
+
endAt: booking.endAt,
|
|
45
|
+
bufferEndAt: booking.bufferEndAt,
|
|
46
|
+
expiresAt: booking.expiresAt,
|
|
47
|
+
};
|
|
48
|
+
if (!MOCK_CART.reservations)
|
|
49
|
+
MOCK_CART.reservations = [];
|
|
50
|
+
MOCK_CART.reservations.push(reservation);
|
|
51
|
+
MOCK_CART.subtotal += price;
|
|
52
|
+
MOCK_CART.total += price;
|
|
53
|
+
MOCK_CART.amountToBeCharged = MOCK_CART.total;
|
|
54
|
+
MOCK_CART.remainingBalance = MOCK_CART.total;
|
|
55
|
+
// The cart topic, not the bookings one: a screen that only watches bookings still has to
|
|
56
|
+
// repaint its cart, and every other cart mutation announces itself the same way.
|
|
57
|
+
// `cart-created`, as the host publishes: the cart topic carries the whole cart, and a screen
|
|
58
|
+
// written against the host's event was not listening for a name only the mock used.
|
|
59
|
+
mockPublishEvent('cart', 'cart-created', { cart: MOCK_CART });
|
|
60
|
+
return {
|
|
61
|
+
success: true,
|
|
62
|
+
booking,
|
|
63
|
+
reservationInternalId: reservation.internalId,
|
|
64
|
+
timestamp: new Date().toISOString(),
|
|
65
|
+
};
|
|
66
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { CFBooking } from '../../CommonTypes';
|
|
2
|
+
export interface AddBookingToCartParams {
|
|
3
|
+
/** The bookable product (`productType: 'booking'`). */
|
|
4
|
+
productId: string;
|
|
5
|
+
/** Which resource serves it: a member of staff, a room, a scooter. */
|
|
6
|
+
resourceId: string;
|
|
7
|
+
/** Must be the START of a slot getBookingAvailability offered, ISO 8601. */
|
|
8
|
+
startAt: string;
|
|
9
|
+
/** End of the window — the slot's own `endAt`, or the last slot's for a stay. */
|
|
10
|
+
endAt: string;
|
|
11
|
+
/** The variant being sold. Defaults to the product's only variant. */
|
|
12
|
+
variantId?: string;
|
|
13
|
+
/** Whom the booking is for. Omit for a walk-in. */
|
|
14
|
+
customerId?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface AddBookingToCartResponse {
|
|
17
|
+
/** False when the command was refused — a taken window, a rule, a booking that is not yours. */
|
|
18
|
+
success: boolean;
|
|
19
|
+
/** Why it was refused, in words a cashier can act on. Absent on success. */
|
|
20
|
+
reason?: string;
|
|
21
|
+
/** The claim the cart now stands on: `id` cancels or releases it, `expiresAt` is your clock. */
|
|
22
|
+
booking?: CFBooking;
|
|
23
|
+
/** Identity of the reservation inside the cart and, later, the order. */
|
|
24
|
+
reservationInternalId?: string;
|
|
25
|
+
timestamp: string;
|
|
26
|
+
}
|
|
27
|
+
export type AddBookingToCart = (params: AddBookingToCartParams) => Promise<AddBookingToCartResponse>;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { MOCK_BOOKINGS, MOCK_CART, mockPublishEvent } from '../../demo/database';
|
|
2
|
+
import { ReservationStatus } from '@final-commerce/common';
|
|
3
|
+
export const mockCancelBooking = async (params) => {
|
|
4
|
+
console.log('[Mock] cancelBooking called', params);
|
|
5
|
+
const booking = MOCK_BOOKINGS.find(({ id }) => id === params.bookingId);
|
|
6
|
+
if (!booking) {
|
|
7
|
+
return { success: false, reason: `No booking ${params.bookingId}`, timestamp: new Date().toISOString() };
|
|
8
|
+
}
|
|
9
|
+
// A paid booking is not cancellable: the window would be freed while the money stays taken.
|
|
10
|
+
if (booking.status === ReservationStatus.CONFIRMED && booking.orderId) {
|
|
11
|
+
return {
|
|
12
|
+
success: false,
|
|
13
|
+
reason: `That booking is paid for on order ${booking.orderId} — refund the service instead`,
|
|
14
|
+
timestamp: new Date().toISOString(),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
booking.status = ReservationStatus.CANCELLED;
|
|
18
|
+
// A window given away may be the one an open cart is selling, so the cart follows — the host
|
|
19
|
+
// does this, and a mock that left the line behind sold time that had just been released.
|
|
20
|
+
const index = (MOCK_CART.reservations ?? []).findIndex((entry) => entry.bookingId === params.bookingId);
|
|
21
|
+
if (index >= 0) {
|
|
22
|
+
const [removed] = MOCK_CART.reservations.splice(index, 1);
|
|
23
|
+
const line = removed.total || removed.price * (removed.quantity ?? 1);
|
|
24
|
+
MOCK_CART.subtotal -= line;
|
|
25
|
+
MOCK_CART.total -= line;
|
|
26
|
+
MOCK_CART.amountToBeCharged = MOCK_CART.total;
|
|
27
|
+
MOCK_CART.remainingBalance = MOCK_CART.total;
|
|
28
|
+
mockPublishEvent('cart', 'cart-created', { cart: MOCK_CART });
|
|
29
|
+
}
|
|
30
|
+
return { success: true, booking, timestamp: new Date().toISOString() };
|
|
31
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { CFBooking } from '../../CommonTypes';
|
|
2
|
+
export interface CancelBookingParams {
|
|
3
|
+
/** `id` of the booking returned by holdBooking or getBookings. */
|
|
4
|
+
bookingId: string;
|
|
5
|
+
}
|
|
6
|
+
export interface CancelBookingResponse {
|
|
7
|
+
/** False when the command was refused — a taken window, a rule, a booking that is not yours. */
|
|
8
|
+
success: boolean;
|
|
9
|
+
/** Why it was refused, in words a cashier can act on. Absent on success. */
|
|
10
|
+
reason?: string;
|
|
11
|
+
booking?: CFBooking;
|
|
12
|
+
timestamp: string;
|
|
13
|
+
}
|
|
14
|
+
export type CancelBooking = (params: CancelBookingParams) => Promise<CancelBookingResponse>;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Get booking availability action
|
|
3
|
+
* Calls the getBookingAvailability action on the parent window
|
|
4
|
+
*/
|
|
5
|
+
import { commandFrameClient } from '../../client';
|
|
6
|
+
export const getBookingAvailability = async (params) => {
|
|
7
|
+
return await commandFrameClient.call('getBookingAvailability', params);
|
|
8
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { mockBookingAvailability } from '../../demo/database';
|
|
2
|
+
export const mockGetBookingAvailability = async (params) => {
|
|
3
|
+
console.log('[Mock] getBookingAvailability called', params);
|
|
4
|
+
// `fromDay` + `days` is the shape a calendar actually asks in; the mock resolves it on the
|
|
5
|
+
// machine's clock, which is the only one it has.
|
|
6
|
+
const start = params.fromDay ? new Date(`${params.fromDay}T00:00:00`) : params.from ? new Date(params.from) : new Date();
|
|
7
|
+
const end = params.to
|
|
8
|
+
? new Date(params.to)
|
|
9
|
+
: new Date(start.getFullYear(), start.getMonth(), start.getDate() + Math.max(1, params.days ?? 1));
|
|
10
|
+
const availability = mockBookingAvailability(params.productId, start, end, params.resourceId);
|
|
11
|
+
return { success: true, availability, timestamp: new Date().toISOString() };
|
|
12
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { CFBookingAvailability } from '../../CommonTypes';
|
|
2
|
+
export interface GetBookingAvailabilityParams {
|
|
3
|
+
/** The bookable product (`productType: 'booking'`) whose calendar you are drawing. */
|
|
4
|
+
productId: string;
|
|
5
|
+
/**
|
|
6
|
+
* Range start, ISO 8601. Optional when `fromDay` is given.
|
|
7
|
+
*
|
|
8
|
+
* Prefer `fromDay`: a calendar asks about DAYS, and turning "this day at this shop" into two
|
|
9
|
+
* instants needs the shop's zone — which the host has and the caller has to go and find. A
|
|
10
|
+
* caller that widened the range by a day at each end and filtered the answer afterwards was
|
|
11
|
+
* working around the absence of `fromDay`.
|
|
12
|
+
*/
|
|
13
|
+
from?: string;
|
|
14
|
+
/** Range end, ISO 8601. Optional when `fromDay` is given. */
|
|
15
|
+
to?: string;
|
|
16
|
+
/**
|
|
17
|
+
* First shop day to cover, `YYYY-MM-DD`. Resolved to local midnight in the shop's own zone,
|
|
18
|
+
* so a day that is 23 or 25 hours long because the clocks moved is still exactly one day.
|
|
19
|
+
* Omit both this and `from`/`to` to get today at the shop.
|
|
20
|
+
*/
|
|
21
|
+
fromDay?: string;
|
|
22
|
+
/** How many shop days from `fromDay`. Default 1. */
|
|
23
|
+
days?: number;
|
|
24
|
+
/** Narrow the answer to one resource — "only Marco", "only room 4". */
|
|
25
|
+
resourceId?: string;
|
|
26
|
+
/** Narrow to the resources standing at one outlet. */
|
|
27
|
+
outletId?: string;
|
|
28
|
+
}
|
|
29
|
+
export interface GetBookingAvailabilityResponse {
|
|
30
|
+
/** False when the command was refused — a taken window, a rule, a booking that is not yours. */
|
|
31
|
+
success: boolean;
|
|
32
|
+
/** Why it was refused, in words a cashier can act on. Absent on success. */
|
|
33
|
+
reason?: string;
|
|
34
|
+
availability?: CFBookingAvailability;
|
|
35
|
+
timestamp: string;
|
|
36
|
+
}
|
|
37
|
+
export type GetBookingAvailability = (params: GetBookingAvailabilityParams) => Promise<GetBookingAvailabilityResponse>;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Get booking resources action
|
|
3
|
+
* Calls the getBookingResources action on the parent window
|
|
4
|
+
*/
|
|
5
|
+
import { commandFrameClient } from '../../client';
|
|
6
|
+
export const getBookingResources = async (params) => {
|
|
7
|
+
return await commandFrameClient.call('getBookingResources', params);
|
|
8
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { MOCK_BOOKING_RESOURCES } from '../../demo/database';
|
|
2
|
+
export const mockGetBookingResources = async (params) => {
|
|
3
|
+
console.log('[Mock] getBookingResources called', params);
|
|
4
|
+
// A resource knows nothing about outlets — where it works lives in its own records, which the
|
|
5
|
+
// host reads. The mock has no such records, so `outletId` narrows nothing here.
|
|
6
|
+
return { success: true, resources: MOCK_BOOKING_RESOURCES, timestamp: new Date().toISOString() };
|
|
7
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { CFBookingResource } from '../../CommonTypes';
|
|
2
|
+
export interface GetBookingResourcesParams {
|
|
3
|
+
/** Only the resources that serve this bookable product. Omit for the whole company. */
|
|
4
|
+
productId?: string;
|
|
5
|
+
/** Only resources standing at this outlet (a resource with no outlet serves all of them). */
|
|
6
|
+
outletId?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface GetBookingResourcesResponse {
|
|
9
|
+
/** False when the command was refused — a taken window, a rule, a booking that is not yours. */
|
|
10
|
+
success: boolean;
|
|
11
|
+
/** Why it was refused, in words a cashier can act on. Absent on success. */
|
|
12
|
+
reason?: string;
|
|
13
|
+
resources?: CFBookingResource[];
|
|
14
|
+
timestamp: string;
|
|
15
|
+
}
|
|
16
|
+
export type GetBookingResources = (params?: GetBookingResourcesParams) => Promise<GetBookingResourcesResponse>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { MOCK_BOOKINGS, mockLiveBookings } from '../../demo/database';
|
|
2
|
+
export const mockGetBookings = async (params) => {
|
|
3
|
+
console.log('[Mock] getBookings called', params);
|
|
4
|
+
let bookings = params?.includeExpired ? MOCK_BOOKINGS : mockLiveBookings();
|
|
5
|
+
if (params?.resourceId)
|
|
6
|
+
bookings = bookings.filter((entry) => entry.resourceId === params.resourceId);
|
|
7
|
+
if (params?.productId)
|
|
8
|
+
bookings = bookings.filter((entry) => entry.productId === params.productId);
|
|
9
|
+
if (params?.customerId)
|
|
10
|
+
bookings = bookings.filter((entry) => entry.customerId === params.customerId);
|
|
11
|
+
if (params?.from) {
|
|
12
|
+
const from = new Date(params.from);
|
|
13
|
+
bookings = bookings.filter((entry) => new Date(entry.bufferEndAt) > from);
|
|
14
|
+
}
|
|
15
|
+
if (params?.to) {
|
|
16
|
+
const to = new Date(params.to);
|
|
17
|
+
bookings = bookings.filter((entry) => new Date(entry.startAt) < to);
|
|
18
|
+
}
|
|
19
|
+
return { success: true, bookings, timestamp: new Date().toISOString() };
|
|
20
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { CFBooking } from '../../CommonTypes';
|
|
2
|
+
export interface GetBookingsParams {
|
|
3
|
+
/** Everything taking this resource's time — the one query a day view needs. */
|
|
4
|
+
resourceId?: string;
|
|
5
|
+
productId?: string;
|
|
6
|
+
/** This customer's appointments. */
|
|
7
|
+
customerId?: string;
|
|
8
|
+
/** Bookings whose window overlaps this range, ISO 8601. */
|
|
9
|
+
from?: string;
|
|
10
|
+
to?: string;
|
|
11
|
+
/** Defaults to live bookings only (confirmed, plus holds that have not expired). */
|
|
12
|
+
includeExpired?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export interface GetBookingsResponse {
|
|
15
|
+
/** False when the command was refused — a taken window, a rule, a booking that is not yours. */
|
|
16
|
+
success: boolean;
|
|
17
|
+
/** Why it was refused, in words a cashier can act on. Absent on success. */
|
|
18
|
+
reason?: string;
|
|
19
|
+
bookings?: CFBooking[];
|
|
20
|
+
timestamp: string;
|
|
21
|
+
}
|
|
22
|
+
export type GetBookings = (params?: GetBookingsParams) => Promise<GetBookingsResponse>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -11,6 +11,10 @@ export const mockGetContext = () => {
|
|
|
11
11
|
stationName: MOCK_STATION.name,
|
|
12
12
|
outletId: MOCK_OUTLET.id,
|
|
13
13
|
outletName: MOCK_OUTLET.name || null,
|
|
14
|
+
// Deliberately NOT the developer's own zone: an app that quietly formats with
|
|
15
|
+
// the machine's clock looks correct in preview and is wrong on every till that
|
|
16
|
+
// sits in a different city. Here it cannot look correct by accident.
|
|
17
|
+
timeZone: "America/Vancouver",
|
|
14
18
|
buildId: "mock_build_id",
|
|
15
19
|
buildName: "Mock Build",
|
|
16
20
|
buildVersion: "1.0.0-mock",
|
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
* Get product modifier selections action (read-only)
|
|
3
3
|
* Calls the getProductModifierSelections action on the parent window
|
|
4
4
|
*/
|
|
5
|
-
import type { GetProductModifierSelections } from
|
|
5
|
+
import type { GetProductModifierSelections } from './types';
|
|
6
6
|
export declare const getProductModifierSelections: GetProductModifierSelections;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Get product modifier selections action (read-only)
|
|
3
3
|
* Calls the getProductModifierSelections action on the parent window
|
|
4
4
|
*/
|
|
5
|
-
import { commandFrameClient } from
|
|
5
|
+
import { commandFrameClient } from '../../client';
|
|
6
6
|
export const getProductModifierSelections = async (params) => {
|
|
7
|
-
return await commandFrameClient.call(
|
|
7
|
+
return await commandFrameClient.call('getProductModifierSelections', params);
|
|
8
8
|
};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { GetProductModifierSelections } from
|
|
1
|
+
import { GetProductModifierSelections } from './types';
|
|
2
2
|
export declare const mockGetProductModifierSelections: GetProductModifierSelections;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { MOCK_CART, buildModifierRows } from
|
|
1
|
+
import { MOCK_CART, buildModifierRows } from '../../demo/database';
|
|
2
2
|
export const mockGetProductModifierSelections = async (params) => {
|
|
3
|
-
console.log(
|
|
3
|
+
console.log('[Mock] getProductModifierSelections called', params);
|
|
4
4
|
// Read the line's stored selections from the mock cart (last line when no internalId).
|
|
5
5
|
const line = params?.internalId
|
|
6
6
|
? MOCK_CART.products.find((p) => p.internalId === params.internalId)
|
|
@@ -8,12 +8,12 @@ export const mockGetProductModifierSelections = async (params) => {
|
|
|
8
8
|
if (!line) {
|
|
9
9
|
return {
|
|
10
10
|
success: false,
|
|
11
|
-
reason:
|
|
11
|
+
reason: 'No matching cart line',
|
|
12
12
|
internalId: params?.internalId,
|
|
13
13
|
selections: [],
|
|
14
14
|
rows: [],
|
|
15
15
|
modifiersTotal: 0,
|
|
16
|
-
timestamp: new Date().toISOString()
|
|
16
|
+
timestamp: new Date().toISOString(),
|
|
17
17
|
};
|
|
18
18
|
}
|
|
19
19
|
// The priced rows already sit on the line; extending them by the line quantity is
|
|
@@ -25,6 +25,6 @@ export const mockGetProductModifierSelections = async (params) => {
|
|
|
25
25
|
selections: line.modifierSelections ?? [],
|
|
26
26
|
rows,
|
|
27
27
|
modifiersTotal,
|
|
28
|
-
timestamp: new Date().toISOString()
|
|
28
|
+
timestamp: new Date().toISOString(),
|
|
29
29
|
};
|
|
30
30
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CFModifierSelection, CFModifierChoiceSelection, CFProdModifierBreakdown } from
|
|
1
|
+
import type { CFModifierSelection, CFModifierChoiceSelection, CFProdModifierBreakdown } from '../../CommonTypes';
|
|
2
2
|
/** Alias of common's — `quantity` is units PER LINE-ITEM UNIT. */
|
|
3
3
|
export type ModifierChoiceSelection = CFModifierChoiceSelection;
|
|
4
4
|
/** Alias of common's — the cashier's answer to one modifier. */
|
|
@@ -5,6 +5,12 @@ export const mockGetProducts = async (params) => {
|
|
|
5
5
|
// Simple filter simulation
|
|
6
6
|
let products = MOCK_PRODUCTS;
|
|
7
7
|
const query = params?.query || {};
|
|
8
|
+
// Same rule as the real host: a bookable service is not catalogue stock and is left out unless
|
|
9
|
+
// the query names `productType`. Mirrored here on purpose — a mock that hands back services the
|
|
10
|
+
// device withholds is how a flow gets built around a grid that cannot exist.
|
|
11
|
+
if (!JSON.stringify(query).includes('productType')) {
|
|
12
|
+
products = products.filter((p) => p.productType !== 'booking');
|
|
13
|
+
}
|
|
8
14
|
if (query.searchValue) {
|
|
9
15
|
const search = String(query.searchValue).toLowerCase();
|
|
10
16
|
products = products.filter((p) => p.name.toLowerCase().includes(search) || p.sku?.toLowerCase().includes(search));
|
|
@@ -1,6 +1,21 @@
|
|
|
1
|
-
import { CFProduct } from
|
|
1
|
+
import { CFProduct } from '../../CommonTypes';
|
|
2
2
|
export interface GetProductsParams {
|
|
3
|
-
/**
|
|
3
|
+
/**
|
|
4
|
+
* MongoDB-like query object.
|
|
5
|
+
*
|
|
6
|
+
* BOOKABLE SERVICES ARE EXCLUDED unless this query mentions `productType`. A service is not
|
|
7
|
+
* catalogue stock: it is sold by claiming a time with `addBookingToCart`, and
|
|
8
|
+
* `addProductToCart` refuses it. Returned by default it appeared in ordinary product grids,
|
|
9
|
+
* where the obvious gesture took the money and reserved nothing.
|
|
10
|
+
*
|
|
11
|
+
* Ask for them explicitly when the user actually wants services — a "Book" tab, a services
|
|
12
|
+
* screen, a search across everything:
|
|
13
|
+
*
|
|
14
|
+
* getProducts({ query: { productType: 'booking' } }) // services only
|
|
15
|
+
* getProducts({ query: { productType: { $in: ['simple', 'variable', 'booking'] } } }) // both
|
|
16
|
+
*
|
|
17
|
+
* Any query naming `productType` is used exactly as written.
|
|
18
|
+
*/
|
|
4
19
|
query?: {
|
|
5
20
|
name?: string | {
|
|
6
21
|
$regex?: string;
|
|
@@ -11,7 +26,14 @@ export interface GetProductsParams {
|
|
|
11
26
|
$options?: string;
|
|
12
27
|
};
|
|
13
28
|
status?: string;
|
|
14
|
-
|
|
29
|
+
/**
|
|
30
|
+
* `simple` | `variable` | `booking`. Naming it at all switches OFF the default exclusion
|
|
31
|
+
* of bookable services — including `{ $ne: 'booking' }`, which then means what it says.
|
|
32
|
+
*/
|
|
33
|
+
productType?: string | {
|
|
34
|
+
$in?: string[];
|
|
35
|
+
$ne?: string;
|
|
36
|
+
};
|
|
15
37
|
categories?: string | {
|
|
16
38
|
$in?: string[];
|
|
17
39
|
};
|
|
@@ -18,11 +18,14 @@ export interface GetRefundPlanParams {
|
|
|
18
18
|
* nothing staged, no `allocation` comes back.
|
|
19
19
|
*/
|
|
20
20
|
items?: {
|
|
21
|
-
/**
|
|
21
|
+
/**
|
|
22
|
+
* `internalId` / `variantId` for a product, `customSaleId`, cart-fee id, tip
|
|
23
|
+
* `transactionId`, or a booking's own `internalId` on `order.reservations[]`.
|
|
24
|
+
*/
|
|
22
25
|
itemKey: string;
|
|
23
26
|
quantity: number;
|
|
24
27
|
/** Optional hint; inferred from the order when omitted. */
|
|
25
|
-
type?: 'product' | 'customSale' | 'fee' | 'tip';
|
|
28
|
+
type?: 'product' | 'customSale' | 'fee' | 'tip' | 'reservation';
|
|
26
29
|
}[];
|
|
27
30
|
}
|
|
28
31
|
export interface RefundPlanSource {
|
|
@@ -18,6 +18,14 @@ export interface GetRemainingRefundableQuantitiesResponse {
|
|
|
18
18
|
* 0/1 semantics: `1` = still refundable, `0` = already refunded.
|
|
19
19
|
*/
|
|
20
20
|
tips: Record<string, number>;
|
|
21
|
+
/**
|
|
22
|
+
* Remaining refundable bookings, keyed by `order.reservations[].internalId` —
|
|
23
|
+
* the same key `processPartialRefund` takes for `type: 'reservation'` items.
|
|
24
|
+
* A service is sold as a reservation rather than a line item, so without this
|
|
25
|
+
* map a refund screen can show everything on the order EXCEPT the appointment.
|
|
26
|
+
* 0/1 semantics: `1` = still refundable, `0` = already refunded.
|
|
27
|
+
*/
|
|
28
|
+
reservations: Record<string, number>;
|
|
21
29
|
timestamp: string;
|
|
22
30
|
}
|
|
23
31
|
export type GetRemainingRefundableQuantities = (params?: GetRemainingRefundableQuantitiesParams) => Promise<GetRemainingRefundableQuantitiesResponse>;
|