@ketvietlab/ketsuite 0.1.3 → 0.1.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.
- package/dist/cli.js +1 -1
- package/dist/deployment.d.ts.map +1 -1
- package/dist/deployment.js +3 -0
- package/dist/deployment.js.map +1 -1
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/modules/account_staff_channel/channel-routes.d.ts.map +1 -1
- package/dist/modules/account_staff_channel/channel-routes.js +1 -12
- package/dist/modules/account_staff_channel/channel-routes.js.map +1 -1
- package/dist/modules/attendance/channel-routes.d.ts.map +1 -1
- package/dist/modules/attendance/channel-routes.js +1 -12
- package/dist/modules/attendance/channel-routes.js.map +1 -1
- package/dist/modules/backend/design/client/table-selection.d.mts +3 -3
- package/dist/modules/backend/design/client/table-selection.d.mts.map +1 -1
- package/dist/modules/backend/design/client/table-selection.mjs +1 -1
- package/dist/modules/backend/design/client/table-selection.mjs.map +3 -3
- package/dist/modules/backend/design/content.css +43 -0
- package/dist/modules/backend/design/forms.css +22 -0
- package/dist/modules/backend/design/responsive.css +0 -5
- package/dist/modules/backend/forms.d.ts +38 -1
- package/dist/modules/backend/forms.d.ts.map +1 -1
- package/dist/modules/backend/forms.js +29 -0
- package/dist/modules/backend/forms.js.map +1 -1
- package/dist/modules/backend/index.d.ts +3 -2
- package/dist/modules/backend/index.d.ts.map +1 -1
- package/dist/modules/backend/index.js +2 -2
- package/dist/modules/backend/index.js.map +1 -1
- package/dist/modules/backend/messages.d.ts.map +1 -1
- package/dist/modules/backend/messages.js +40 -2
- package/dist/modules/backend/messages.js.map +1 -1
- package/dist/modules/channel_api/core.d.ts +30 -0
- package/dist/modules/channel_api/core.d.ts.map +1 -1
- package/dist/modules/channel_api/core.js +28 -0
- package/dist/modules/channel_api/core.js.map +1 -1
- package/dist/modules/crm_staff_channel/channel-routes.d.ts.map +1 -1
- package/dist/modules/crm_staff_channel/channel-routes.js +1 -12
- package/dist/modules/crm_staff_channel/channel-routes.js.map +1 -1
- package/dist/modules/flow/membership.d.ts.map +1 -1
- package/dist/modules/flow/membership.js +7 -1
- package/dist/modules/flow/membership.js.map +1 -1
- package/dist/modules/flow/operations.d.ts +10 -4
- package/dist/modules/flow/operations.d.ts.map +1 -1
- package/dist/modules/flow/operations.js +10 -4
- package/dist/modules/flow/operations.js.map +1 -1
- package/dist/modules/flow_staff_channel/channel-routes.d.ts.map +1 -1
- package/dist/modules/flow_staff_channel/channel-routes.js +1 -20
- package/dist/modules/flow_staff_channel/channel-routes.js.map +1 -1
- package/dist/modules/hospitality_core/messages.d.ts.map +1 -1
- package/dist/modules/hospitality_core/messages.js +44 -0
- package/dist/modules/hospitality_core/messages.js.map +1 -1
- package/dist/modules/hospitality_core/night-audit.d.ts.map +1 -1
- package/dist/modules/hospitality_core/night-audit.js +7 -0
- package/dist/modules/hospitality_core/night-audit.js.map +1 -1
- package/dist/modules/hospitality_core/operations.d.ts.map +1 -1
- package/dist/modules/hospitality_core/operations.js +150 -40
- package/dist/modules/hospitality_core/operations.js.map +1 -1
- package/dist/modules/hospitality_core/routes.d.ts.map +1 -1
- package/dist/modules/hospitality_core/routes.js +24 -2
- package/dist/modules/hospitality_core/routes.js.map +1 -1
- package/dist/modules/hospitality_core/screens/check-out-prep.d.ts +46 -0
- package/dist/modules/hospitality_core/screens/check-out-prep.d.ts.map +1 -0
- package/dist/modules/hospitality_core/screens/check-out-prep.js +133 -0
- package/dist/modules/hospitality_core/screens/check-out-prep.js.map +1 -0
- package/dist/modules/hospitality_core/screens/folio-detail.d.ts.map +1 -1
- package/dist/modules/hospitality_core/screens/folio-detail.js +2 -4
- package/dist/modules/hospitality_core/screens/folio-detail.js.map +1 -1
- package/dist/modules/hospitality_core/screens/index.d.ts +2 -0
- package/dist/modules/hospitality_core/screens/index.d.ts.map +1 -1
- package/dist/modules/hospitality_core/screens/index.js +1 -0
- package/dist/modules/hospitality_core/screens/index.js.map +1 -1
- package/dist/modules/hospitality_core/screens/reservation-detail.d.ts.map +1 -1
- package/dist/modules/hospitality_core/screens/reservation-detail.js +8 -1
- package/dist/modules/hospitality_core/screens/reservation-detail.js.map +1 -1
- package/dist/modules/hospitality_core/screens/shared.d.ts +12 -0
- package/dist/modules/hospitality_core/screens/shared.d.ts.map +1 -1
- package/dist/modules/hospitality_core/screens/shared.js +25 -4
- package/dist/modules/hospitality_core/screens/shared.js.map +1 -1
- package/dist/modules/hospitality_core/services.d.ts +48 -0
- package/dist/modules/hospitality_core/services.d.ts.map +1 -1
- package/dist/modules/hospitality_core/services.js +70 -0
- package/dist/modules/hospitality_core/services.js.map +1 -1
- package/dist/modules/hospitality_staff_channel/channel-routes.d.ts.map +1 -1
- package/dist/modules/hospitality_staff_channel/channel-routes.js +1 -12
- package/dist/modules/hospitality_staff_channel/channel-routes.js.map +1 -1
- package/dist/modules/inventory_staff_channel/channel-routes.d.ts.map +1 -1
- package/dist/modules/inventory_staff_channel/channel-routes.js +1 -12
- package/dist/modules/inventory_staff_channel/channel-routes.js.map +1 -1
- package/dist/modules/purchase_staff_channel/order-routes.d.ts.map +1 -1
- package/dist/modules/purchase_staff_channel/order-routes.js +1 -12
- package/dist/modules/purchase_staff_channel/order-routes.js.map +1 -1
- package/dist/modules/quality_staff_channel/channel-routes.d.ts.map +1 -1
- package/dist/modules/quality_staff_channel/channel-routes.js +1 -12
- package/dist/modules/quality_staff_channel/channel-routes.js.map +1 -1
- package/dist/modules/sale/functions.d.ts.map +1 -1
- package/dist/modules/sale/functions.js +27 -17
- package/dist/modules/sale/functions.js.map +1 -1
- package/dist/modules/sale_staff_channel/channel-routes.d.ts.map +1 -1
- package/dist/modules/sale_staff_channel/channel-routes.js +1 -12
- package/dist/modules/sale_staff_channel/channel-routes.js.map +1 -1
- package/dist/modules/sale_staff_channel/order-routes.d.ts.map +1 -1
- package/dist/modules/sale_staff_channel/order-routes.js +1 -12
- package/dist/modules/sale_staff_channel/order-routes.js.map +1 -1
- package/dist/modules/stock_staff_channel/operation-routes.d.ts.map +1 -1
- package/dist/modules/stock_staff_channel/operation-routes.js +1 -12
- package/dist/modules/stock_staff_channel/operation-routes.js.map +1 -1
- package/dist/modules/user_backend/messages.d.ts +4 -0
- package/dist/modules/user_backend/messages.d.ts.map +1 -1
- package/dist/modules/user_backend/messages.js +4 -0
- package/dist/modules/user_backend/messages.js.map +1 -1
- package/dist/modules/user_backend/routes.d.ts.map +1 -1
- package/dist/modules/user_backend/routes.js +28 -1
- package/dist/modules/user_backend/routes.js.map +1 -1
- package/dist/modules/user_backend/screens/user-form.d.ts +1 -0
- package/dist/modules/user_backend/screens/user-form.d.ts.map +1 -1
- package/dist/modules/user_backend/screens/user-form.js +28 -0
- package/dist/modules/user_backend/screens/user-form.js.map +1 -1
- package/dist/modules/website/cms.d.ts.map +1 -1
- package/dist/modules/website/cms.js +283 -6
- package/dist/modules/website/cms.js.map +1 -1
- package/dist/modules/website/index.d.ts.map +1 -1
- package/dist/modules/website/index.js +12 -0
- package/dist/modules/website/index.js.map +1 -1
- package/dist/modules/website/media-usage.d.ts +38 -0
- package/dist/modules/website/media-usage.d.ts.map +1 -0
- package/dist/modules/website/media-usage.js +87 -0
- package/dist/modules/website/media-usage.js.map +1 -0
- package/dist/modules/website/paths.js +1 -1
- package/dist/modules/website/paths.js.map +1 -1
- package/dist/modules/website/sections.d.ts.map +1 -1
- package/dist/modules/website/sections.js +12 -1
- package/dist/modules/website/sections.js.map +1 -1
- package/dist/modules/website_backend/index.js +138 -0
- package/dist/modules/website_backend/index.js.map +1 -1
- package/dist/modules/website_backend/menus.d.ts.map +1 -1
- package/dist/modules/website_backend/menus.js +11 -0
- package/dist/modules/website_backend/menus.js.map +1 -1
- package/dist/modules/website_backend/routes.d.ts.map +1 -1
- package/dist/modules/website_backend/routes.js +414 -31
- package/dist/modules/website_backend/routes.js.map +1 -1
- package/dist/modules/website_backend/screens/index.d.ts +98 -6
- package/dist/modules/website_backend/screens/index.d.ts.map +1 -1
- package/dist/modules/website_backend/screens/index.js +600 -154
- package/dist/modules/website_backend/screens/index.js.map +1 -1
- package/dist/modules/website_backend/screens/page-frame.d.ts +3 -1
- package/dist/modules/website_backend/screens/page-frame.d.ts.map +1 -1
- package/dist/modules/website_backend/screens/page-frame.js +1 -1
- package/dist/modules/website_backend/screens/page-frame.js.map +1 -1
- package/dist/modules/website_menu/functions.d.ts.map +1 -1
- package/dist/modules/website_menu/functions.js +51 -0
- package/dist/modules/website_menu/functions.js.map +1 -1
- package/dist/modules/website_menu/index.js +2 -0
- package/dist/modules/website_menu/index.js.map +1 -1
- package/dist/modules/website_search/functions.d.ts.map +1 -1
- package/dist/modules/website_search/functions.js +10 -120
- package/dist/modules/website_search/functions.js.map +1 -1
- package/dist/modules/website_search/index.js +2 -0
- package/dist/modules/website_search/index.js.map +1 -1
- package/dist/modules/website_search/jobs.d.ts +3 -0
- package/dist/modules/website_search/jobs.d.ts.map +1 -0
- package/dist/modules/website_search/jobs.js +101 -0
- package/dist/modules/website_search/jobs.js.map +1 -0
- package/dist/modules/website_search/rebuild.d.ts +33 -0
- package/dist/modules/website_search/rebuild.d.ts.map +1 -0
- package/dist/modules/website_search/rebuild.js +125 -0
- package/dist/modules/website_search/rebuild.js.map +1 -0
- package/dist/permission-catalogue.d.ts.map +1 -1
- package/dist/permission-catalogue.js +14 -1
- package/dist/permission-catalogue.js.map +1 -1
- package/dist/scaffold/index.js +1 -1
- package/dist/ui/chrome.d.ts +8 -0
- package/dist/ui/chrome.d.ts.map +1 -1
- package/dist/ui/chrome.js +9 -1
- package/dist/ui/chrome.js.map +1 -1
- package/dist/ui/client/table-selection-view.d.ts.map +1 -1
- package/dist/ui/client/table-selection-view.js +67 -5
- package/dist/ui/client/table-selection-view.js.map +1 -1
- package/dist/ui/form.d.ts +2 -0
- package/dist/ui/form.d.ts.map +1 -1
- package/dist/ui/form.js +1 -0
- package/dist/ui/form.js.map +1 -1
- package/dist/ui/index.d.ts +2 -2
- package/dist/ui/index.d.ts.map +1 -1
- package/dist/ui/index.js +2 -2
- package/dist/ui/index.js.map +1 -1
- package/dist/ui/state.d.ts +20 -1
- package/dist/ui/state.d.ts.map +1 -1
- package/dist/ui/state.js +17 -0
- package/dist/ui/state.js.map +1 -1
- package/package.json +4 -4
|
@@ -3,7 +3,7 @@ import { addDecimals, canonicalDecimalText, compareDecimals, moneyMinor, minorTe
|
|
|
3
3
|
import { BILLING_MODES, BOOKING_PROVIDERS, BOOKING_TYPES, DOCUMENT_TYPES, GENDERS, OCR_STATES, OUT_OF_SERVICE_ROOM_STATUSES, } from './types.js';
|
|
4
4
|
import { dateKeyIn } from './calendar.js';
|
|
5
5
|
import { defaultRatePlan, hourlyAvailable, InventoryConflict, occupancyDates, quoteAvailability, recordInventoryChange, releaseInventory, replaceReservedInventory, reserveInventory, restrictionIssues, } from './inventory.js';
|
|
6
|
-
import { postCharge } from './services.js';
|
|
6
|
+
import { FolioConflict, postCharge, settleCancelledFolio } from './services.js';
|
|
7
7
|
import { initializeRecurringRent } from './night-audit.js';
|
|
8
8
|
const issue = (field, code, params) => ({
|
|
9
9
|
field,
|
|
@@ -150,6 +150,8 @@ const transition = async (run) => {
|
|
|
150
150
|
catch (error) {
|
|
151
151
|
if (error instanceof TransitionConflict)
|
|
152
152
|
return failure(error.problem);
|
|
153
|
+
if (error instanceof FolioConflict)
|
|
154
|
+
return failure(error.problem);
|
|
153
155
|
if (error instanceof InventoryConflict)
|
|
154
156
|
return failure(error.problem);
|
|
155
157
|
throw error;
|
|
@@ -391,6 +393,11 @@ const voidPostedCharge = async (ctx, args) => {
|
|
|
391
393
|
export const applyNoShow = async (ctx, reservation, reason, at) => {
|
|
392
394
|
const timestamp = at.toISOString();
|
|
393
395
|
const property = await record(ctx, 'hospitality_core.Property', reservation.propertyId);
|
|
396
|
+
// A guest who never arrived owes what the cancellation policy says, not the
|
|
397
|
+
// whole stay. The folio used to close with every room night still active on
|
|
398
|
+
// it, so a flexible booking nobody showed up for was billed in full — a
|
|
399
|
+
// no-show is the cancellation the guest never got around to making.
|
|
400
|
+
const fee = await cancellationFee(ctx, reservation, at);
|
|
394
401
|
const inventoryDates = reservation.bookingType === 'hourly'
|
|
395
402
|
? []
|
|
396
403
|
: occupancyDates(reservation.checkIn, reservation.checkOut, String(property?.timezone ?? 'UTC'));
|
|
@@ -407,9 +414,16 @@ export const applyNoShow = async (ctx, reservation, reason, at) => {
|
|
|
407
414
|
const folio = await record(tx, 'hospitality_core.Folio', reservation.folioId);
|
|
408
415
|
if (folio?.state !== 'open')
|
|
409
416
|
throw new TransitionConflict(issue('folioId', 'folio_not_open'));
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
417
|
+
await settleCancelledFolio(tx, {
|
|
418
|
+
folioId: folio.id,
|
|
419
|
+
stayId: reservation.stayId,
|
|
420
|
+
fee: fee.amount,
|
|
421
|
+
chargeId: `${String(reservation.id)}:no_show`,
|
|
422
|
+
sourceKey: `reservation:${String(reservation.id)}:no_show`,
|
|
423
|
+
reason: fee.code,
|
|
424
|
+
kind: 'no_show',
|
|
425
|
+
at: timestamp,
|
|
426
|
+
});
|
|
413
427
|
if (inventoryDates.length) {
|
|
414
428
|
await releaseInventory(tx, reservation.propertyId, reservation.roomTypeId, inventoryDates, Number(reservation.roomQuantity ?? 1));
|
|
415
429
|
await recordInventoryChange(tx, {
|
|
@@ -421,17 +435,9 @@ export const applyNoShow = async (ctx, reservation, reason, at) => {
|
|
|
421
435
|
aggregateId: reservation.id,
|
|
422
436
|
});
|
|
423
437
|
}
|
|
424
|
-
return success(reservation.id, { state: 'no_show' });
|
|
438
|
+
return success(reservation.id, { state: 'no_show', noShowFee: fee.amount });
|
|
425
439
|
});
|
|
426
440
|
};
|
|
427
|
-
/**
|
|
428
|
-
* A room is kept for one stay at a time over the nights it is kept for.
|
|
429
|
-
*
|
|
430
|
-
* Room `status` cannot carry this. A room held for next Tuesday is an ordinary
|
|
431
|
-
* available room today and must stay sellable; `occupied` is about now, not
|
|
432
|
-
* about a calendar. So the exclusion lives in the assignment schedule, and this
|
|
433
|
-
* is the question every writer of one has to ask first.
|
|
434
|
-
*/
|
|
435
441
|
/**
|
|
436
442
|
* Put the room's own name on the assignments that carry only its id.
|
|
437
443
|
*
|
|
@@ -455,6 +461,14 @@ const nameAssignedRooms = async (ctx, stays) => {
|
|
|
455
461
|
assignment.roomName = names.get(String(assignment.roomId)) ?? assignment.roomName;
|
|
456
462
|
return stays;
|
|
457
463
|
};
|
|
464
|
+
/**
|
|
465
|
+
* A room is kept for one stay at a time over the nights it is kept for.
|
|
466
|
+
*
|
|
467
|
+
* Room `status` cannot carry this. A room held for next Tuesday is an ordinary
|
|
468
|
+
* available room today and must stay sellable; `occupied` is about now, not
|
|
469
|
+
* about a calendar. So the exclusion lives in the assignment schedule, and this
|
|
470
|
+
* is the question every writer of one has to ask first.
|
|
471
|
+
*/
|
|
458
472
|
const roomTakenBetween = async (ctx, roomId, startAt, endAt, exceptStayId) => {
|
|
459
473
|
const A = ctx.table('hospitality_core.RoomAssignment');
|
|
460
474
|
const rows = await ctx.db.all(from(A).where(eq(A.roomId, roomId)));
|
|
@@ -930,32 +944,14 @@ export const operations = {
|
|
|
930
944
|
throw new TransitionConflict(issue('state', 'transition_conflict'));
|
|
931
945
|
await closeHold(tx, reservation.stayId, 'room_hold_cancelled');
|
|
932
946
|
}
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
folioId: reservation.folioId,
|
|
942
|
-
stayId: reservation.stayId,
|
|
943
|
-
description: `cancellation:${fee.code}`,
|
|
944
|
-
type: 'cancellation',
|
|
945
|
-
quantity: '1',
|
|
946
|
-
unitPrice: fee.amount,
|
|
947
|
-
amount: fee.amount,
|
|
948
|
-
occurredAt: at,
|
|
949
|
-
sourceKey: `reservation:${String(reservation.id)}:cancellation`,
|
|
950
|
-
state: 'active',
|
|
951
|
-
});
|
|
952
|
-
// Money owed does not vanish because the stay did. A folio carrying a
|
|
953
|
-
// penalty closes; only a folio owing nothing is cancelled outright.
|
|
954
|
-
await tx.db.update('hospitality_core.Folio', { id: reservation.folioId }, {
|
|
955
|
-
state: compareDecimals(fee.amount, '0') > 0 ? 'closed' : 'cancelled',
|
|
956
|
-
amountTotal: fee.amount,
|
|
957
|
-
closedAt: at,
|
|
958
|
-
version: Number(folio?.version ?? 0) + 1,
|
|
947
|
+
await settleCancelledFolio(tx, {
|
|
948
|
+
folioId: reservation.folioId,
|
|
949
|
+
stayId: reservation.stayId,
|
|
950
|
+
fee: fee.amount,
|
|
951
|
+
chargeId: `${String(reservation.id)}:cancellation`,
|
|
952
|
+
sourceKey: `reservation:${String(reservation.id)}:cancellation`,
|
|
953
|
+
reason: fee.code,
|
|
954
|
+
at,
|
|
959
955
|
});
|
|
960
956
|
if (inventoryDates.length)
|
|
961
957
|
await releaseInventory(tx, reservation.propertyId, reservation.roomTypeId, inventoryDates, Number(reservation.roomQuantity ?? 1));
|
|
@@ -974,16 +970,21 @@ export const operations = {
|
|
|
974
970
|
}),
|
|
975
971
|
markNoShow: defineFn({
|
|
976
972
|
input: { id: 'id', reason: 'text', at: 'datetime?' },
|
|
977
|
-
output: { ok: 'bool', id: 'id?', state: 'text?', errors: 'json?' },
|
|
973
|
+
output: { ok: 'bool', id: 'id?', state: 'text?', noShowFee: 'decimal?', errors: 'json?' },
|
|
978
974
|
effects: [
|
|
979
975
|
'read:hospitality_core.Reservation',
|
|
976
|
+
'read:hospitality_core.Charge',
|
|
980
977
|
'read:hospitality_core.Folio',
|
|
981
978
|
'read:hospitality_core.Property',
|
|
979
|
+
'read:hospitality_core.RoomType',
|
|
980
|
+
'read:hospitality_core.CancellationPolicy',
|
|
981
|
+
'read:company.Company',
|
|
982
982
|
'read:hospitality_core.Room',
|
|
983
983
|
'read:hospitality_core.AvailabilityLedger',
|
|
984
984
|
'write:hospitality_core.Reservation',
|
|
985
985
|
'write:hospitality_core.Stay',
|
|
986
986
|
'write:hospitality_core.Folio',
|
|
987
|
+
'write:hospitality_core.Charge',
|
|
987
988
|
'write:hospitality_core.AvailabilityLedger',
|
|
988
989
|
'write:hospitality_core.InventoryChange',
|
|
989
990
|
'read:hospitality_core.RoomAssignment',
|
|
@@ -1695,6 +1696,115 @@ export const operations = {
|
|
|
1695
1696
|
return ctx.db.all(query);
|
|
1696
1697
|
},
|
|
1697
1698
|
}),
|
|
1699
|
+
/**
|
|
1700
|
+
* Everything the desk needs before it presses check out, in one read.
|
|
1701
|
+
*
|
|
1702
|
+
* A departure was four screens: the stay to see who and which room, the folio
|
|
1703
|
+
* to see what is owed, the extras to see what a supplier has not confirmed
|
|
1704
|
+
* yet, and the property to know whether the hour matters. Nothing joined
|
|
1705
|
+
* them, so whether a guest could leave was a judgement each person made by
|
|
1706
|
+
* hand — and the refusal only arrived after the button.
|
|
1707
|
+
*
|
|
1708
|
+
* The blockers here are the same ones `checkOut` raises, read rather than
|
|
1709
|
+
* discovered. `balance` is deliberately not one of them: leaving is not
|
|
1710
|
+
* paying, and a hotel that refused every departure with a bill outstanding
|
|
1711
|
+
* would refuse most of them.
|
|
1712
|
+
*/
|
|
1713
|
+
checkOutReadiness: defineFn({
|
|
1714
|
+
input: { stayId: 'id' },
|
|
1715
|
+
output: {
|
|
1716
|
+
stayId: 'id',
|
|
1717
|
+
state: 'text',
|
|
1718
|
+
ready: 'bool',
|
|
1719
|
+
roomId: 'id?',
|
|
1720
|
+
roomName: 'text?',
|
|
1721
|
+
guestName: 'text?',
|
|
1722
|
+
checkIn: 'datetime?',
|
|
1723
|
+
dueOut: 'datetime?',
|
|
1724
|
+
late: 'bool',
|
|
1725
|
+
lateReasonRequired: 'bool',
|
|
1726
|
+
folioId: 'id?',
|
|
1727
|
+
folioState: 'text?',
|
|
1728
|
+
balance: 'decimal?',
|
|
1729
|
+
charges: 'json?',
|
|
1730
|
+
awaitingFulfilment: 'json?',
|
|
1731
|
+
blockers: 'json?',
|
|
1732
|
+
},
|
|
1733
|
+
effects: [
|
|
1734
|
+
'read:hospitality_core.Stay',
|
|
1735
|
+
'read:hospitality_core.Property',
|
|
1736
|
+
'read:hospitality_core.Room',
|
|
1737
|
+
'read:hospitality_core.RoomAssignment',
|
|
1738
|
+
'read:hospitality_core.Folio',
|
|
1739
|
+
'read:hospitality_core.Charge',
|
|
1740
|
+
'read:hospitality_core.ExtraLine',
|
|
1741
|
+
'read:partner.Partner',
|
|
1742
|
+
],
|
|
1743
|
+
agent: true,
|
|
1744
|
+
handler: async (ctx, args) => {
|
|
1745
|
+
const stay = await record(ctx, 'hospitality_core.Stay', args.stayId);
|
|
1746
|
+
if (!stay)
|
|
1747
|
+
return {
|
|
1748
|
+
stayId: args.stayId,
|
|
1749
|
+
state: 'missing',
|
|
1750
|
+
ready: false,
|
|
1751
|
+
late: false,
|
|
1752
|
+
lateReasonRequired: false,
|
|
1753
|
+
blockers: [issue('stayId', 'stay_missing')],
|
|
1754
|
+
};
|
|
1755
|
+
const property = await record(ctx, 'hospitality_core.Property', stay.propertyId);
|
|
1756
|
+
const room = stay.currentRoomId ? await record(ctx, 'hospitality_core.Room', stay.currentRoomId) : null;
|
|
1757
|
+
const partner = stay.partnerId ? await record(ctx, 'partner.Partner', stay.partnerId) : null;
|
|
1758
|
+
const folio = stay.folioId ? await record(ctx, 'hospitality_core.Folio', stay.folioId) : null;
|
|
1759
|
+
const now = new Date();
|
|
1760
|
+
const dueOut = date(stay.checkOut);
|
|
1761
|
+
const late = !!dueOut && now > dueOut;
|
|
1762
|
+
const blockers = [];
|
|
1763
|
+
if (stay.state === 'checked_out')
|
|
1764
|
+
blockers.push(issue('state', 'stay_already_checked_out'));
|
|
1765
|
+
else if (stay.state !== 'checked_in' || !stay.currentRoomId)
|
|
1766
|
+
blockers.push(issue('state', 'stay_cannot_check_out'));
|
|
1767
|
+
if (!property)
|
|
1768
|
+
blockers.push(issue('propertyId', 'property_missing'));
|
|
1769
|
+
const A = ctx.table('hospitality_core.RoomAssignment');
|
|
1770
|
+
const active = await ctx.db.one(from(A).where(eq(A.stayId, stay.id), eq(A.state, 'active')));
|
|
1771
|
+
if (stay.state === 'checked_in' && !active)
|
|
1772
|
+
blockers.push(issue('stayId', 'active_assignment_missing'));
|
|
1773
|
+
const C = ctx.table('hospitality_core.Charge');
|
|
1774
|
+
const charges = folio
|
|
1775
|
+
? await ctx.db.all(from(C)
|
|
1776
|
+
.where(eq(C.folioId, folio.id), eq(C.state, 'active'))
|
|
1777
|
+
.orderBy(asc(C.occurredAt), asc(C.id)))
|
|
1778
|
+
: [];
|
|
1779
|
+
// A line a supplier still owes us is not a refusal, but closing the folio
|
|
1780
|
+
// over it is how a charge goes missing. The desk gets to see it first.
|
|
1781
|
+
const E = ctx.table('hospitality_core.ExtraLine');
|
|
1782
|
+
const lines = await ctx.db.all(from(E).where(eq(E.stayId, stay.id), eq(E.active, true)).preload('charges'));
|
|
1783
|
+
const awaitingFulfilment = lines
|
|
1784
|
+
.filter((line) => String(line.fulfillmentKind ?? 'none') === 'external_stock')
|
|
1785
|
+
.filter((line) => !(line.charges ?? []).some((charge) => charge.state === 'active'))
|
|
1786
|
+
.map((line) => ({ id: line.id, description: line.description, quantity: line.quantity }));
|
|
1787
|
+
const lateReasonRequired = property?.enforceTimes === true && late;
|
|
1788
|
+
return {
|
|
1789
|
+
stayId: stay.id,
|
|
1790
|
+
state: String(stay.state),
|
|
1791
|
+
ready: blockers.length === 0,
|
|
1792
|
+
roomId: stay.currentRoomId ?? null,
|
|
1793
|
+
roomName: room ? String(room.name ?? room.code ?? room.id) : null,
|
|
1794
|
+
guestName: partner ? String(partner.name ?? '') : null,
|
|
1795
|
+
checkIn: stay.checkIn ?? null,
|
|
1796
|
+
dueOut: stay.checkOut ?? null,
|
|
1797
|
+
late,
|
|
1798
|
+
lateReasonRequired,
|
|
1799
|
+
folioId: folio?.id ?? null,
|
|
1800
|
+
folioState: folio ? String(folio.state) : null,
|
|
1801
|
+
balance: folio ? String(folio.amountTotal ?? '0') : null,
|
|
1802
|
+
charges,
|
|
1803
|
+
awaitingFulfilment,
|
|
1804
|
+
blockers,
|
|
1805
|
+
};
|
|
1806
|
+
},
|
|
1807
|
+
}),
|
|
1698
1808
|
getFolio: defineFn({
|
|
1699
1809
|
input: { id: 'id' },
|
|
1700
1810
|
output: {
|