@final-commerce/command-frame 0.7.0-staging.3 → 0.7.0-staging.5

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.
@@ -1,8 +1,44 @@
1
+ import { ReservationStatus } from '@final-commerce/common';
2
+ import { MOCK_CART, MOCK_PRODUCTS, mockPublishEvent } from '../../demo/database';
1
3
  import { mockHoldBooking } from '../hold-booking/mock';
2
4
  export const mockAddBookingToCart = async (params) => {
3
5
  console.log('[Mock] addBookingToCart called', params);
4
- // Same claim as holdBookingincluding its refusal when the window is already taken. The mock
5
- // cart is not modelled here; what a booking screen must handle is the refusal, not the line.
6
+ // One call, two effectsthe window is claimed AND the service is in the cart. The claim goes
7
+ // first and its refusal propagates: a cart line for a window somebody else took is worse than
8
+ // no line at all.
6
9
  const { booking } = await mockHoldBooking(params);
7
- return { booking, reservationInternalId: `res_${booking.id}`, timestamp: new Date().toISOString() };
10
+ const product = MOCK_PRODUCTS.find(({ _id }) => _id === params.productId);
11
+ const variant = params.variantId
12
+ ? product?.variants?.find(({ _id }) => _id === params.variantId)
13
+ : product?.variants?.[0];
14
+ const price = variant?.price ?? product?.minPrice ?? 0;
15
+ const reservation = {
16
+ internalId: `res_${booking.id}`,
17
+ bookingId: booking.id,
18
+ productId: params.productId,
19
+ variantId: variant?._id ?? null,
20
+ resourceId: params.resourceId,
21
+ name: product?.name ?? 'Booking',
22
+ resourceName: booking.resourceName,
23
+ price,
24
+ quantity: 1,
25
+ total: price,
26
+ taxTableId: product?.taxTable,
27
+ startAt: booking.startAt,
28
+ endAt: booking.endAt,
29
+ bufferEndAt: booking.bufferEndAt,
30
+ status: ReservationStatus.HELD,
31
+ expiresAt: booking.expiresAt,
32
+ };
33
+ if (!MOCK_CART.reservations)
34
+ MOCK_CART.reservations = [];
35
+ MOCK_CART.reservations.push(reservation);
36
+ MOCK_CART.subtotal += price;
37
+ MOCK_CART.total += price;
38
+ MOCK_CART.amountToBeCharged = MOCK_CART.total;
39
+ MOCK_CART.remainingBalance = MOCK_CART.total;
40
+ // The cart topic, not the bookings one: a screen that only watches bookings still has to
41
+ // repaint its cart, and every other cart mutation announces itself the same way.
42
+ mockPublishEvent('cart', 'reservation-added', { reservation });
43
+ return { booking, reservationInternalId: reservation.internalId, timestamp: new Date().toISOString() };
8
44
  };
