@v-office/website-sdk 2.11.0 → 2.13.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.
Files changed (37) hide show
  1. package/README.md +6 -1
  2. package/dist/capabilities/search.d.mts +2 -0
  3. package/dist/capabilities/search.mjs +2 -0
  4. package/dist/cli.d.mts +1 -1
  5. package/dist/cli.mjs +2 -2
  6. package/dist/{client-DYAHnU5F.mjs → client-Ma0daa2H.mjs} +140 -68
  7. package/dist/index.d.mts +569 -344
  8. package/dist/index.mjs +1 -1
  9. package/dist/instructions/CHANGELOG.md +2 -0
  10. package/dist/instructions/MIGRATION.md +2 -0
  11. package/dist/instructions/README.md +12 -4
  12. package/dist/instructions/booking.md +56 -40
  13. package/dist/instructions/quote.md +100 -6
  14. package/dist/instructions/search.md +99 -12
  15. package/dist/instructions/versions/2.12.0/CHANGELOG.md +56 -0
  16. package/dist/instructions/versions/2.12.0/MIGRATION.md +194 -0
  17. package/dist/instructions/versions/2.13.0/CHANGELOG.md +87 -0
  18. package/dist/instructions/versions/2.13.0/MIGRATION.md +315 -0
  19. package/dist/instructions/versions/2.5.0/MIGRATION.md +3 -0
  20. package/dist/{quote-Cs6jGoRS.mjs → quote-Qy_affum.mjs} +4 -4
  21. package/dist/{rentals-CFod9H4m.mjs → rentals-BG1diZoo.mjs} +7 -4
  22. package/dist/{search-QlZrI7ue.mjs → search-Byi6XboR.mjs} +39 -19
  23. package/dist/{to-rental-highlights-B6a_fi0j.mjs → to-rental-highlights-BVikT4Iz.mjs} +4 -2
  24. package/dist/translations/shared/de-DE/booking.json +7 -0
  25. package/dist/translations/shared/en-US/booking.json +7 -0
  26. package/instructions/CHANGELOG.md +2 -0
  27. package/instructions/MIGRATION.md +2 -0
  28. package/instructions/README.md +12 -4
  29. package/instructions/booking.md +56 -40
  30. package/instructions/quote.md +100 -6
  31. package/instructions/search.md +99 -12
  32. package/instructions/versions/2.12.0/CHANGELOG.md +56 -0
  33. package/instructions/versions/2.12.0/MIGRATION.md +194 -0
  34. package/instructions/versions/2.13.0/CHANGELOG.md +87 -0
  35. package/instructions/versions/2.13.0/MIGRATION.md +315 -0
  36. package/instructions/versions/2.5.0/MIGRATION.md +3 -0
  37. package/package.json +16 -13
