@go-labs-sg/rsvp 1.2.0 → 1.3.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/README.md
CHANGED
|
@@ -377,7 +377,11 @@ including tax. Omitted tax remains compatible with legacy no-tax configuration.
|
|
|
377
377
|
Read `payment event get` back with the same scope to verify the saved policy.
|
|
378
378
|
|
|
379
379
|
`payment mine get` exposes the attempt's saved tax quote and subtotal/tax/final
|
|
380
|
-
total
|
|
380
|
+
total, plus `capturedAttempt`: the selected attempt when it is captured;
|
|
381
|
+
otherwise the latest captured attempt, including refunded ones. `payment mine receipt get` (`eventSlug`, optional `formSlug`,
|
|
382
|
+
`paymentAttemptId`) reads the provider's current hosted receipt link for the
|
|
383
|
+
caller's own captured attempt; links expire, so read it when needed rather than
|
|
384
|
+
storing it. `payment event list` exposes each attempt's original `quote` plus
|
|
381
385
|
`refundBreakdown` (cumulative refunded subtotal, tax and total). Pending refunds
|
|
382
386
|
are not confirmed refunded amounts. These are payment breakdowns, not statutory
|
|
383
387
|
tax invoices. Organizers choose applicable settings; configuration does not
|
|
@@ -908,8 +912,34 @@ configuration version. Configurable copy is
|
|
|
908
912
|
`definition.communications.participant.seatChange: {enabled, subject, body}`;
|
|
909
913
|
merge into the complete named draft, preview, save with its revision, read back,
|
|
910
914
|
then publish only with separate approval. Existing responses use their saved
|
|
911
|
-
published version
|
|
912
|
-
|
|
915
|
+
published version **unless** the event explicitly opts into the current automatic
|
|
916
|
+
policy. Both default disabled/blank; missing copy never falls back to confirmation
|
|
917
|
+
copy.
|
|
918
|
+
|
|
919
|
+
An event can opt into automatic notices using current event metadata
|
|
920
|
+
`seatChangeNotification: {automatic: true, enabled: true, subject, body}` through
|
|
921
|
+
that same revision-checked `REGISTRATION_RULES` update. It covers historical form
|
|
922
|
+
versions without rewriting responses or sending anything retroactively. Successful
|
|
923
|
+
draft publishes and direct organizer moves/swaps then atomically queue one notice
|
|
924
|
+
per affected registration even when the client checkbox is false or omitted.
|
|
925
|
+
Non-seating edits, drafts, no-ops and rejected saves do not queue notices. Existing
|
|
926
|
+
notification permissions, deduplication and stale-version delivery checks remain.
|
|
927
|
+
Omitting `automatic` from a settings update preserves its saved value; explicitly
|
|
928
|
+
send `automatic: false` to turn it off.
|
|
929
|
+
|
|
930
|
+
Because event policy may send automatically, registration updates containing
|
|
931
|
+
`seats`, all swaps and all draft publishes now require `--allow-email` (or the
|
|
932
|
+
existing interactive effects confirmation). This conservative CLI guard also
|
|
933
|
+
applies to events without automatic notices; approval does not itself turn on
|
|
934
|
+
notifications. Profile/menu-only updates and draft save/discard retain their
|
|
935
|
+
previous effects.
|
|
936
|
+
|
|
937
|
+
Optional `registrationEmail` and `registrationMasterList` presentation settings
|
|
938
|
+
are also written through `REGISTRATION_RULES`; omitted settings are preserved.
|
|
939
|
+
See `examples/amgen-rsvp-copy/registration-presentation-patch.json` and its README
|
|
940
|
+
for a reviewed activation fragment, output-key semantics, receipt/header behavior
|
|
941
|
+
and the separate deployment/configuration/live-send approval gates. No event
|
|
942
|
+
settings are activated by installing the code or reading this example.
|
|
913
943
|
|
|
914
944
|
## Audience tiers
|
|
915
945
|
|
package/dist/index.js
CHANGED
|
@@ -7565,6 +7565,45 @@ var magicLinkSignInInputSchema = object({
|
|
|
7565
7565
|
email: email2({ error: "Enter a valid email address" }).trim(),
|
|
7566
7566
|
callbackURL: string2().min(1).optional()
|
|
7567
7567
|
});
|
|
7568
|
+
// ../../utils/src/rich-text.ts
|
|
7569
|
+
var richTextJsonShape = object({
|
|
7570
|
+
json: custom(),
|
|
7571
|
+
html: string2()
|
|
7572
|
+
});
|
|
7573
|
+
var htmlEntityMap = {
|
|
7574
|
+
amp: "&",
|
|
7575
|
+
apos: "'",
|
|
7576
|
+
cent: "\xA2",
|
|
7577
|
+
copy: "\xA9",
|
|
7578
|
+
emsp: " ",
|
|
7579
|
+
ensp: " ",
|
|
7580
|
+
gt: ">",
|
|
7581
|
+
lt: "<",
|
|
7582
|
+
nbsp: " ",
|
|
7583
|
+
ndash: "\u2013",
|
|
7584
|
+
quot: '"',
|
|
7585
|
+
reg: "\xAE",
|
|
7586
|
+
thinsp: " "
|
|
7587
|
+
};
|
|
7588
|
+
var decodeHtmlEntity = (entity) => {
|
|
7589
|
+
const fromCodePoint = (codePoint) => Number.isInteger(codePoint) && codePoint >= 0 && codePoint <= 1114111 ? String.fromCodePoint(codePoint) : " ";
|
|
7590
|
+
const normalizedEntity = entity.toLowerCase();
|
|
7591
|
+
if (normalizedEntity.startsWith("#x")) {
|
|
7592
|
+
const codePoint = Number.parseInt(normalizedEntity.slice(2), 16);
|
|
7593
|
+
return fromCodePoint(codePoint);
|
|
7594
|
+
}
|
|
7595
|
+
if (normalizedEntity.startsWith("#")) {
|
|
7596
|
+
const codePoint = Number.parseInt(normalizedEntity.slice(1), 10);
|
|
7597
|
+
return fromCodePoint(codePoint);
|
|
7598
|
+
}
|
|
7599
|
+
return htmlEntityMap[normalizedEntity] ?? `&${entity};`;
|
|
7600
|
+
};
|
|
7601
|
+
var stripHtmlToText = (html) => html.replace(/<style[\s\S]*?<\/style>/gi, " ").replace(/<script[\s\S]*?<\/script>/gi, " ").replace(/<!--[\s\S]*?-->/g, " ").replace(/<[^>]+>/g, " ").replace(/&(#\d+|#x[\da-f]+|[a-z][a-z0-9]+);/gi, (_, entity) => decodeHtmlEntity(entity)).replace(/[\u200B-\u200D\uFEFF]/g, "").replace(/\s+/g, " ").trim();
|
|
7602
|
+
var extractTextFromRichText = (richText, fallback = "Untitled") => {
|
|
7603
|
+
const text = stripHtmlToText(richText?.html ?? "");
|
|
7604
|
+
return text || fallback;
|
|
7605
|
+
};
|
|
7606
|
+
|
|
7568
7607
|
// ../validators/src/flow.ts
|
|
7569
7608
|
var eventFlowDateAnswerSchema = exports_iso.date();
|
|
7570
7609
|
var eventFlowDateTimeAnswerSchema = exports_iso.datetime({
|
|
@@ -7697,6 +7736,7 @@ var customFieldValidationSchema = object({
|
|
|
7697
7736
|
});
|
|
7698
7737
|
var customFieldBaseShape = {
|
|
7699
7738
|
helpText: string2().trim().max(500).optional(),
|
|
7739
|
+
hideLabel: boolean2().optional(),
|
|
7700
7740
|
label: string2().trim().min(1).max(240),
|
|
7701
7741
|
outputKey: flowIdentifierSchema,
|
|
7702
7742
|
required: boolean2().default(false),
|
|
@@ -7903,9 +7943,16 @@ var defaultEventSeatChangeNotification = {
|
|
|
7903
7943
|
};
|
|
7904
7944
|
var eventSeatChangeNotificationSchema = object({
|
|
7905
7945
|
body: string2().trim().max(1e4).default(""),
|
|
7946
|
+
automatic: boolean2().optional(),
|
|
7906
7947
|
enabled: boolean2().default(false),
|
|
7907
7948
|
subject: string2().trim().max(240).default("")
|
|
7908
7949
|
}).superRefine((notification, ctx) => {
|
|
7950
|
+
if (notification.automatic && !notification.enabled)
|
|
7951
|
+
ctx.addIssue({
|
|
7952
|
+
code: "custom",
|
|
7953
|
+
message: "Automatic seat-change notifications must be enabled.",
|
|
7954
|
+
path: ["enabled"]
|
|
7955
|
+
});
|
|
7909
7956
|
if (notification.enabled && !notification.subject) {
|
|
7910
7957
|
ctx.addIssue({
|
|
7911
7958
|
code: "custom",
|
|
@@ -7921,6 +7968,68 @@ var eventSeatChangeNotificationSchema = object({
|
|
|
7921
7968
|
});
|
|
7922
7969
|
}
|
|
7923
7970
|
});
|
|
7971
|
+
var getEventFlowConfirmationBodyText = (richText) => {
|
|
7972
|
+
const text = (node) => {
|
|
7973
|
+
if (node.type === "text")
|
|
7974
|
+
return node.text ?? "";
|
|
7975
|
+
if (node.type === "hardBreak")
|
|
7976
|
+
return `
|
|
7977
|
+
`;
|
|
7978
|
+
const content = (node.content ?? []).map(text).join("");
|
|
7979
|
+
return ["paragraph", "heading", "listItem"].includes(node.type ?? "") ? `${content}
|
|
7980
|
+
` : content;
|
|
7981
|
+
};
|
|
7982
|
+
return text(richText.json).trim();
|
|
7983
|
+
};
|
|
7984
|
+
var confirmationBodyRichTextSchema = unknown().superRefine((value, ctx) => {
|
|
7985
|
+
const ancestors = new Set;
|
|
7986
|
+
const withinDepth = (nested, depth) => {
|
|
7987
|
+
if (depth > 32)
|
|
7988
|
+
return false;
|
|
7989
|
+
if (typeof nested !== "object" || nested === null)
|
|
7990
|
+
return true;
|
|
7991
|
+
if (ancestors.has(nested))
|
|
7992
|
+
return false;
|
|
7993
|
+
ancestors.add(nested);
|
|
7994
|
+
const valid = Object.values(nested).every((child) => withinDepth(child, depth + 1));
|
|
7995
|
+
ancestors.delete(nested);
|
|
7996
|
+
return valid;
|
|
7997
|
+
};
|
|
7998
|
+
let valid = withinDepth(value, 0);
|
|
7999
|
+
try {
|
|
8000
|
+
valid = valid && new TextEncoder().encode(JSON.stringify(value)).byteLength <= 250000;
|
|
8001
|
+
} catch {
|
|
8002
|
+
valid = false;
|
|
8003
|
+
}
|
|
8004
|
+
if (!valid)
|
|
8005
|
+
ctx.addIssue({
|
|
8006
|
+
code: "custom",
|
|
8007
|
+
message: "Confirmation body is too large or nested too deeply"
|
|
8008
|
+
});
|
|
8009
|
+
}).pipe(richTextJsonShape).superRefine((value, ctx) => {
|
|
8010
|
+
const validNode = (node) => {
|
|
8011
|
+
if (!node || typeof node !== "object" || !("type" in node) || typeof node.type !== "string")
|
|
8012
|
+
return false;
|
|
8013
|
+
if ("text" in node && typeof node.text !== "string")
|
|
8014
|
+
return false;
|
|
8015
|
+
if ("marks" in node && (!Array.isArray(node.marks) || !node.marks.every((mark) => mark && typeof mark === "object" && typeof mark.type === "string")))
|
|
8016
|
+
return false;
|
|
8017
|
+
return !("content" in node) || Array.isArray(node.content) && node.content.every(validNode);
|
|
8018
|
+
};
|
|
8019
|
+
if (!validNode(value.json) || value.json.type !== "doc") {
|
|
8020
|
+
ctx.addIssue({
|
|
8021
|
+
code: "custom",
|
|
8022
|
+
message: "Confirmation body must be an editor document"
|
|
8023
|
+
});
|
|
8024
|
+
return;
|
|
8025
|
+
}
|
|
8026
|
+
const length = getEventFlowConfirmationBodyText(value).length;
|
|
8027
|
+
if (length === 0 || length > 1e4)
|
|
8028
|
+
ctx.addIssue({
|
|
8029
|
+
code: "custom",
|
|
8030
|
+
message: "Confirmation body must contain 1\u201310,000 characters"
|
|
8031
|
+
});
|
|
8032
|
+
});
|
|
7924
8033
|
var participantConfirmationSchema = object({
|
|
7925
8034
|
enabled: boolean2(),
|
|
7926
8035
|
includeResponseDetails: boolean2().default(false),
|
|
@@ -7929,6 +8038,7 @@ var participantConfirmationSchema = object({
|
|
|
7929
8038
|
supportEmail: email2(),
|
|
7930
8039
|
templates: array(object({
|
|
7931
8040
|
body: string2().trim().min(1).max(1e4),
|
|
8041
|
+
bodyRichText: confirmationBodyRichTextSchema.optional(),
|
|
7932
8042
|
outcomeId: eventFlowEntityIdSchema,
|
|
7933
8043
|
subject: string2().trim().min(1).max(240)
|
|
7934
8044
|
})).max(50)
|
|
@@ -9942,44 +10052,28 @@ var PUBLIC_EVENT_VALIDATED_FONT_FAMILIES = [
|
|
|
9942
10052
|
"Zilla Slab Highlight"
|
|
9943
10053
|
];
|
|
9944
10054
|
|
|
9945
|
-
//
|
|
9946
|
-
var
|
|
9947
|
-
|
|
9948
|
-
|
|
10055
|
+
// ../validators/src/registration-email-presentation.ts
|
|
10056
|
+
var registrationEmailPresentationSchema = strictObject({
|
|
10057
|
+
layout: _enum(["DEFAULT", "GROUPED"]).default("DEFAULT"),
|
|
10058
|
+
senderName: string2().trim().min(1).max(120).regex(/^[^<>\r\n]+$/).optional(),
|
|
10059
|
+
introText: string2().trim().min(1).max(1e4).optional(),
|
|
10060
|
+
menuDetailOutputKeys: array(string2().trim().min(1).max(120)).max(100).default([]),
|
|
10061
|
+
submittedConsentOutputKeys: array(string2().trim().min(1).max(120)).max(100).default([]),
|
|
10062
|
+
seatingDisclaimer: string2().trim().min(1).max(2000).optional(),
|
|
10063
|
+
useResubmissionFooter: boolean2().default(false),
|
|
10064
|
+
supportEmail: email2().optional(),
|
|
10065
|
+
showLuckyDrawId: boolean2().optional()
|
|
9949
10066
|
});
|
|
9950
|
-
|
|
9951
|
-
|
|
9952
|
-
|
|
9953
|
-
|
|
9954
|
-
|
|
9955
|
-
|
|
9956
|
-
|
|
9957
|
-
|
|
9958
|
-
|
|
9959
|
-
|
|
9960
|
-
ndash: "\u2013",
|
|
9961
|
-
quot: '"',
|
|
9962
|
-
reg: "\xAE",
|
|
9963
|
-
thinsp: " "
|
|
9964
|
-
};
|
|
9965
|
-
var decodeHtmlEntity = (entity) => {
|
|
9966
|
-
const fromCodePoint = (codePoint) => Number.isInteger(codePoint) && codePoint >= 0 && codePoint <= 1114111 ? String.fromCodePoint(codePoint) : " ";
|
|
9967
|
-
const normalizedEntity = entity.toLowerCase();
|
|
9968
|
-
if (normalizedEntity.startsWith("#x")) {
|
|
9969
|
-
const codePoint = Number.parseInt(normalizedEntity.slice(2), 16);
|
|
9970
|
-
return fromCodePoint(codePoint);
|
|
9971
|
-
}
|
|
9972
|
-
if (normalizedEntity.startsWith("#")) {
|
|
9973
|
-
const codePoint = Number.parseInt(normalizedEntity.slice(1), 10);
|
|
9974
|
-
return fromCodePoint(codePoint);
|
|
9975
|
-
}
|
|
9976
|
-
return htmlEntityMap[normalizedEntity] ?? `&${entity};`;
|
|
9977
|
-
};
|
|
9978
|
-
var stripHtmlToText = (html) => html.replace(/<style[\s\S]*?<\/style>/gi, " ").replace(/<script[\s\S]*?<\/script>/gi, " ").replace(/<!--[\s\S]*?-->/g, " ").replace(/<[^>]+>/g, " ").replace(/&(#\d+|#x[\da-f]+|[a-z][a-z0-9]+);/gi, (_, entity) => decodeHtmlEntity(entity)).replace(/[\u200B-\u200D\uFEFF]/g, "").replace(/\s+/g, " ").trim();
|
|
9979
|
-
var extractTextFromRichText = (richText, fallback = "Untitled") => {
|
|
9980
|
-
const text = stripHtmlToText(richText?.html ?? "");
|
|
9981
|
-
return text || fallback;
|
|
9982
|
-
};
|
|
10067
|
+
|
|
10068
|
+
// ../validators/src/registration-master-list-presentation.ts
|
|
10069
|
+
var outputKeysSchema = array(string2().min(1).max(160).regex(/^[A-Za-z][A-Za-z0-9_.:-]*$/)).max(100).refine((keys) => new Set(keys).size === keys.length, {
|
|
10070
|
+
message: "Output keys must be unique"
|
|
10071
|
+
});
|
|
10072
|
+
var eventRegistrationMasterListSchema = object({
|
|
10073
|
+
omittedOutputKeys: outputKeysSchema.default([]),
|
|
10074
|
+
finalOutputKeys: outputKeysSchema.default([]),
|
|
10075
|
+
showEligibilityCutoffInHeading: boolean2().default(false)
|
|
10076
|
+
}).strict().refine((config) => !config.finalOutputKeys.some((key) => config.omittedOutputKeys.includes(key)), { message: "Omitted output keys cannot also be final output keys" });
|
|
9983
10077
|
|
|
9984
10078
|
// ../validators/src/event.ts
|
|
9985
10079
|
var eventStatusSchema = _enum(EventStatus);
|
|
@@ -10474,6 +10568,8 @@ var updateEventRegistrationRulesInputSchema = setupStepScopeSchema.extend({
|
|
|
10474
10568
|
capacityLimit: eventCapacityLimitSchema.nullable(),
|
|
10475
10569
|
guestPolicy: guestPolicySchema,
|
|
10476
10570
|
seatChangeNotification: eventSeatChangeNotificationSchema.optional(),
|
|
10571
|
+
registrationEmail: registrationEmailPresentationSchema.optional(),
|
|
10572
|
+
registrationMasterList: eventRegistrationMasterListSchema.optional(),
|
|
10477
10573
|
waitlistEnabled: boolean2(),
|
|
10478
10574
|
waitlistOfferTtlMinutes: number2().int().min(5).max(10080)
|
|
10479
10575
|
}).refine((value) => !value.rsvpOpensAt || !value.rsvpClosesAt || value.rsvpOpensAt < value.rsvpClosesAt, {
|
|
@@ -11360,6 +11456,9 @@ var createPaymentCheckoutInputSchema = object({
|
|
|
11360
11456
|
idempotencyKey: uuid2()
|
|
11361
11457
|
});
|
|
11362
11458
|
var getMyPaymentInputSchema = object({ eventSlug: eventSlugSchema });
|
|
11459
|
+
var getMyPaymentReceiptInputSchema = getMyPaymentInputSchema.extend({
|
|
11460
|
+
paymentAttemptId: string2().min(1)
|
|
11461
|
+
});
|
|
11363
11462
|
var listEventPaymentsInputSchema = eventScopeSchema4.extend({
|
|
11364
11463
|
cursor: string2().min(1).optional(),
|
|
11365
11464
|
limit: number2().int().min(1).max(100).default(50)
|
|
@@ -12339,13 +12438,14 @@ Object.assign(commands, {
|
|
|
12339
12438
|
"event seating occupants list": payloadCommand((api, input) => api.event.getLiveSeatingOccupants.query(input), "List permitted occupants by seatMapId, tableId, department, categoryValue and query; follow nextCursor (--payload JSON).", ["sensitive-data"]),
|
|
12340
12439
|
"people registration edit get": payloadCommand((api, input) => api.event.getRegistrationForEdit.query(input), "Get current revision, permitted attendees, eligible seats and notification readiness; mode RESTORE loads seat maps and previous seats for a cancelled registration (--payload JSON).", ["sensitive-data"]),
|
|
12341
12440
|
"people registration update": {
|
|
12342
|
-
...payloadCommand((api, input) => api.event.updateRegistration.mutate(input), "Save organizer edits with expectedVersion and reason; seating requires full-party seats and
|
|
12441
|
+
...payloadCommand((api, input) => api.event.updateRegistration.mutate(input), "Save organizer edits with expectedVersion and reason; seating requires full-party seats, configured holds and --allow-email because event policy may automatically queue notices. Optional seatChangeNotice queues a notice with a seat-only save (--payload JSON).", ["sensitive-data", "state-change"], "registrationId"),
|
|
12343
12442
|
conditionalEffects: [
|
|
12344
|
-
{ effect: "email", whenPayloadField: "seatChangeNotice" }
|
|
12443
|
+
{ effect: "email", whenPayloadField: "seatChangeNotice" },
|
|
12444
|
+
{ effect: "email", whenPayloadField: "seats" }
|
|
12345
12445
|
]
|
|
12346
12446
|
},
|
|
12347
12447
|
"event seating swap": {
|
|
12348
|
-
...payloadCommand((api, input) => api.event.swapAttendeeSeats.mutate(input), "Swap the seats of two seated attendees in one save, given each attendee's current seatId and a reason.
|
|
12448
|
+
...payloadCommand((api, input) => api.event.swapAttendeeSeats.mutate(input), "Swap the seats of two seated attendees in one save, given each attendee's current seatId and a reason. Requires --allow-email because event policy may automatically queue one notice per affected registration; optional seatChangeNotice also requests notices (--payload JSON).", ["sensitive-data", "state-change", "email"]),
|
|
12349
12449
|
conditionalEffects: [
|
|
12350
12450
|
{ effect: "email", whenPayloadField: "seatChangeNotice" }
|
|
12351
12451
|
]
|
|
@@ -12363,7 +12463,7 @@ Object.assign(commands, {
|
|
|
12363
12463
|
"event mine list": payloadCommand((api, input) => api.event.listMyEvents.query(input), "List the caller's events (--payload JSON)."),
|
|
12364
12464
|
"event reminder list": payloadCommand((api, input) => api.event.listReminderSegments.query(input), "List reminder segments (--payload JSON).", ["sensitive-data"]),
|
|
12365
12465
|
"event rsvp submit": payloadCommand((api, input) => api.event.submitRsvp.mutate(input), "Submit the caller's registration (--payload JSON).", ["sensitive-data", "state-change"]),
|
|
12366
|
-
"event setup step update": payloadCommand((api, input) => api.event.updateSetupStep.mutate(input), "Update one event setup step (--payload JSON).", ["state-change"]),
|
|
12466
|
+
"event setup step update": payloadCommand((api, input) => api.event.updateSetupStep.mutate(input), "Update one revision-checked event setup step (--payload JSON). REGISTRATION_RULES accepts registrationEmail presentation, registrationMasterList output-key ordering/omissions and seatChangeNotification automatic policy; omitted settings are preserved.", ["state-change"]),
|
|
12367
12467
|
"event page update": payloadCommand((api, input) => api.event.updateSetupStep.mutate({
|
|
12368
12468
|
...input,
|
|
12369
12469
|
section: "DETAILS_PAGE"
|
|
@@ -12376,6 +12476,7 @@ Object.assign(commands, {
|
|
|
12376
12476
|
Object.assign(commands, {
|
|
12377
12477
|
"payment event get": payloadCommand((api, input) => api.payment.getEventPayment.query(input), "Get event payment configuration (--payload JSON).", ["financial-write"]),
|
|
12378
12478
|
"payment mine get": payloadCommand((api, input) => api.payment.getMyPayment.query(input), "Get the caller's payment with its saved subtotal, tax and final total breakdown (--payload JSON).", ["financial-write", "sensitive-data"]),
|
|
12479
|
+
"payment mine receipt get": payloadCommand((api, input) => api.payment.getMyPaymentReceipt.query(input), "Get the provider's current hosted receipt link for the caller's captured payment attempt; receiptUrl is null when unavailable (--payload JSON).", ["financial-write", "sensitive-data"]),
|
|
12379
12480
|
"payment event list": payloadCommand((api, input) => api.payment.listEventPayments.query(input), "List event payments with original quote and cumulative refunded subtotal/tax/total, including verified external refunds (--payload JSON).", ["financial-write", "sensitive-data"]),
|
|
12380
12481
|
"payment exception list": payloadCommand((api, input) => api.payment.listPaymentExceptions.query(input), "List payment exceptions (--payload JSON).", ["financial-write", "sensitive-data"]),
|
|
12381
12482
|
"payment exception resolve": payloadCommand((api, input) => api.payment.resolvePaymentException.mutate(input), "Resolve a payment exception (--payload JSON).", ["financial-write", "sensitive-data", "state-change"]),
|
|
@@ -12444,7 +12545,7 @@ Object.assign(commands, {
|
|
|
12444
12545
|
},
|
|
12445
12546
|
"event seating draft publish": {
|
|
12446
12547
|
scope: "event",
|
|
12447
|
-
effects: ["state-change"],
|
|
12548
|
+
effects: ["state-change", "email"],
|
|
12448
12549
|
targetKey: "eventId",
|
|
12449
12550
|
conditionalEffects: [
|
|
12450
12551
|
{ effect: "email", whenPayloadField: "sendSeatChangeEmail" }
|
|
@@ -12456,7 +12557,7 @@ Object.assign(commands, {
|
|
|
12456
12557
|
},
|
|
12457
12558
|
validateInput: (input) => validateSeatingDraftInput(publishSeatingDraftInputSchema, input),
|
|
12458
12559
|
operation: (api, input) => api.event.publishSeatingDraft.mutate(publishSeatingDraftInputSchema.parse(input)),
|
|
12459
|
-
summary: "Atomically publish all draft changes with expectedVersion, UUID requestId and reason.
|
|
12560
|
+
summary: "Atomically publish all draft changes with expectedVersion, UUID requestId and reason. Requires --allow-email: event policy can automatically send notices even when sendSeatChangeEmail is false. Retries reuse requestId and intent. Realtime-enabled free targets need per-registration seatHolds (--payload JSON)."
|
|
12460
12561
|
}
|
|
12461
12562
|
});
|
|
12462
12563
|
var getCommandManifest = () => ({
|
|
@@ -76,3 +76,75 @@ rsvp event setup update \
|
|
|
76
76
|
```
|
|
77
77
|
|
|
78
78
|
This command is documented for later use and was not executed for this task.
|
|
79
|
+
|
|
80
|
+
## Receipt, master-list and automatic seat-change presentation (#1657)
|
|
81
|
+
|
|
82
|
+
`registration-presentation-patch.json` is a **configuration fragment**, not an
|
|
83
|
+
executable mutation payload. It contains reusable event settings with Amgen copy
|
|
84
|
+
and confirmed output keys. No staff-exclusion filter is needed or included.
|
|
85
|
+
|
|
86
|
+
After the compatible API/web/CLI release and **separate approval**, refresh the
|
|
87
|
+
test event first. Build a `REGISTRATION_RULES` input for
|
|
88
|
+
`rsvp event setup step update` with its current `organizationId`, `eventId`,
|
|
89
|
+
`version: configurationVersion`, `rsvpOpensAt`, `rsvpClosesAt`, `dietaryClosesAt`,
|
|
90
|
+
`capacityLimit`, `guestPolicy`, `waitlistEnabled` and
|
|
91
|
+
`waitlistOfferTtlMinutes`. Merge the fragment's three top-level keys into that
|
|
92
|
+
input; retain every other current setting. Do not send the fragment to the
|
|
93
|
+
full `event setup update` endpoint. Review the exact payload before writing,
|
|
94
|
+
read the same event back afterwards, and verify each key. Repeat independently
|
|
95
|
+
for the official event only after test-event acceptance and approval. Do not
|
|
96
|
+
reuse cached version numbers, event IDs or schedules. No commands in this
|
|
97
|
+
section have been executed.
|
|
98
|
+
|
|
99
|
+
- **Receipt:** `registrationEmail` groups check-in, Table Seating, Menu Selection
|
|
100
|
+
and other responses. The current presentation can update old receipts without
|
|
101
|
+
changing their saved form, outcome or answers. Accepted `indemnityAccepted`
|
|
102
|
+
displays “Submitted”; false/missing values do not become accepted. Saved
|
|
103
|
+
`confirmation.showLuckyDrawId: false` remains authoritative; the event may
|
|
104
|
+
additionally hide IDs, but cannot reveal IDs a saved form hid. The display name
|
|
105
|
+
becomes “ASM DND Registration” while retaining the configured verified sender
|
|
106
|
+
address/domain. The one footer derives the event's current registration URL
|
|
107
|
+
and named form slug at delivery, so the test event cannot point to the official
|
|
108
|
+
form by copying an example URL. This fragment replaces the old free-text body
|
|
109
|
+
with one short introduction, avoiding duplicate acknowledgement/footer copy.
|
|
110
|
+
- **Export:** `registrationMasterList.omittedOutputKeys: ["menu:diet"]` omits only
|
|
111
|
+
the derived Vegetarian/non-vegetarian classification. The actual employee/guest
|
|
112
|
+
menus and dietary answers remain. `finalOutputKeys: ["indemnityAccepted"]`
|
|
113
|
+
moves consent after operational columns. The cutoff heading uses the event's
|
|
114
|
+
existing lucky-draw cutoff/timezone; this fragment does **not** change it. The
|
|
115
|
+
cached approved cutoff is 27 November 2026, 8pm Asia/Singapore; verify it afresh.
|
|
116
|
+
- **Automatic notices:** event metadata `seatChangeNotification.automatic: true`
|
|
117
|
+
deliberately enables the current policy for historical form versions too.
|
|
118
|
+
It does not migrate stored forms, responses, or seating. Draft publish and
|
|
119
|
+
direct committed organizer move/swap queue one minimal notice per affected
|
|
120
|
+
registration in the same transaction, regardless of a manual checkbox. The
|
|
121
|
+
current registration email and committed seats are re-read at delivery. A
|
|
122
|
+
failed/denied operation, no-op or non-seating edit does not queue a notice;
|
|
123
|
+
retries do not queue another notice for the same committed request. Existing
|
|
124
|
+
stale-version delivery suppression remains. Enabling this policy **does not
|
|
125
|
+
enqueue or resend anything retroactively**.
|
|
126
|
+
|
|
127
|
+
The fragment's seat-change copy and subject are a proposed activation payload;
|
|
128
|
+
review them before a live write. The support address `events@getout.sg` is
|
|
129
|
+
confirmed. Sender/recipient/contact and active policy must be verified with a
|
|
130
|
+
controlled test send only after separate email approval. Queue acceptance is
|
|
131
|
+
not provider delivery.
|
|
132
|
+
|
|
133
|
+
`confirmation.resend` permission is still required when an automatic notice
|
|
134
|
+
would be queued. A seating edit without that permission fails atomically rather
|
|
135
|
+
than silently skipping the notice. Registration-settings clients that omit the
|
|
136
|
+
new `automatic` property retain the stored value; explicitly set `automatic:
|
|
137
|
+
false` when turning the policy off. An automatic policy cannot be disabled merely
|
|
138
|
+
by saving `enabled: false` without explicitly clearing `automatic`.
|
|
139
|
+
|
|
140
|
+
CLI safety remains separate from the automatic policy: direct registration
|
|
141
|
+
updates containing `seats`, all swaps, and all seating-draft publishes require
|
|
142
|
+
`--allow-email` (or the existing interactive effects confirmation), even when
|
|
143
|
+
`sendSeatChangeEmail` is false or omitted. This conservative guard also applies
|
|
144
|
+
to events without automatic notices; the flag grants approval but does not
|
|
145
|
+
itself enable sending. Non-seating profile/menu updates and saving or discarding
|
|
146
|
+
a draft do not acquire an email effect.
|
|
147
|
+
|
|
148
|
+
Production deployment, event configuration writes, live submissions/reseating,
|
|
149
|
+
export jobs, resends and any real provider send require explicit approval and
|
|
150
|
+
readback. Local HTML/XLSX and synthetic browser checks are not live acceptance.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"registrationEmail": {
|
|
3
|
+
"layout": "GROUPED",
|
|
4
|
+
"senderName": "ASM DND Registration",
|
|
5
|
+
"introText": "Thank you for your response.",
|
|
6
|
+
"menuDetailOutputKeys": [
|
|
7
|
+
"hasDietaryRestrictions",
|
|
8
|
+
"dietaryRestrictions",
|
|
9
|
+
"guestHasDietaryRestrictions",
|
|
10
|
+
"guestDietaryRestrictions"
|
|
11
|
+
],
|
|
12
|
+
"submittedConsentOutputKeys": ["indemnityAccepted"],
|
|
13
|
+
"seatingDisclaimer": "Table seating is subject to changes based on the discretion of the organisation committee, due to seating capacity and to accommodate all guests.",
|
|
14
|
+
"useResubmissionFooter": true,
|
|
15
|
+
"supportEmail": "events@getout.sg",
|
|
16
|
+
"showLuckyDrawId": false
|
|
17
|
+
},
|
|
18
|
+
"registrationMasterList": {
|
|
19
|
+
"omittedOutputKeys": ["menu:diet"],
|
|
20
|
+
"finalOutputKeys": ["indemnityAccepted"],
|
|
21
|
+
"showEligibilityCutoffInHeading": true
|
|
22
|
+
},
|
|
23
|
+
"seatChangeNotification": {
|
|
24
|
+
"automatic": true,
|
|
25
|
+
"enabled": true,
|
|
26
|
+
"subject": "ASM Dinner and Dance 2026 — seating updated",
|
|
27
|
+
"body": "Your table seating for ASM Dinner and Dance 2026 has been updated. Please refer to your current seat assignments below.\n\nTable seating is subject to changes based on the discretion of the organisation committee, due to seating capacity and to accommodate all guests.\n\nFor further queries, please contact events@getout.sg."
|
|
28
|
+
}
|
|
29
|
+
}
|