@@ -1,11 +1,21 @@
1
- import { MOCK_BOOKINGS } from '../../demo/database';
1
+ import { MOCK_BOOKINGS, MOCK_CART, mockPublishEvent } from '../../demo/database';
2
2
  export const mockRemoveBookingFromCart = async (params) => {
3
3
  console.log('[Mock] removeBookingFromCart called', params);
4
+ const reservations = MOCK_CART.reservations ?? [];
5
+ const cartIndex = reservations.findIndex(({ internalId }) => internalId === params.reservationInternalId);
6
+ const [removed] = cartIndex >= 0 ? reservations.splice(cartIndex, 1) : [];
7
+ if (removed) {
8
+ const line = removed.total || removed.price * (removed.quantity ?? 1);
9
+ MOCK_CART.subtotal -= line;
10
+ MOCK_CART.total -= line;
11
+ MOCK_CART.amountToBeCharged = MOCK_CART.total;
12
+ MOCK_CART.remainingBalance = MOCK_CART.total;
13
+ mockPublishEvent('cart', 'reservation-removed', { reservation: removed });
14
+ }
4
15
  // The hold has to go back, or the mock is a trap: `addBookingToCart` pushes into the very list
5
16
  // availability reads, so add → remove → add the same slot used to refuse forever with "that
6
- // window has just been taken". The reservation id the mock hands out is `res_<booking.id>`,
7
- // which is the only link back to the row.
8
- const bookingId = params.reservationInternalId.replace(/^res_/, '');
17
+ // window has just been taken". The reservation id is `res_<booking.id>`, the link back to the row.
18
+ const bookingId = removed?.bookingId ?? params.reservationInternalId.replace(/^res_/, '');
9
19
  const index = MOCK_BOOKINGS.findIndex(({ id }) => id === bookingId);
10
20
  const [released] = index >= 0 ? MOCK_BOOKINGS.splice(index, 1) : [];
11
21
  return {
@@ -15,6 +15,10 @@ export interface MockDatabaseConfig {
15
15
  products?: CFProduct[];
16
16
  orders?: CFActiveOrder[];
17
17
  parkedOrders?: CFActiveOrder[];
18
+ /** Who is scarce: the staff, rooms or machines the dataset's services are booked against. */
19
+ bookingResources?: CFBookingResource[];
20
+ /** Windows already taken when the dataset loads, so a calendar does not open empty. */
21
+ bookings?: CFBooking[];
18
22
  }
19
23
  export declare const MOCK_COMPANY: CFActiveCompany;
20
24
  export declare const MOCK_OUTLET_MAIN: CFActiveOutlet;
@@ -688,6 +688,7 @@ export let MOCK_CART = {
688
688
  remainingBalance: 0,
689
689
  products: [],
690
690
  customSales: [],
691
+ reservations: [],
691
692
  nonRevenueItems: [],
692
693
  customer: null,
693
694
  };
@@ -708,6 +709,7 @@ export const resetMockCart = () => {
708
709
  remainingBalance: 0,
709
710
  products: [],
710
711
  customSales: [],
712
+ reservations: [],
711
713
  nonRevenueItems: [],
712
714
  customer: null,
713
715
  };
@@ -752,6 +754,12 @@ export function setMockDatabase(config) {
752
754
  if (config.parkedOrders !== undefined) {
753
755
  MOCK_PARKED_ORDERS.splice(0, MOCK_PARKED_ORDERS.length, ...config.parkedOrders);
754
756
  }
757
+ if (config.bookingResources !== undefined) {
758
+ MOCK_BOOKING_RESOURCES.splice(0, MOCK_BOOKING_RESOURCES.length, ...config.bookingResources);
759
+ }
760
+ if (config.bookings !== undefined) {
761
+ MOCK_BOOKINGS.splice(0, MOCK_BOOKINGS.length, ...config.bookings);
762
+ }
755
763
  if (MOCK_OUTLETS.length > 0) {
756
764
  Object.assign(MOCK_OUTLET_MAIN, MOCK_OUTLETS[0]);
757
765
  if (MOCK_OUTLET_MAIN.id === undefined && MOCK_OUTLET_MAIN._id !== undefined) {
@@ -888,6 +896,30 @@ export const createOrderFromCart = (paymentType, amount, processor = 'cash') =>
888
896
  billing: MOCK_CART.customer?.billing || null,
889
897
  shipping: MOCK_CART.customer?.shipping || null,
890
898
  lineItems,
899
+ // A paid booking is a sale: it belongs on the order (and thus the receipt),
900
+ // in its own array rather than among the line items, and CONFIRMED — payment
901
+ // is what turns a held window into a kept appointment.
902
+ reservations: (MOCK_CART.reservations ?? []).map((reservation) => ({
903
+ internalId: reservation.internalId,
904
+ bookingId: reservation.bookingId,
905
+ productId: reservation.productId,
906
+ variantId: reservation.variantId,
907
+ resourceId: reservation.resourceId,
908
+ name: reservation.name,
909
+ resourceName: reservation.resourceName,
910
+ price: reservation.price,
911
+ quantity: reservation.quantity,
912
+ total: reservation.total,
913
+ taxTableId: reservation.taxTableId,
914
+ startAt: reservation.startAt,
915
+ endAt: reservation.endAt,
916
+ bufferEndAt: reservation.bufferEndAt,
917
+ // The mock computes no tax anywhere — line items ship `taxes: []` too.
918
+ taxes: [],
919
+ // `expiresAt` deliberately does not come along: a paid booking has no
920
+ // clock left to run out, which is what CONFIRMED means.
921
+ status: ReservationStatus.CONFIRMED,
922
+ })),
891
923
  customSales: [],
892
924
  balance: 0,
893
925
  user: employeeUser,
@@ -935,9 +967,11 @@ const SLOT_MINUTES = 30;
935
967
  const BUFFER_MINUTES = 5;
936
968
  const OPEN_HOUR = 9;
937
969
  const CLOSE_HOUR = 18;
970
+ // Rooms, not people: a resource is whatever is scarce, and a room is the case that
971
+ // reads the same in every vertical a dataset might describe.
938
972
  export const MOCK_BOOKING_RESOURCES = [
939
- { id: 'res_marco', name: 'Marco', kind: BookingResourceKind.STAFF },
940
- { id: 'res_jessica', name: 'Jessica', kind: BookingResourceKind.STAFF },
973
+ { id: 'res_room_1', name: 'Room 1', kind: BookingResourceKind.ROOM },
974
+ { id: 'res_room_2', name: 'Room 2', kind: BookingResourceKind.ROOM },
941
975
  ];
942
976
  const at = (dayOffset, hour, minute = 0) => {
943
977
  const date = new Date();
@@ -958,9 +992,9 @@ const booking = (id, resourceId, start, status, customerName) => ({
958
992
  customerName,
959
993
  });
960
994
  export const MOCK_BOOKINGS = [
961
- booking('bk_1', 'res_marco', at(0, 10), ReservationStatus.CONFIRMED, 'Alex Green'),
962
- booking('bk_2', 'res_jessica', at(0, 11, 30), ReservationStatus.CONFIRMED, 'Dana White'),
963
- booking('bk_3', 'res_marco', at(1, 9, 30), ReservationStatus.CONFIRMED, 'Sam Blue'),
995
+ booking('bk_1', 'res_room_1', at(0, 10), ReservationStatus.CONFIRMED, 'Alex Green'),
996
+ booking('bk_2', 'res_room_2', at(0, 11, 30), ReservationStatus.CONFIRMED, 'Dana White'),
997
+ booking('bk_3', 'res_room_1', at(1, 9, 30), ReservationStatus.CONFIRMED, 'Sam Blue'),
964
998
  ];
965
999
  /** Live = confirmed, or held and not yet expired. An expired hold occupies nothing. */
966
1000
  export const mockLiveBookings = () => MOCK_BOOKINGS.filter(({ status, expiresAt }) => status === ReservationStatus.CONFIRMED ||
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@final-commerce/command-frame",
3
- "version": "0.7.0-staging.3",
3
+ "version": "0.7.0-staging.5",
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.3.0-staging.3"
60
+ "@final-commerce/common": "2.3.0-staging.4"
61
61
  },
62
62
  "devDependencies": {
63
63
  "@commitlint/cli": "^19.0.0",