@@ -0,0 +1,194 @@
1
+ # Migration: 2.11.0 to 2.12.0
2
+
3
+ 2.12.0 adds optional discount information to priced legacy v9 search results,
4
+ normalizes empty quote booking-card lines across v9 and v10, and completes v10
5
+ card-level pre-modifier totals across all modifier presentation paths. It also
6
+ rejects unsupported v9 flexible-period searches before transport and restores
7
+ empty-query inventory browsing on v9. Existing supported calls remain valid
8
+ without changes.
9
+
10
+ ## 1. Reuse the Shared Discount Output
11
+
12
+ Legacy v9 now uses the same optional shape introduced for v10 in 2.11.0:
13
+
14
+ ```ts
15
+ type SearchDiscount = {
16
+ readonly formattedOriginalTotal: string;
17
+ readonly applied: readonly {
18
+ readonly label: string;
19
+ readonly formattedAmount: string;
20
+ }[];
21
+ };
22
+ ```
23
+
24
+ For exact-period results, read `discount` from the item:
25
+
26
+ ```ts
27
+ result.items[0]?.discount;
28
+ ```
29
+
30
+ For nearby suggestions, read it from the individual alternative period:
31
+
32
+ ```ts
33
+ result.alternatives?.[0]?.alternativePeriods[0]?.discount;
34
+ ```
35
+
36
+ ## 2. Understand the Legacy v9 Mapping
37
+
38
+ For priced v9 results:
39
+
40
+ - `calc.total` remains the final `formattedTotal`.
41
+ - `discount` is present only when `calc.oTotal` is greater than `calc.total`.
42
+ - `discount.formattedOriginalTotal` is formatted from `calc.oTotal`.
43
+ - A non-empty `calc.discountName` becomes the label of the single applied entry.
44
+ - The applied amount is the formatted negative difference
45
+ `calc.total - calc.oTotal`.
46
+ - If the backend supplies no usable discount name, `applied` is empty while the
47
+ formatted original total remains available.
48
+
49
+ The SDK omits `discount` when there is no final total or when the original total is
50
+ not greater than the final total.
51
+
52
+ ## 3. Account for Backend Detail Differences
53
+
54
+ v9 exposes one aggregated discount name and therefore returns at most one
55
+ `discount.applied` entry. v10 search can expose multiple `discount.applied`
56
+ entries. Consumers should render the array without assuming a fixed length.
57
+
58
+ Both backends return presentation-ready localized strings. Backend numeric amounts
59
+ and internal modifier keys are not part of the public output.
60
+
61
+ ## 4. Account for Booking-Card Cleanup
62
+
63
+ Quote booking cards on both backends now omit numeric zero-value lines without
64
+ subitems from the `other` section, matching the existing handling of included and
65
+ tax sections.
66
+
67
+ - A zero-only `other` section is omitted.
68
+ - A mixed `other` section retains its non-zero lines unchanged.
69
+ - Lines with subitems remain available because they still carry descriptive
70
+ content.
71
+
72
+ No required booking-card property was added and the public schema remains
73
+ source-compatible. Runtime output can contain fewer empty rows. Sites that used a
74
+ zero-value row to represent a declined service should use explicit selection or
75
+ service state instead of inferring that state from the price breakdown.
76
+
77
+ ## 5. Render v10 Quote Modifiers
78
+
79
+ On v10, an item at `bookingCardInformation.sections[].lines[].item` can include:
80
+
81
+ ```ts
82
+ {
83
+ modifiers?: readonly {
84
+ label: string;
85
+ amount: string;
86
+ }[];
87
+ totalBeforeModifiers?: string;
88
+ }
89
+ ```
90
+
91
+ The card can also include `bookingCardInformation.totalBeforeModifiers`.
92
+ `appliedCharge` and `total` are the final amounts after modifiers. The
93
+ corresponding `totalBeforeModifiers` is the formatted amount before modifiers and
94
+ is omitted when there is no non-zero net modifier effect. `modifiers` is omitted
95
+ when the item has none; entries with the same localized label are grouped and
96
+ their amounts are summed before formatting.
97
+
98
+ Included, tax, and other lines attach their adjustments to `item.modifiers`.
99
+ Negative modifiers from backend lines that do not map to those sections may
100
+ instead appear as dedicated booking-card lines. In 2.12.0, card-level
101
+ `totalBeforeModifiers` accounts for both forms. v9 quote cards omit these fields.
102
+
103
+ Example for `de-DE`:
104
+
105
+ ```json
106
+ {
107
+ "bookingCardInformation": {
108
+ "sections": [
109
+ {
110
+ "lines": [
111
+ {
112
+ "item": {
113
+ "position": "Inklusivpreis",
114
+ "appliedCharge": "1.554,00 €",
115
+ "modifiers": [
116
+ {
117
+ "label": "Frühbucher/Last-Minute",
118
+ "amount": "-140,00 €"
119
+ }
120
+ ],
121
+ "totalBeforeModifiers": "1.694,00 €"
122
+ }
123
+ }
124
+ ]
125
+ }
126
+ ],
127
+ "total": "1.596,70 €",
128
+ "totalBeforeModifiers": "1.736,70 €"
129
+ }
130
+ }
131
+ ```
132
+
133
+ Do not assume a voucher appears as its own section line. Use
134
+ `quote.voucher.status` for voucher application status and the booking-card fields
135
+ for price presentation. To keep price arithmetic visible, render the pre-modifier
136
+ amount, modifier rows, and final amount separately. Whether to display or strike
137
+ through a reference price remains the consuming site's product and legal
138
+ decision.
139
+
140
+ ## 6. Keep Flexible Search Disabled on v9
141
+
142
+ No configuration or supported request migration is required. Legacy v9 now rejects
143
+ the unsupported flexible-period keys `month`, `dates`, `nights`, and `weekend`
144
+ before transport instead of silently returning an unpriced item list. Keep
145
+ flexible search unavailable on v9 sites and use fixed `start`/`end` period
146
+ queries. v10 flexible search is unchanged.
147
+
148
+ The rejection is a `CoreSDKError` with operation `v9.search` and occurs before a
149
+ GraphQL request is sent. The public `SearchSearchInput` type remains unchanged
150
+ because its `query` field is an opaque query string.
151
+
152
+ This release enriches v9 search output when its existing response contains
153
+ sufficient discount data, removes empty quote breakdown rows from both backends,
154
+ and can return the existing optional card-level `totalBeforeModifiers` in
155
+ additional v10 cases.
156
+
157
+ ## 7. Remove Dummy Occupancy from Empty v9 Searches
158
+
159
+ Legacy v9 now sends an empty backend-filter set as GraphQL `data: null` instead of
160
+ `data: []`. An empty query can therefore browse the first page of searchable
161
+ inventory under the configured rental scope:
162
+
163
+ ```ts
164
+ await sdk.live.search.search({
165
+ locale: "de-DE",
166
+ query: "",
167
+ });
168
+ ```
169
+
170
+ Remove workarounds that inject `adults=1` only to make an empty query return
171
+ results. Real occupancy, period, and non-empty backend filters keep their existing
172
+ mapping. Pagination and `limit` still apply, and an undated result does not assert
173
+ availability for a particular stay.
174
+
175
+ ## Recommended Verification
176
+
177
+ 1. Run a priced v9 exact-period search where the original total exceeds the final
178
+ total and verify the formatted values and label.
179
+ 2. Verify discounted v9 alternative periods independently.
180
+ 3. Run a v9 search without a discount and confirm existing rendering is unchanged.
181
+ 4. Verify a missing v9 discount name produces an empty `applied` array.
182
+ 5. Verify v9 rejects `month`, `dates`, `nights`, and `weekend` before transport.
183
+ 6. Run an empty v9 query and confirm it returns searchable inventory without a
184
+ dummy occupancy value.
185
+ 7. Recheck a discounted v10 search and confirm its existing multi-entry behavior
186
+ remains unchanged.
187
+ 8. Quote a v9 and v10 rental with a zero-value `other` service and confirm the
188
+ empty section is absent.
189
+ 9. Confirm a mixed `other` section retains only its non-zero lines.
190
+ 10. Quote a discounted v10 rental and render `item.modifiers`, item-level
191
+ `totalBeforeModifiers`, and card-level `totalBeforeModifiers`.
192
+ 11. Confirm a v9 quote continues to omit the modifier fields.
193
+ 12. Verify a v10 modifier rendered as a dedicated line still contributes to the
194
+ card-level `totalBeforeModifiers`.
@@ -0,0 +1,87 @@
1
+ # Changelog: 2.13.0
2
+
3
+ This release adds static backend search capability discovery and replaces the
4
+ ambiguous flat booking payment schedule with a shared, presentation-ready payment
5
+ model for v9 and v10. It preserves every known remaining payment step, separates
6
+ contractual steps from full-payment alternatives, and keeps backend credentials
7
+ out of the public booking output.
8
+
9
+ ## Added
10
+
11
+ - `@v-office/website-sdk/capabilities/search` exposes pure, instance-independent
12
+ helpers for checking flexible-period support and the supported period query keys
13
+ for a backend.
14
+ - `BookingOutput.payment.schedule` contains the remaining contractual payment
15
+ steps known after booking.
16
+ - `BookingOutput.payment.alternatives` contains optional ways to settle the
17
+ complete outstanding balance.
18
+ - Schedule entries have a stable `kind`: `prepayment`, `remaining_payment`,
19
+ `installment`, or `deposit`.
20
+ - Full-payment alternatives use `kind: "full_amount"`.
21
+ - v10 booking output includes all remaining online, on-site, and external payment
22
+ steps instead of exposing only the next online step.
23
+ - v10 schedule entries can include translated `paymentAt` text derived from the
24
+ backend payment location and recipient.
25
+ - Legacy v9 now preserves its independently supplied remaining-payment amount and
26
+ deadline.
27
+
28
+ ## Changed
29
+
30
+ - `dueOn` is now a complete translated, long-form display string such as
31
+ `Zahlbar bis zum 6. Februar 2027`.
32
+ - Schedule and alternative amounts use the `amount` field and remain
33
+ locale-formatted display strings.
34
+ - `paymentOptions` is present on a schedule entry only when the SDK can currently
35
+ perform that payment. Informational future, on-site, and external steps remain
36
+ visible without payment options.
37
+ - A synthesized v9 full-payment alternative no longer copies the prepayment
38
+ deadline. An explicit backend total deadline can still be retained.
39
+ - v10 initializes Adyen only for the next payable online step and the
40
+ full-outstanding alternative.
41
+ - v9 initializes Stripe only for currently payable choices.
42
+ - Post-booking insurance now receives the opaque booking result:
43
+
44
+ ```ts
45
+ await sdk.live.quote.bookInsurance({ quote, booking });
46
+ ```
47
+
48
+ ## Removed
49
+
50
+ - `BookingOutput.paymentSchedules`
51
+ - Public `BookingOutput.guestToken`
52
+ - ISO-like or locale-short date assumptions for booking payment output
53
+
54
+ The private v9 guest token remains attached to the live opaque booking object and
55
+ is used internally when completing insurance. It is not enumerable or
56
+ serializable.
57
+
58
+ ## Backend Mapping
59
+
60
+ - v9 `prepayment` becomes `kind: "prepayment"`.
61
+ - v9 `rest` becomes `kind: "remaining_payment"`.
62
+ - v9 `total` becomes a `full_amount` alternative.
63
+ - v10 `INSTALLMENT` becomes `kind: "installment"`.
64
+ - v10 `DEPOSIT` becomes `kind: "deposit"`.
65
+ - v10 `financials.outstanding` becomes a `full_amount` alternative when it is a
66
+ distinct payable choice.
67
+
68
+ ## Migration Impact
69
+
70
+ This is a breaking output and insurance-input change. Consumers must:
71
+
72
+ 1. Replace reads of `paymentSchedules` with `payment.schedule` and
73
+ `payment.alternatives`.
74
+ 2. Read `amount` instead of the former schedule `total`.
75
+ 3. Treat schedule `paymentOptions` as optional.
76
+ 4. Render `dueOn`, `paymentAt`, labels, amounts, and line amounts directly without
77
+ reparsing or reformatting them.
78
+ 5. Pass the live `booking` object to `bookInsurance` instead of passing
79
+ `bookingNumber` and `guestToken`.
80
+ 6. Keep the booking object in memory until optional v9 insurance booking is
81
+ complete; private credentials do not survive JSON serialization.
82
+
83
+ The search capability API is additive. Consumers with hardcoded backend period-key
84
+ tables can replace them with the new helpers.
85
+
86
+ See `versions/2.13.0/MIGRATION.md`, `../../search.md`, `../../booking.md`, and
87
+ `../../quote.md`.
@@ -0,0 +1,315 @@
1
+ # Migration: 2.12.0 to 2.13.0
2
+
3
+ 2.13.0 introduces a breaking, shared booking-payment output for v9 and v10. The
4
+ new model is intentionally presentation-oriented: labels, amounts, deadlines,
5
+ payment locations, and breakdown lines are already translated and formatted for
6
+ the booking locale.
7
+
8
+ ## 1. Replace the Flat Payment Schedule
9
+
10
+ Before 2.13.0:
11
+
12
+ ```ts
13
+ booking.paymentSchedules;
14
+ ```
15
+
16
+ From 2.13.0:
17
+
18
+ ```ts
19
+ booking.payment.schedule;
20
+ booking.payment.alternatives;
21
+ ```
22
+
23
+ `payment.schedule` contains contractual payment steps. `payment.alternatives`
24
+ contains distinct alternatives such as paying the complete outstanding balance.
25
+
26
+ Example:
27
+
28
+ ```json
29
+ {
30
+ "bookingNumber": "B-2026-0001",
31
+ "payment": {
32
+ "schedule": [
33
+ {
34
+ "kind": "prepayment",
35
+ "label": "Anzahlung",
36
+ "amount": "76,00 €",
37
+ "dueOn": "Zahlbar bis zum 19. August 2026",
38
+ "paymentOptions": [
39
+ {
40
+ "kind": "bank_transfer",
41
+ "label": "Überweisung",
42
+ "holder": "Ferienvermietung Muster GmbH",
43
+ "iban": "DE02 1203 0000 0000 2020 51",
44
+ "remittanceText": "Buchungsnummer: B-2026-0001"
45
+ }
46
+ ]
47
+ },
48
+ {
49
+ "kind": "remaining_payment",
50
+ "label": "Restzahlung",
51
+ "amount": "304,00 €",
52
+ "dueOn": "Zahlbar bis zum 6. Februar 2027"
53
+ }
54
+ ],
55
+ "alternatives": [
56
+ {
57
+ "kind": "full_amount",
58
+ "label": "Gesamtzahlung",
59
+ "amount": "380,00 €",
60
+ "paymentOptions": [
61
+ {
62
+ "kind": "bank_transfer",
63
+ "label": "Überweisung",
64
+ "holder": "Ferienvermietung Muster GmbH",
65
+ "iban": "DE02 1203 0000 0000 2020 51",
66
+ "remittanceText": "Buchungsnummer: B-2026-0001"
67
+ }
68
+ ]
69
+ }
70
+ ]
71
+ }
72
+ }
73
+ ```
74
+
75
+ ## 2. Read `amount` Instead of `total`
76
+
77
+ The old schedule-level `total` field has been renamed to `amount`:
78
+
79
+ ```ts
80
+ for (const entry of booking.payment.schedule) {
81
+ renderAmount(entry.amount);
82
+ }
83
+ ```
84
+
85
+ The value remains a localized display string. Do not parse it for arithmetic or
86
+ payment initiation.
87
+
88
+ Optional `lines` continue to contain translated labels and formatted amounts:
89
+
90
+ ```ts
91
+ for (const line of entry.lines ?? []) {
92
+ renderBreakdownLine(line.label, line.amount);
93
+ }
94
+ ```
95
+
96
+ ## 3. Render Schedule and Alternative Entries
97
+
98
+ To render every payment entry:
99
+
100
+ ```ts
101
+ const paymentEntries = [...booking.payment.schedule, ...booking.payment.alternatives];
102
+
103
+ for (const entry of paymentEntries) {
104
+ renderPaymentEntry(entry);
105
+ }
106
+ ```
107
+
108
+ Use the stable `kind` only for programmatic behavior. Use `label` for visible
109
+ text.
110
+
111
+ Schedule kinds:
112
+
113
+ - `prepayment`: a named v9 down payment.
114
+ - `remaining_payment`: a named v9 remaining payment.
115
+ - `installment`: a v10 installment step.
116
+ - `deposit`: a v10 deposit step.
117
+
118
+ Alternative kinds:
119
+
120
+ - `full_amount`: settle the complete currently outstanding balance.
121
+
122
+ Do not infer v9 semantics from v10 step order. A v10 installment is not
123
+ automatically a prepayment or remaining payment.
124
+
125
+ ## 4. Treat `paymentOptions` as Capability Data
126
+
127
+ `paymentOptions` is optional on scheduled steps. Its absence means the entry is
128
+ informational and is not currently payable through the SDK.
129
+
130
+ ```ts
131
+ for (const option of entry.paymentOptions ?? []) {
132
+ if (option.kind === "bank_transfer") {
133
+ renderBankTransfer(option);
134
+ } else {
135
+ renderSubmitButton(() => sdk.live.booking.submitPaymentOption(option));
136
+ }
137
+ }
138
+ ```
139
+
140
+ Do not attach payment buttons to future, on-site, or external entries merely
141
+ because they appear in `payment.schedule`.
142
+
143
+ `full_amount` alternatives always contain a `paymentOptions` array. The array can
144
+ be empty when the backend supplies no executable method.
145
+
146
+ ## 5. Render Dates and Payment Locations Directly
147
+
148
+ Before 2.13.0, `dueOn` was locale-short text even though older documentation
149
+ showed an ISO value. Consumers sometimes attempted to parse it as
150
+ `YYYY-MM-DD`.
151
+
152
+ From 2.13.0, `dueOn` is a complete localized phrase:
153
+
154
+ ```text
155
+ Zahlbar bis zum 6. Februar 2027
156
+ Payable until February 6, 2027
157
+ ```
158
+
159
+ Render it directly:
160
+
161
+ ```ts
162
+ if (entry.dueOn) renderSupportingText(entry.dueOn);
163
+ if (entry.paymentAt) renderSupportingText(entry.paymentAt);
164
+ ```
165
+
166
+ Do not prepend another due-date label and do not parse or reformat these fields.
167
+ `paymentAt` is currently populated by v10 when payment-step location information
168
+ is available.
169
+
170
+ ## 6. Account for the Corrected v9 Schedule
171
+
172
+ Legacy v9 now emits both authoritative contractual steps:
173
+
174
+ - `prepayment` with its own amount and deadline.
175
+ - `remaining_payment` with its own amount and deadline.
176
+
177
+ The `full_amount` alternative is separate. When the SDK synthesizes it from
178
+ prepayment plus remaining payment, it has no deadline. The SDK no longer copies
179
+ the prepayment deadline onto that alternative.
180
+
181
+ Consequently, code must not derive a remaining-payment deadline from
182
+ `payment.alternatives`.
183
+
184
+ ## 7. Account for the Expanded v10 Schedule
185
+
186
+ v10 now preserves all remaining backend payment steps in
187
+ `payment.schedule`, including:
188
+
189
+ - `ONLINE`
190
+ - `ON_SITE`
191
+ - `EXTERN`
192
+
193
+ Only the next eligible online step receives an initialized Adyen option. Later
194
+ steps remain available as informational entries. The complete outstanding amount
195
+ can appear separately under `payment.alternatives`.
196
+
197
+ The alternative amount is the current outstanding balance, not necessarily the
198
+ original booking total.
199
+
200
+ ## 8. Migrate Post-Booking Insurance
201
+
202
+ Before 2.13.0:
203
+
204
+ ```ts
205
+ await sdk.live.quote.bookInsurance({
206
+ quote,
207
+ bookingNumber: booking.bookingNumber,
208
+ guestToken: booking.guestToken,
209
+ });
210
+ ```
211
+
212
+ From 2.13.0:
213
+
214
+ ```ts
215
+ await sdk.live.quote.bookInsurance({
216
+ quote,
217
+ booking,
218
+ });
219
+ ```
220
+
221
+ `guestToken` is no longer public. For v9, it is stored as private state on the
222
+ opaque booking object and consumed internally by the SDK.
223
+
224
+ Keep the same live booking object until insurance booking is complete:
225
+
226
+ ```ts
227
+ const booking = await sdk.live.booking.book(input);
228
+
229
+ // Do not JSON round-trip `booking` before this call.
230
+ const insurance = await sdk.live.quote.bookInsurance({ quote, booking });
231
+ ```
232
+
233
+ Spreading, serializing, reconstructing, or transferring the booking object loses
234
+ its private backend context. If the application must cross a page reload or
235
+ process boundary, complete insurance before that boundary.
236
+
237
+ ## 9. Update Payment-Option Selection
238
+
239
+ Before 2.13.0:
240
+
241
+ ```ts
242
+ const option = booking.paymentSchedules[0]?.paymentOptions.find(
243
+ (candidate) => candidate.kind !== "bank_transfer",
244
+ );
245
+ ```
246
+
247
+ From 2.13.0:
248
+
249
+ ```ts
250
+ const option = [...booking.payment.schedule, ...booking.payment.alternatives]
251
+ .flatMap((entry) => entry.paymentOptions ?? [])
252
+ .find((candidate) => candidate.kind !== "bank_transfer");
253
+ ```
254
+
255
+ Continue passing the selected structured option unchanged to
256
+ `submitPaymentOption`.
257
+
258
+ ## 10. Handle Optional Insurance Output
259
+
260
+ `insurancePaymentOptions` is omitted when no post-booking insurance payment is
261
+ available:
262
+
263
+ ```ts
264
+ for (const option of booking.insurancePaymentOptions ?? []) {
265
+ renderInsurancePaymentOption(option);
266
+ }
267
+ ```
268
+
269
+ This keeps v10 and bookings without selected insurance minimal.
270
+
271
+ ## 11. Replace Hardcoded Search Capability Tables
272
+
273
+ This migration is optional and additive. Search UI that needs to decide which
274
+ period controls to render can query backend support without constructing an SDK:
275
+
276
+ ```ts
277
+ import {
278
+ getSupportedPeriodQueryKeys,
279
+ supportsFlexiblePeriodSearch,
280
+ type BackendKind,
281
+ } from "@v-office/website-sdk/capabilities/search";
282
+
283
+ const shouldRenderFlexiblePeriod = (backend: BackendKind) => supportsFlexiblePeriodSearch(backend);
284
+
285
+ const supportedPeriodKeys = getSupportedPeriodQueryKeys("v9");
286
+ supportedPeriodKeys.has("month"); // false
287
+ ```
288
+
289
+ Remove local v9/v10 period-key tables in favor of these helpers. Both helpers are
290
+ derived from the same core definitions used by SDK search parsing, so backend
291
+ support changes do not require consumers to update a copied matrix.
292
+
293
+ The returned `ReadonlySet` includes supported aliases such as `from`, `till`,
294
+ `nights_min`, and `nights_max`. It describes individual key support; normal
295
+ search validation still determines whether a complete combination of keys and
296
+ values is executable.
297
+
298
+ ## Recommended Verification
299
+
300
+ 1. Book a v9 reservation with distinct prepayment and remaining-payment dates.
301
+ 2. Confirm both entries retain their own translated long-form deadline.
302
+ 3. Confirm a synthesized v9 `full_amount` alternative has no deadline.
303
+ 4. Confirm future v9 remaining payment has no payment button unless the SDK
304
+ supplies a payment option.
305
+ 5. Book a v10 reservation with multiple online and on-site/external payment steps.
306
+ 6. Confirm every remaining v10 step is rendered in backend order.
307
+ 7. Confirm only the next payable online step has an Adyen option.
308
+ 8. Confirm the full-payment alternative displays the outstanding balance without
309
+ an inferred deadline.
310
+ 9. Render both supported locales and verify all amounts, dates, locations, labels,
311
+ and breakdown lines are presentation-ready.
312
+ 10. Complete v9 insurance with the live booking object and verify the private
313
+ credential is not present in serialized output.
314
+ 11. If replacing a local search capability table, confirm flexible-period controls
315
+ remain hidden for v9 and visible for v10.
@@ -124,6 +124,9 @@ The public output type is unchanged, but v10 search cards now reflect the REST p
124
124
  Starting with 2.11.0, priced v10 items and alternative periods may also include
