@final-commerce/command-frame 0.8.0-staging.1 → 0.8.0
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/actions/add-booking-to-cart/mock.js +32 -8
- package/dist/actions/add-booking-to-cart/types.d.ts +6 -2
- package/dist/actions/cancel-booking/mock.js +25 -4
- package/dist/actions/cancel-booking/types.d.ts +5 -1
- package/dist/actions/get-booking-availability/mock.js +8 -2
- package/dist/actions/get-booking-availability/types.d.ts +24 -5
- package/dist/actions/get-booking-resources/mock.js +1 -1
- package/dist/actions/get-booking-resources/types.d.ts +5 -1
- package/dist/actions/get-bookings/mock.js +1 -1
- package/dist/actions/get-bookings/types.d.ts +5 -1
- package/dist/actions/get-refund-plan/mock.d.ts +4 -0
- package/dist/actions/get-refund-plan/mock.js +8 -0
- package/dist/actions/get-refund-plan/mockRows.d.ts +6 -0
- package/dist/actions/get-refund-plan/mockRows.js +114 -0
- package/dist/actions/get-refund-plan/types.d.ts +89 -0
- package/dist/actions/hold-booking/mock.js +8 -4
- package/dist/actions/hold-booking/types.d.ts +6 -2
- package/dist/actions/remove-booking-from-cart/mock.js +17 -3
- package/dist/actions/remove-booking-from-cart/types.d.ts +5 -1
- package/dist/demo/database.d.ts +6 -0
- package/dist/demo/database.js +66 -8
- package/dist/index.d.ts +1 -1
- package/package.json +5 -2
|
@@ -5,24 +5,41 @@ export const mockAddBookingToCart = async (params) => {
|
|
|
5
5
|
// One call, two effects — the window is claimed AND the service is in the cart. The claim goes
|
|
6
6
|
// first and its refusal propagates: a cart line for a window somebody else took is worse than
|
|
7
7
|
// no line at all.
|
|
8
|
-
const
|
|
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.
|
|
9
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.
|
|
10
20
|
const variant = params.variantId
|
|
11
|
-
? product
|
|
12
|
-
: product
|
|
13
|
-
|
|
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;
|
|
14
31
|
const reservation = {
|
|
15
32
|
internalId: `res_${booking.id}`,
|
|
16
33
|
bookingId: booking.id,
|
|
17
34
|
productId: params.productId,
|
|
18
35
|
variantId: variant?._id ?? null,
|
|
19
36
|
resourceId: params.resourceId,
|
|
20
|
-
name: product
|
|
37
|
+
name: product.name,
|
|
21
38
|
resourceName: booking.resourceName,
|
|
22
39
|
price,
|
|
23
40
|
quantity: 1,
|
|
24
41
|
total: price,
|
|
25
|
-
taxTableId: product
|
|
42
|
+
taxTableId: product.taxTable,
|
|
26
43
|
startAt: booking.startAt,
|
|
27
44
|
endAt: booking.endAt,
|
|
28
45
|
bufferEndAt: booking.bufferEndAt,
|
|
@@ -37,6 +54,13 @@ export const mockAddBookingToCart = async (params) => {
|
|
|
37
54
|
MOCK_CART.remainingBalance = MOCK_CART.total;
|
|
38
55
|
// The cart topic, not the bookings one: a screen that only watches bookings still has to
|
|
39
56
|
// repaint its cart, and every other cart mutation announces itself the same way.
|
|
40
|
-
|
|
41
|
-
|
|
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
|
+
};
|
|
42
66
|
};
|
|
@@ -14,10 +14,14 @@ export interface AddBookingToCartParams {
|
|
|
14
14
|
customerId?: string;
|
|
15
15
|
}
|
|
16
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;
|
|
17
21
|
/** The claim the cart now stands on: `id` cancels or releases it, `expiresAt` is your clock. */
|
|
18
|
-
booking
|
|
22
|
+
booking?: CFBooking;
|
|
19
23
|
/** Identity of the reservation inside the cart and, later, the order. */
|
|
20
|
-
reservationInternalId
|
|
24
|
+
reservationInternalId?: string;
|
|
21
25
|
timestamp: string;
|
|
22
26
|
}
|
|
23
27
|
export type AddBookingToCart = (params: AddBookingToCartParams) => Promise<AddBookingToCartResponse>;
|
|
@@ -1,10 +1,31 @@
|
|
|
1
|
-
import { MOCK_BOOKINGS } from '../../demo/database';
|
|
1
|
+
import { MOCK_BOOKINGS, MOCK_CART, mockPublishEvent } from '../../demo/database';
|
|
2
2
|
import { ReservationStatus } from '@final-commerce/common';
|
|
3
3
|
export const mockCancelBooking = async (params) => {
|
|
4
4
|
console.log('[Mock] cancelBooking called', params);
|
|
5
5
|
const booking = MOCK_BOOKINGS.find(({ id }) => id === params.bookingId);
|
|
6
|
-
if (!booking)
|
|
7
|
-
|
|
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
|
+
}
|
|
8
17
|
booking.status = ReservationStatus.CANCELLED;
|
|
9
|
-
|
|
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() };
|
|
10
31
|
};
|
|
@@ -4,7 +4,11 @@ export interface CancelBookingParams {
|
|
|
4
4
|
bookingId: string;
|
|
5
5
|
}
|
|
6
6
|
export interface CancelBookingResponse {
|
|
7
|
-
booking
|
|
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;
|
|
8
12
|
timestamp: string;
|
|
9
13
|
}
|
|
10
14
|
export type CancelBooking = (params: CancelBookingParams) => Promise<CancelBookingResponse>;
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { mockBookingAvailability } from '../../demo/database';
|
|
2
2
|
export const mockGetBookingAvailability = async (params) => {
|
|
3
3
|
console.log('[Mock] getBookingAvailability called', params);
|
|
4
|
-
|
|
5
|
-
|
|
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() };
|
|
6
12
|
};
|
|
@@ -2,17 +2,36 @@ import { CFBookingAvailability } from '../../CommonTypes';
|
|
|
2
2
|
export interface GetBookingAvailabilityParams {
|
|
3
3
|
/** The bookable product (`productType: 'booking'`) whose calendar you are drawing. */
|
|
4
4
|
productId: string;
|
|
5
|
-
/**
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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;
|
|
9
24
|
/** Narrow the answer to one resource — "only Marco", "only room 4". */
|
|
10
25
|
resourceId?: string;
|
|
11
26
|
/** Narrow to the resources standing at one outlet. */
|
|
12
27
|
outletId?: string;
|
|
13
28
|
}
|
|
14
29
|
export interface GetBookingAvailabilityResponse {
|
|
15
|
-
|
|
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;
|
|
16
35
|
timestamp: string;
|
|
17
36
|
}
|
|
18
37
|
export type GetBookingAvailability = (params: GetBookingAvailabilityParams) => Promise<GetBookingAvailabilityResponse>;
|
|
@@ -3,5 +3,5 @@ export const mockGetBookingResources = async (params) => {
|
|
|
3
3
|
console.log('[Mock] getBookingResources called', params);
|
|
4
4
|
// A resource knows nothing about outlets — where it works lives in its own records, which the
|
|
5
5
|
// host reads. The mock has no such records, so `outletId` narrows nothing here.
|
|
6
|
-
return { resources: MOCK_BOOKING_RESOURCES, timestamp: new Date().toISOString() };
|
|
6
|
+
return { success: true, resources: MOCK_BOOKING_RESOURCES, timestamp: new Date().toISOString() };
|
|
7
7
|
};
|
|
@@ -6,7 +6,11 @@ export interface GetBookingResourcesParams {
|
|
|
6
6
|
outletId?: string;
|
|
7
7
|
}
|
|
8
8
|
export interface GetBookingResourcesResponse {
|
|
9
|
-
|
|
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[];
|
|
10
14
|
timestamp: string;
|
|
11
15
|
}
|
|
12
16
|
export type GetBookingResources = (params?: GetBookingResourcesParams) => Promise<GetBookingResourcesResponse>;
|
|
@@ -16,5 +16,5 @@ export const mockGetBookings = async (params) => {
|
|
|
16
16
|
const to = new Date(params.to);
|
|
17
17
|
bookings = bookings.filter((entry) => new Date(entry.startAt) < to);
|
|
18
18
|
}
|
|
19
|
-
return { bookings, timestamp: new Date().toISOString() };
|
|
19
|
+
return { success: true, bookings, timestamp: new Date().toISOString() };
|
|
20
20
|
};
|
|
@@ -12,7 +12,11 @@ export interface GetBookingsParams {
|
|
|
12
12
|
includeExpired?: boolean;
|
|
13
13
|
}
|
|
14
14
|
export interface GetBookingsResponse {
|
|
15
|
-
|
|
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[];
|
|
16
20
|
timestamp: string;
|
|
17
21
|
}
|
|
18
22
|
export type GetBookings = (params?: GetBookingsParams) => Promise<GetBookingsResponse>;
|
|
@@ -15,5 +15,9 @@ import { GetRefundPlan } from './types';
|
|
|
15
15
|
* and no company cash-rounding setting, so it always describes a FULL refund
|
|
16
16
|
* with `rounding: 0` and no cash `payout`. Against real kaching the allocation
|
|
17
17
|
* tracks the live selection and carries the drawer snap.
|
|
18
|
+
*
|
|
19
|
+
* `rows` / `allocation.breakdown` follow the engine's reading of the order
|
|
20
|
+
* (`line.total` includes the line's tax) but prorate by plain quantity share;
|
|
21
|
+
* the engine's remainder carry across earlier partial refunds is not modelled.
|
|
18
22
|
*/
|
|
19
23
|
export declare const mockGetRefundPlan: GetRefundPlan;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { MOCK_ORDERS } from '../../demo/database';
|
|
2
|
+
import { mockRefundBreakdown, mockRefundRows } from './mockRows';
|
|
2
3
|
/**
|
|
3
4
|
* Demo derivation of the runtime `getRefundPlan`. Builds the per-source rows
|
|
4
5
|
* from the mock order's `paymentMethods`, mirroring the runtime's capacity
|
|
@@ -15,6 +16,10 @@ import { MOCK_ORDERS } from '../../demo/database';
|
|
|
15
16
|
* and no company cash-rounding setting, so it always describes a FULL refund
|
|
16
17
|
* with `rounding: 0` and no cash `payout`. Against real kaching the allocation
|
|
17
18
|
* tracks the live selection and carries the drawer snap.
|
|
19
|
+
*
|
|
20
|
+
* `rows` / `allocation.breakdown` follow the engine's reading of the order
|
|
21
|
+
* (`line.total` includes the line's tax) but prorate by plain quantity share;
|
|
22
|
+
* the engine's remainder carry across earlier partial refunds is not modelled.
|
|
18
23
|
*/
|
|
19
24
|
export const mockGetRefundPlan = async (params) => {
|
|
20
25
|
console.log('[Mock] getRefundPlan called', params);
|
|
@@ -62,16 +67,19 @@ export const mockGetRefundPlan = async (params) => {
|
|
|
62
67
|
requiresGiftCardDestination: s.paymentType === 'redeem',
|
|
63
68
|
}));
|
|
64
69
|
const budget = legs.reduce((sum, l) => sum + l.amount, 0);
|
|
70
|
+
const rows = mockRefundRows(order);
|
|
65
71
|
return {
|
|
66
72
|
success: true,
|
|
67
73
|
orderId: order._id,
|
|
68
74
|
sources,
|
|
75
|
+
rows,
|
|
69
76
|
allocation: {
|
|
70
77
|
budget,
|
|
71
78
|
// No cash rounding in the demo, so the goods value and the budget agree.
|
|
72
79
|
itemTotal: budget,
|
|
73
80
|
rounding: 0,
|
|
74
81
|
legs,
|
|
82
|
+
breakdown: mockRefundBreakdown(rows, params?.items),
|
|
75
83
|
},
|
|
76
84
|
// Demo: no prior refunds, so remaining = captured minus the non-revenue load.
|
|
77
85
|
remainingRefundable: Math.max(0, totalCaptured - nonRefundableLiability),
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { CFActiveOrder } from '../../CommonTypes';
|
|
2
|
+
import type { GetRefundPlanParams, RefundPlanBreakdown, RefundPlanRow } from './types';
|
|
3
|
+
/** Every row of a demo order. The demo has no refund ledger, so everything is refundable. */
|
|
4
|
+
export declare function mockRefundRows(order: CFActiveOrder): RefundPlanRow[];
|
|
5
|
+
/** The selection's breakdown; no `items` means everything, like the demo's full-refund legs. */
|
|
6
|
+
export declare function mockRefundBreakdown(rows: RefundPlanRow[], items?: GetRefundPlanParams['items']): RefundPlanBreakdown;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// Demo derivation of `getRefundPlan`'s `rows` and `allocation.breakdown`, for
|
|
2
|
+
// local/standalone mode only. See the note on `mockGetRefundPlan`.
|
|
3
|
+
const minor = (v) => Math.round(Number(v ?? 0)) || 0;
|
|
4
|
+
/** A line's whole-quantity money, reading `total` as tax-inclusive (the engine's reading). */
|
|
5
|
+
function lineAmounts(line) {
|
|
6
|
+
const tax = minor(line.totalTax);
|
|
7
|
+
const itemDiscount = (line.discount?.itemDiscounts ?? []).reduce((s, d) => s + minor(d.amount), 0);
|
|
8
|
+
const cartDiscount = minor(line.discount?.cartDiscount?.amount);
|
|
9
|
+
const net = Math.max(0, minor(line.total) - tax);
|
|
10
|
+
const taxes = (line.taxes ?? []).map((t) => ({
|
|
11
|
+
name: String(t.name ?? 'Tax'),
|
|
12
|
+
percentage: t.percentage,
|
|
13
|
+
amount: minor(t.amount),
|
|
14
|
+
}));
|
|
15
|
+
return { subtotal: net + itemDiscount + cartDiscount, itemDiscount, cartDiscount, tax, taxes, total: net + tax };
|
|
16
|
+
}
|
|
17
|
+
function scale(a, share) {
|
|
18
|
+
if (share >= 1)
|
|
19
|
+
return a;
|
|
20
|
+
const r = (n) => Math.round(n * share);
|
|
21
|
+
const out = {
|
|
22
|
+
subtotal: r(a.subtotal),
|
|
23
|
+
itemDiscount: r(a.itemDiscount),
|
|
24
|
+
cartDiscount: r(a.cartDiscount),
|
|
25
|
+
tax: r(a.tax),
|
|
26
|
+
taxes: a.taxes.map((t) => ({ ...t, amount: r(t.amount) })),
|
|
27
|
+
};
|
|
28
|
+
return { ...out, total: out.subtotal - out.itemDiscount - out.cartDiscount + out.tax };
|
|
29
|
+
}
|
|
30
|
+
/** Every row of a demo order. The demo has no refund ledger, so everything is refundable. */
|
|
31
|
+
export function mockRefundRows(order) {
|
|
32
|
+
const rows = [];
|
|
33
|
+
for (const li of order.lineItems ?? []) {
|
|
34
|
+
const itemKey = String(li.internalId || li.variantId || '');
|
|
35
|
+
if (!itemKey || !(li.quantity > 0))
|
|
36
|
+
continue;
|
|
37
|
+
rows.push({
|
|
38
|
+
type: 'product',
|
|
39
|
+
itemKey,
|
|
40
|
+
label: String(li.name ?? 'Item'),
|
|
41
|
+
...(li.sku ? { sku: li.sku } : {}),
|
|
42
|
+
...(li.attributes ? { attributes: li.attributes } : {}),
|
|
43
|
+
quantity: li.quantity,
|
|
44
|
+
refundableQuantity: li.quantity,
|
|
45
|
+
amounts: lineAmounts(li),
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
for (const cs of order.customSales ?? []) {
|
|
49
|
+
if (!cs.customSaleId || !(cs.quantity > 0))
|
|
50
|
+
continue;
|
|
51
|
+
rows.push({
|
|
52
|
+
type: 'customSale',
|
|
53
|
+
itemKey: cs.customSaleId,
|
|
54
|
+
label: String(cs.name ?? 'Custom sale'),
|
|
55
|
+
quantity: cs.quantity,
|
|
56
|
+
refundableQuantity: cs.quantity,
|
|
57
|
+
amounts: lineAmounts(cs),
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
for (const fee of order.cartFees ?? []) {
|
|
61
|
+
const amount = minor(fee.amount);
|
|
62
|
+
const tax = minor(fee.tax);
|
|
63
|
+
rows.push({
|
|
64
|
+
type: 'fee',
|
|
65
|
+
itemKey: String(fee.id),
|
|
66
|
+
label: String(fee.label || 'Cart fee'),
|
|
67
|
+
quantity: 1,
|
|
68
|
+
refundableQuantity: 1,
|
|
69
|
+
amounts: { subtotal: amount, itemDiscount: 0, cartDiscount: 0, tax, taxes: [], total: amount + tax },
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
for (const pm of order.paymentMethods ?? []) {
|
|
73
|
+
const tip = minor(pm.tip?.amount);
|
|
74
|
+
if (tip <= 0)
|
|
75
|
+
continue;
|
|
76
|
+
rows.push({
|
|
77
|
+
type: 'tip',
|
|
78
|
+
itemKey: pm.transactionId,
|
|
79
|
+
label: 'Tip',
|
|
80
|
+
paymentType: pm.paymentType,
|
|
81
|
+
quantity: 1,
|
|
82
|
+
refundableQuantity: 1,
|
|
83
|
+
amounts: { subtotal: tip, itemDiscount: 0, cartDiscount: 0, tax: 0, taxes: [], total: tip },
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
return rows;
|
|
87
|
+
}
|
|
88
|
+
/** The selection's breakdown; no `items` means everything, like the demo's full-refund legs. */
|
|
89
|
+
export function mockRefundBreakdown(rows, items) {
|
|
90
|
+
const picked = new Map((items ?? []).map((i) => [i.itemKey, i.quantity]));
|
|
91
|
+
const selected = rows
|
|
92
|
+
.map((row) => {
|
|
93
|
+
const quantity = items?.length
|
|
94
|
+
? Math.min(picked.get(row.itemKey) ?? 0, row.refundableQuantity)
|
|
95
|
+
: row.refundableQuantity;
|
|
96
|
+
return { type: row.type, itemKey: row.itemKey, quantity, amounts: scale(row.amounts, quantity / row.quantity) };
|
|
97
|
+
})
|
|
98
|
+
.filter((r) => r.quantity > 0);
|
|
99
|
+
const totals = { items: 0, discounts: 0, fees: 0, tax: 0, tip: 0, total: 0 };
|
|
100
|
+
for (const r of selected) {
|
|
101
|
+
const a = r.amounts;
|
|
102
|
+
if (r.type === 'fee')
|
|
103
|
+
totals.fees += a.subtotal;
|
|
104
|
+
else if (r.type === 'tip')
|
|
105
|
+
totals.tip += a.subtotal;
|
|
106
|
+
else {
|
|
107
|
+
totals.items += a.subtotal;
|
|
108
|
+
totals.discounts += a.itemDiscount + a.cartDiscount;
|
|
109
|
+
}
|
|
110
|
+
totals.tax += a.tax;
|
|
111
|
+
totals.total += a.total;
|
|
112
|
+
}
|
|
113
|
+
return { rows: selected, totals };
|
|
114
|
+
}
|
|
@@ -79,6 +79,81 @@ export interface RefundPlanLeg {
|
|
|
79
79
|
rounding: number;
|
|
80
80
|
};
|
|
81
81
|
}
|
|
82
|
+
export type RefundPlanRowType = 'product' | 'customSale' | 'fee' | 'tip';
|
|
83
|
+
/** One tax rate's share of a row's refund (minor units). */
|
|
84
|
+
export interface RefundPlanTaxLine {
|
|
85
|
+
name: string;
|
|
86
|
+
/** Decimal rate as stored on the order (e.g. `0.15`), when the order recorded one. */
|
|
87
|
+
percentage?: number;
|
|
88
|
+
amount: number;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* The money a row refunds, split the way a receipt shows it. Every field is
|
|
92
|
+
* minor units and already rounded; `subtotal − itemDiscount − cartDiscount +
|
|
93
|
+
* tax === total` always holds. DISPLAY IT — never re-add or prorate it.
|
|
94
|
+
*/
|
|
95
|
+
export interface RefundPlanAmounts {
|
|
96
|
+
/** Before discounts, tax excluded. For a tip row, the tip itself. */
|
|
97
|
+
subtotal: number;
|
|
98
|
+
/** Per-item discounts on the refunded quantity (positive). */
|
|
99
|
+
itemDiscount: number;
|
|
100
|
+
/** Cart discount share on the refunded quantity (positive). */
|
|
101
|
+
cartDiscount: number;
|
|
102
|
+
/** Tax on the refunded quantity. Zero for a tip. */
|
|
103
|
+
tax: number;
|
|
104
|
+
/** `tax` per rate — the price breakdown's tax lines. */
|
|
105
|
+
taxes: RefundPlanTaxLine[];
|
|
106
|
+
/** What the row refunds. */
|
|
107
|
+
total: number;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* One refundable row of the order — a line item, custom sale, cart fee or tip —
|
|
111
|
+
* served ready to render. Rows with nothing left to refund are not listed.
|
|
112
|
+
*/
|
|
113
|
+
export interface RefundPlanRow {
|
|
114
|
+
type: RefundPlanRowType;
|
|
115
|
+
/** The key `items[].itemKey` takes, for this call and for `processPartialRefund`. */
|
|
116
|
+
itemKey: string;
|
|
117
|
+
/** Line or fee name; `Tip` for a tip. */
|
|
118
|
+
label: string;
|
|
119
|
+
/** Product lines only, when the order recorded them. */
|
|
120
|
+
sku?: string;
|
|
121
|
+
attributes?: string;
|
|
122
|
+
/** Tip rows only: the tender that took the tip (`card`, `cash`, …). */
|
|
123
|
+
paymentType?: string;
|
|
124
|
+
/** Quantity originally sold. Fees and tips are `1` — all or nothing. */
|
|
125
|
+
quantity: number;
|
|
126
|
+
/** Quantity still refundable — the stepper's max. */
|
|
127
|
+
refundableQuantity: number;
|
|
128
|
+
/** The money for refunding ALL of `refundableQuantity`. */
|
|
129
|
+
amounts: RefundPlanAmounts;
|
|
130
|
+
}
|
|
131
|
+
/** One selected row and what refunding the selected quantity of it moves. */
|
|
132
|
+
export interface RefundPlanSelectedRow {
|
|
133
|
+
type: RefundPlanRowType;
|
|
134
|
+
itemKey: string;
|
|
135
|
+
quantity: number;
|
|
136
|
+
amounts: RefundPlanAmounts;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* The selection's goods value broken down for display. Minor units;
|
|
140
|
+
* `items − discounts + fees + tax + tip === total === allocation.itemTotal`.
|
|
141
|
+
*/
|
|
142
|
+
export interface RefundPlanTotals {
|
|
143
|
+
/** Σ selected line subtotals (before discounts, tax excluded). */
|
|
144
|
+
items: number;
|
|
145
|
+
/** Σ item + cart discounts on the selected lines (positive). */
|
|
146
|
+
discounts: number;
|
|
147
|
+
/** Σ selected cart fees, tax excluded. */
|
|
148
|
+
fees: number;
|
|
149
|
+
tax: number;
|
|
150
|
+
tip: number;
|
|
151
|
+
total: number;
|
|
152
|
+
}
|
|
153
|
+
export interface RefundPlanBreakdown {
|
|
154
|
+
rows: RefundPlanSelectedRow[];
|
|
155
|
+
totals: RefundPlanTotals;
|
|
156
|
+
}
|
|
82
157
|
/**
|
|
83
158
|
* The engine's own allocation of the CURRENT refund selection across the
|
|
84
159
|
* order's captures — what a flow renders and submits instead of computing a
|
|
@@ -106,11 +181,25 @@ export interface RefundPlanAllocation {
|
|
|
106
181
|
rounding: number;
|
|
107
182
|
/** One leg per source that receives money. Submit as `legs`, unchanged. */
|
|
108
183
|
legs: RefundPlanLeg[];
|
|
184
|
+
/**
|
|
185
|
+
* What the selection refunds, per row and in total — the numbers a refund
|
|
186
|
+
* dialog's rows and footer render. Optional so an older runtime still
|
|
187
|
+
* type-checks; kaching 1.12.1+ always sends it.
|
|
188
|
+
*/
|
|
189
|
+
breakdown?: RefundPlanBreakdown;
|
|
109
190
|
}
|
|
110
191
|
export interface GetRefundPlanResponse {
|
|
111
192
|
success: boolean;
|
|
112
193
|
orderId: string;
|
|
113
194
|
sources: RefundPlanSource[];
|
|
195
|
+
/**
|
|
196
|
+
* Every row still refundable on the order, with the money for refunding all
|
|
197
|
+
* of it — what a refund dialog lists before anything is selected. Replaces
|
|
198
|
+
* reading line totals off the order and deciding whether they include tax.
|
|
199
|
+
* Optional so an older runtime still type-checks; kaching 1.12.1+ always
|
|
200
|
+
* sends it.
|
|
201
|
+
*/
|
|
202
|
+
rows?: RefundPlanRow[];
|
|
114
203
|
/**
|
|
115
204
|
* Ready-to-submit allocation of the current selection. Present only when a
|
|
116
205
|
* refund selection exists on the active order. See {@link RefundPlanAllocation}.
|
|
@@ -2,6 +2,7 @@ import { MOCK_BOOKINGS, MOCK_BOOKING_RESOURCES, mockLiveBookings } from '../../d
|
|
|
2
2
|
import { ReservationStatus } from '@final-commerce/common';
|
|
3
3
|
const BUFFER_MINUTES = 5;
|
|
4
4
|
const HOLD_MINUTES = 10;
|
|
5
|
+
let held = 0;
|
|
5
6
|
export const mockHoldBooking = async (params) => {
|
|
6
7
|
console.log('[Mock] holdBooking called', params);
|
|
7
8
|
const startAt = new Date(params.startAt);
|
|
@@ -12,10 +13,13 @@ export const mockHoldBooking = async (params) => {
|
|
|
12
13
|
const clash = mockLiveBookings().some((entry) => entry.resourceId === params.resourceId &&
|
|
13
14
|
new Date(entry.startAt) < bufferEndAt &&
|
|
14
15
|
new Date(entry.bufferEndAt) > startAt);
|
|
15
|
-
if (clash)
|
|
16
|
-
|
|
16
|
+
if (clash) {
|
|
17
|
+
return { success: false, reason: 'That window has just been taken — pick another slot.', timestamp: new Date().toISOString() };
|
|
18
|
+
}
|
|
17
19
|
const booking = {
|
|
18
|
-
|
|
20
|
+
// A counter, not a clock: two holds in the same millisecond used to share an id, and cancel
|
|
21
|
+
// or remove then acted on the wrong row. Scripted tests hit that routinely.
|
|
22
|
+
id: `bk_mock_${(held += 1)}`,
|
|
19
23
|
productId: params.productId,
|
|
20
24
|
resourceId: params.resourceId,
|
|
21
25
|
variantId: params.variantId,
|
|
@@ -29,5 +33,5 @@ export const mockHoldBooking = async (params) => {
|
|
|
29
33
|
};
|
|
30
34
|
// Held in the same list availability reads, so the slot really does disappear.
|
|
31
35
|
MOCK_BOOKINGS.push(booking);
|
|
32
|
-
return { booking, timestamp: new Date().toISOString() };
|
|
36
|
+
return { success: true, booking, timestamp: new Date().toISOString() };
|
|
33
37
|
};
|
|
@@ -7,7 +7,7 @@ export interface HoldBookingParams {
|
|
|
7
7
|
startAt: string;
|
|
8
8
|
/**
|
|
9
9
|
* End of the window — the `endAt` of the slot you picked, or of the last slot in a stay.
|
|
10
|
-
* Required:
|
|
10
|
+
* Required: a window that cannot be lined up with the grid is refused, and the till
|
|
11
11
|
* has no local copy of the rules to derive it from.
|
|
12
12
|
*/
|
|
13
13
|
endAt: string;
|
|
@@ -17,8 +17,12 @@ export interface HoldBookingParams {
|
|
|
17
17
|
customerId?: string;
|
|
18
18
|
}
|
|
19
19
|
export interface HoldBookingResponse {
|
|
20
|
+
/** False when the command was refused — a taken window, a rule, a booking that is not yours. */
|
|
21
|
+
success: boolean;
|
|
22
|
+
/** Why it was refused, in words a cashier can act on. Absent on success. */
|
|
23
|
+
reason?: string;
|
|
20
24
|
/** The claim. `expiresAt` says how long it survives unpaid; `id` is what you confirm or cancel. */
|
|
21
|
-
booking
|
|
25
|
+
booking?: CFBooking;
|
|
22
26
|
timestamp: string;
|
|
23
27
|
}
|
|
24
28
|
export type HoldBooking = (params: HoldBookingParams) => Promise<HoldBookingResponse>;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ReservationStatus } from '@final-commerce/common';
|
|
1
2
|
import { MOCK_BOOKINGS, MOCK_CART, mockPublishEvent } from '../../demo/database';
|
|
2
3
|
export const mockRemoveBookingFromCart = async (params) => {
|
|
3
4
|
console.log('[Mock] removeBookingFromCart called', params);
|
|
@@ -10,15 +11,28 @@ export const mockRemoveBookingFromCart = async (params) => {
|
|
|
10
11
|
MOCK_CART.total -= line;
|
|
11
12
|
MOCK_CART.amountToBeCharged = MOCK_CART.total;
|
|
12
13
|
MOCK_CART.remainingBalance = MOCK_CART.total;
|
|
13
|
-
mockPublishEvent('cart', '
|
|
14
|
+
mockPublishEvent('cart', 'cart-created', { cart: MOCK_CART });
|
|
14
15
|
}
|
|
15
16
|
// The hold has to go back, or the mock is a trap: `addBookingToCart` pushes into the very list
|
|
16
17
|
// availability reads, so add → remove → add the same slot used to refuse forever with "that
|
|
17
18
|
// window has just been taken". The reservation id is `res_<booking.id>`, the link back to the row.
|
|
18
19
|
const bookingId = removed?.bookingId ?? params.reservationInternalId.replace(/^res_/, '');
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
// CANCELLED, not deleted — the host releases a window by moving the row, and a mock that
|
|
21
|
+
// erased it hid both the trail and the fact that a released booking is still a booking.
|
|
22
|
+
const released = MOCK_BOOKINGS.find(({ id }) => id === bookingId);
|
|
23
|
+
if (released)
|
|
24
|
+
released.status = ReservationStatus.CANCELLED;
|
|
25
|
+
// An id the cart does not have is a refusal, as it is on the host — resolving with an empty
|
|
26
|
+
// booking made a failed remove look like a successful one.
|
|
27
|
+
if (!removed) {
|
|
28
|
+
return {
|
|
29
|
+
success: false,
|
|
30
|
+
reason: `Reservation ${params.reservationInternalId} is not in the cart`,
|
|
31
|
+
timestamp: new Date().toISOString(),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
21
34
|
return {
|
|
35
|
+
success: true,
|
|
22
36
|
reservationInternalId: params.reservationInternalId,
|
|
23
37
|
booking: released,
|
|
24
38
|
timestamp: new Date().toISOString(),
|
|
@@ -4,7 +4,11 @@ export interface RemoveBookingFromCartParams {
|
|
|
4
4
|
reservationInternalId: string;
|
|
5
5
|
}
|
|
6
6
|
export interface RemoveBookingFromCartResponse {
|
|
7
|
-
|
|
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
|
+
reservationInternalId?: string;
|
|
8
12
|
/** The released hold, so the caller can see it is no longer held. */
|
|
9
13
|
booking?: CFBooking;
|
|
10
14
|
timestamp: string;
|
package/dist/demo/database.d.ts
CHANGED
|
@@ -94,6 +94,12 @@ export declare const createOrderFromCart: (paymentType: string, amount: number,
|
|
|
94
94
|
export declare const applyMockPayment: (amount: number, paymentType: string, processor?: string) => CFActiveOrder | null;
|
|
95
95
|
export declare const MOCK_BOOKING_RULES_ID = "rule_salon_30";
|
|
96
96
|
export declare const MOCK_BOOKING_RESOURCES: CFBookingResource[];
|
|
97
|
+
/**
|
|
98
|
+
* The clock this mock shop keeps — the same one `getContext` reports. They used to disagree: the
|
|
99
|
+
* slot hours were built with `setHours`, which is the MACHINE's clock, while the context claimed
|
|
100
|
+
* Vancouver. On a machine in Paris a correct screen showed a salon open from midnight to nine.
|
|
101
|
+
*/
|
|
102
|
+
export declare const MOCK_SHOP_TIME_ZONE = "America/Vancouver";
|
|
97
103
|
export declare const MOCK_BOOKINGS: CFBooking[];
|
|
98
104
|
/** Live = confirmed, or held and not yet expired. An expired hold occupies nothing. */
|
|
99
105
|
export declare const mockLiveBookings: () => CFBooking[];
|
package/dist/demo/database.js
CHANGED
|
@@ -928,6 +928,18 @@ export const createOrderFromCart = (paymentType, amount, processor = 'cash') =>
|
|
|
928
928
|
createdAt: new Date().toISOString(),
|
|
929
929
|
};
|
|
930
930
|
MOCK_ORDERS.push(newOrder);
|
|
931
|
+
// Payment is what turns a held window into a kept appointment, so the ROW moves too — the
|
|
932
|
+
// comment above said CONFIRMED while nothing set it. Left held, a paid appointment dropped out
|
|
933
|
+
// of `getBookings` when its deadline passed and the window could be taken again, which is the
|
|
934
|
+
// one transition a booking screen most needs to be able to test.
|
|
935
|
+
for (const reservation of newOrder.reservations ?? []) {
|
|
936
|
+
const row = MOCK_BOOKINGS.find(({ id }) => id === reservation.bookingId);
|
|
937
|
+
if (row) {
|
|
938
|
+
row.status = ReservationStatus.CONFIRMED;
|
|
939
|
+
row.orderId = newOrder._id;
|
|
940
|
+
row.expiresAt = undefined;
|
|
941
|
+
}
|
|
942
|
+
}
|
|
931
943
|
resetMockCart();
|
|
932
944
|
// Publish cart-created event after cart is reset (simulates new empty cart)
|
|
933
945
|
mockPublishEvent('cart', 'cart-created', {});
|
|
@@ -970,14 +982,38 @@ const CLOSE_HOUR = 18;
|
|
|
970
982
|
// Rooms, not people: a resource is whatever is scarce, and a room is the case that
|
|
971
983
|
// reads the same in every vertical a dataset might describe.
|
|
972
984
|
export const MOCK_BOOKING_RESOURCES = [
|
|
973
|
-
|
|
974
|
-
|
|
985
|
+
// Two different photos, from the assets this demo already ships: a screen that picks a
|
|
986
|
+
// resource by sight has to be fed two that look apart, not one repeated.
|
|
987
|
+
{ id: 'res_room_1', name: 'Room 1', kind: BookingResourceKind.ROOM, image: beetImg },
|
|
988
|
+
{ id: 'res_room_2', name: 'Room 2', kind: BookingResourceKind.ROOM, image: roastedTomatoImg },
|
|
975
989
|
];
|
|
990
|
+
/**
|
|
991
|
+
* The clock this mock shop keeps — the same one `getContext` reports. They used to disagree: the
|
|
992
|
+
* slot hours were built with `setHours`, which is the MACHINE's clock, while the context claimed
|
|
993
|
+
* Vancouver. On a machine in Paris a correct screen showed a salon open from midnight to nine.
|
|
994
|
+
*/
|
|
995
|
+
export const MOCK_SHOP_TIME_ZONE = 'America/Vancouver';
|
|
976
996
|
const at = (dayOffset, hour, minute = 0) => {
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
997
|
+
// The shop's wall clock, not this machine's. Two passes, because the offset is a function of
|
|
998
|
+
// the instant and the instant is what we are solving for.
|
|
999
|
+
const naive = Date.UTC(new Date().getUTCFullYear(), new Date().getUTCMonth(), new Date().getUTCDate() + dayOffset, hour, minute);
|
|
1000
|
+
const offsetAt = (instant) => {
|
|
1001
|
+
const parts = {};
|
|
1002
|
+
for (const part of new Intl.DateTimeFormat('en-US', {
|
|
1003
|
+
timeZone: MOCK_SHOP_TIME_ZONE,
|
|
1004
|
+
hour12: false,
|
|
1005
|
+
year: 'numeric',
|
|
1006
|
+
month: '2-digit',
|
|
1007
|
+
day: '2-digit',
|
|
1008
|
+
hour: '2-digit',
|
|
1009
|
+
minute: '2-digit',
|
|
1010
|
+
}).formatToParts(new Date(instant))) {
|
|
1011
|
+
parts[part.type] = part.value;
|
|
1012
|
+
}
|
|
1013
|
+
const asUtc = Date.UTC(Number(parts.year), Number(parts.month) - 1, Number(parts.day), Number(parts.hour) % 24, Number(parts.minute));
|
|
1014
|
+
return asUtc - instant;
|
|
1015
|
+
};
|
|
1016
|
+
return new Date(naive - offsetAt(naive - offsetAt(naive)));
|
|
981
1017
|
};
|
|
982
1018
|
const booking = (id, resourceId, start, status, customerName) => ({
|
|
983
1019
|
id,
|
|
@@ -1000,6 +1036,19 @@ export const MOCK_BOOKINGS = [
|
|
|
1000
1036
|
export const mockLiveBookings = () => MOCK_BOOKINGS.filter(({ status, expiresAt }) => status === ReservationStatus.CONFIRMED ||
|
|
1001
1037
|
(status === ReservationStatus.HELD && (!expiresAt || new Date(expiresAt) > new Date())));
|
|
1002
1038
|
const takenBy = (resourceId, startAt, bufferEndAt) => mockLiveBookings().some((entry) => entry.resourceId === resourceId && new Date(entry.startAt) < bufferEndAt && new Date(entry.bufferEndAt) > startAt);
|
|
1039
|
+
/** The mock's shop day for an instant, `YYYY-MM-DD`, built from parts so it never reads as D/M/Y. */
|
|
1040
|
+
const shopDayOf = (instant) => {
|
|
1041
|
+
const parts = {};
|
|
1042
|
+
for (const part of new Intl.DateTimeFormat('en-CA', {
|
|
1043
|
+
timeZone: MOCK_SHOP_TIME_ZONE,
|
|
1044
|
+
year: 'numeric',
|
|
1045
|
+
month: '2-digit',
|
|
1046
|
+
day: '2-digit',
|
|
1047
|
+
}).formatToParts(instant)) {
|
|
1048
|
+
parts[part.type] = part.value;
|
|
1049
|
+
}
|
|
1050
|
+
return `${parts.year}-${parts.month}-${parts.day}`;
|
|
1051
|
+
};
|
|
1003
1052
|
export const mockBookingAvailability = (productId, from, to, resourceId) => {
|
|
1004
1053
|
const serving = resourceId
|
|
1005
1054
|
? MOCK_BOOKING_RESOURCES.filter((resource) => resource.id === resourceId)
|
|
@@ -1008,9 +1057,12 @@ export const mockBookingAvailability = (productId, from, to, resourceId) => {
|
|
|
1008
1057
|
const step = (SLOT_MINUTES + BUFFER_MINUTES) * 60000;
|
|
1009
1058
|
for (let day = 0; day < 14; day += 1) {
|
|
1010
1059
|
const open = at(day, OPEN_HOUR);
|
|
1011
|
-
if (open < from || open > to)
|
|
1012
|
-
continue;
|
|
1013
1060
|
const close = at(day, CLOSE_HOUR);
|
|
1061
|
+
// Overlap, not containment. Asking from noon used to return nothing for today, because the
|
|
1062
|
+
// day's opening hour lay before the range start — so an afternoon question got an empty
|
|
1063
|
+
// calendar and read as "fully booked".
|
|
1064
|
+
if (close <= from || open >= to)
|
|
1065
|
+
continue;
|
|
1014
1066
|
for (let start = open.getTime(); start + SLOT_MINUTES * 60000 <= close.getTime(); start += step) {
|
|
1015
1067
|
const startAt = new Date(start);
|
|
1016
1068
|
const endAt = new Date(start + SLOT_MINUTES * 60000);
|
|
@@ -1030,6 +1082,10 @@ export const mockBookingAvailability = (productId, from, to, resourceId) => {
|
|
|
1030
1082
|
booked: resources.reduce((sum, entry) => sum + entry.booked, 0),
|
|
1031
1083
|
free: resources.reduce((sum, entry) => sum + entry.free, 0),
|
|
1032
1084
|
resources,
|
|
1085
|
+
// The shop day, as the real host stamps it. The mock keeps one shop on the machine's
|
|
1086
|
+
// own clock, so here that is the machine's day — the point is that the FIELD is there,
|
|
1087
|
+
// so a screen built against the mock never learns to work the day out for itself.
|
|
1088
|
+
dayKey: shopDayOf(startAt),
|
|
1033
1089
|
});
|
|
1034
1090
|
}
|
|
1035
1091
|
}
|
|
@@ -1039,6 +1095,8 @@ export const mockBookingAvailability = (productId, from, to, resourceId) => {
|
|
|
1039
1095
|
bookingType: BookingType.APPOINTMENT,
|
|
1040
1096
|
ratePeriod: BookingRatePeriod.SLOT,
|
|
1041
1097
|
slots,
|
|
1098
|
+
timeZone: MOCK_SHOP_TIME_ZONE,
|
|
1099
|
+
days: [...new Set(slots.map((slot) => slot.dayKey))],
|
|
1042
1100
|
};
|
|
1043
1101
|
};
|
|
1044
1102
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -160,7 +160,7 @@ export type { CalculateRefundTotal, CalculateRefundTotalParams, CalculateRefundT
|
|
|
160
160
|
export type { GetRemainingRefundableQuantities, GetRemainingRefundableQuantitiesParams, GetRemainingRefundableQuantitiesResponse, } from './actions/get-remaining-refundable-quantities/types';
|
|
161
161
|
export type { ProcessPartialRefund, ProcessPartialRefundParams, ProcessPartialRefundResponse, } from './actions/process-partial-refund/types';
|
|
162
162
|
export type { RedeemRefund, RedeemRefundParams, RedeemRefundResponse } from './actions/redeem-refund/types';
|
|
163
|
-
export type { GetRefundPlan, GetRefundPlanParams, GetRefundPlanResponse, RefundPlanSource, RefundPlanAllocation, RefundPlanLeg, } from './actions/get-refund-plan/types';
|
|
163
|
+
export type { GetRefundPlan, GetRefundPlanParams, GetRefundPlanResponse, RefundPlanSource, RefundPlanAllocation, RefundPlanLeg, RefundPlanRowType, RefundPlanTaxLine, RefundPlanAmounts, RefundPlanRow, RefundPlanSelectedRow, RefundPlanTotals, RefundPlanBreakdown, } from './actions/get-refund-plan/types';
|
|
164
164
|
export type { CheckPermission, CheckPermissionParams, CheckPermissionResponse } from './actions/check-permission/types';
|
|
165
165
|
export type { InitiateRefund, InitiateRefundParams, InitiateRefundResponse } from './actions/initiate-refund/types';
|
|
166
166
|
export type { GetCurrentCart, GetCurrentCartResponse } from './actions/get-current-cart/types';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@final-commerce/command-frame",
|
|
3
|
-
"version": "0.8.0
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Commands Frame library",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"ignoreBranchesMissingTickets": true
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
|
-
"@final-commerce/common": "2.4.0
|
|
60
|
+
"@final-commerce/common": "2.4.0"
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
63
|
"@commitlint/cli": "^19.0.0",
|
|
@@ -73,5 +73,8 @@
|
|
|
73
73
|
"typescript-eslint": "^8.0.0",
|
|
74
74
|
"vitest": "^4.0.0"
|
|
75
75
|
},
|
|
76
|
+
"overrides": {
|
|
77
|
+
"js-yaml": "^4.3.2"
|
|
78
|
+
},
|
|
76
79
|
"prettier": "@final-commerce/common/prettier"
|
|
77
80
|
}
|