125
125
  optional localized `discount` details. See `../../search.md`.
126
126
 
127
+ Starting with 2.12.0, legacy v9 maps the same optional shape from its aggregated
128
+ original total and discount name when the original total exceeds the final total.
129
+
127
130
  ## Backend Behavior
128
131
 
129
132
  - Fixed-period and flexible searches are priced by the REST backend; occupancy is required for priced searches.
@@ -241,12 +241,12 @@ const toSections = ({ input, guestQuoteResult, locale, translations }) => Effect
241
241
  locale,
242
242
  translations
243
243
  });
244
- const sectionsOther = yield* toSectionsOther({
244
+ const sectionsOtherWithContent = (yield* toSectionsOther({
245
245
  input,
246
246
  guestQuoteResult,
247
247
  locale,
248
248
  translations
249
- });
249
+ })).filter(hasSectionLineContent);
250
250
  if (hasSectionLineContent(sectionsIncluded)) sections.push({
251
251
  lines: [sectionsIncluded],
252
252
  displayOrder: GuestQuoteBookingCardDisplayOrder.inclusive
@@ -255,8 +255,8 @@ const toSections = ({ input, guestQuoteResult, locale, translations }) => Effect
255
255
  lines: [sectionsTax],
256
256
  displayOrder: GuestQuoteBookingCardDisplayOrder.tax
257
257
  });
258
- if (sectionsOther.length > 0) sections.push({
259
- lines: sectionsOther,
258
+ if (sectionsOtherWithContent.length > 0) sections.push({
259
+ lines: sectionsOtherWithContent,
260
260
  displayOrder: GuestQuoteBookingCardDisplayOrder.other
261
261
  });
262
262
  return sections;
@@ -1,4 +1,4 @@
1
- import { c as VofficeUnitDataFieldSchemas, d as VofficeUnitDataPropertyMetadata, f as parseVofficeFacilityData, i as toLocalizedString, l as VofficeUnitDataPropertyCategoryLabelKeys, m as parseVofficeUnitData, n as toImages, o as rentalsAllFeedbacksQuery, p as parseVofficeFeedbackData, r as toAddress, s as rentalsAllWithoutFeedbacksQuery, t as toRentalHighlights, u as VofficeUnitDataPropertyCategoryValues } from "./to-rental-highlights-B6a_fi0j.mjs";
1
+ import { c as VofficeUnitDataFieldSchemas, d as VofficeUnitDataPropertyMetadata, f as parseVofficeFacilityData, i as toLocalizedString, l as VofficeUnitDataPropertyCategoryLabelKeys, m as parseVofficeUnitData, n as toImages, o as rentalsAllFeedbacksQuery, p as parseVofficeFeedbackData, r as toAddress, s as rentalsAllWithoutFeedbacksQuery, t as toRentalHighlights, u as VofficeUnitDataPropertyCategoryValues } from "./to-rental-highlights-BVikT4Iz.mjs";
2
2
  import { makeTranslate, renderLabeledAttributeValue, renderResolvedCustomAttribute, toCustomAttributeList, toResolvedCustomAttributeCategoryTranslationKey } from "@v-office/sdk-core";
3
3
  import { Effect } from "effect";
4
4
  //#region src/legacy-v9/parser/rentals/to-description.ts
@@ -91,9 +91,10 @@ const toRentalAttributes = ({ locale, translations, attributes, customAttributes
91
91
  for (const { key, metadata } of PROPERTY_METADATA_ENTRIES) {
92
92
  const value = knownAttributes[key];
93
93
  if (!canRenderLabeledAttributeValue$1(value)) continue;
94
+ const label = yield* translate(metadata.labelKey, key);
94
95
  const attribute = renderLabeledAttributeValue({
95
96
  key,
96
- label: yield* translate(metadata.labelKey, key),
97
+ label,
97
98
  locale,
98
99
  value
99
100
  });
@@ -154,9 +155,10 @@ const toRentalVicinity = ({ locale, translations, attributes }) => Effect.gen(fu
154
155
  for (const { key, metadata } of LOCATION_METADATA_ENTRIES) {
155
156
  const value = knownAttributes[key];
156
157
  if (!canRenderLabeledAttributeValue(value)) continue;
158
+ const label = yield* translate(metadata.labelKey, key);
157
159
  const item = renderLabeledAttributeValue({
158
160
  key,
159
- label: yield* translate(metadata.labelKey, key),
161
+ label,
160
162
  locale,
161
163
  value
162
164
  });
@@ -200,7 +202,8 @@ const toReviews = ({ locale, translations, rental }) => Effect.gen(function* ()
200
202
  }];
201
203
  }) ?? [];
202
204
  if (parsedItems.length === 0) return void 0;
203
- const summaryRating = roundRatingUp(parsedItems.reduce((ratingSum, item) => ratingSum + item.rating, 0) / parsedItems.length);
205
+ const averageRating = parsedItems.reduce((ratingSum, item) => ratingSum + item.rating, 0) / parsedItems.length;
206
+ const summaryRating = roundRatingUp(averageRating);
204
207
  const summaryRatingTemplate = yield* translations.translate(locale, "reviews.summary.rating");
205
208
  const summaryCountTemplate = yield* translations.translate(locale, "reviews.summary.count");
206
209
  const classification = toClassification(summaryRating);