@v-office/website-sdk 2.17.1 → 2.18.1

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 (44) hide show
  1. package/README.md +18 -0
  2. package/dist/cli.mjs +10 -11
  3. package/dist/{client-yYl7H82t.mjs → client-BnA0oXd_.mjs} +220 -23
  4. package/dist/index.d.mts +48 -2
  5. package/dist/index.mjs +2 -2
  6. package/dist/instructions/CHANGELOG.md +26 -27
  7. package/dist/instructions/MIGRATION.md +26 -27
  8. package/dist/instructions/README.md +7 -2
  9. package/dist/instructions/availability.md +9 -7
  10. package/dist/instructions/creation.md +46 -1
  11. package/dist/instructions/custom-attributes.md +7 -0
  12. package/dist/instructions/quote.md +8 -0
  13. package/dist/instructions/rentals.md +130 -2
  14. package/dist/instructions/versions/2.18.0/CHANGELOG.md +133 -0
  15. package/dist/instructions/versions/2.18.0/MIGRATION.md +364 -0
  16. package/dist/instructions/versions/2.18.1/CHANGELOG.md +16 -0
  17. package/dist/instructions/versions/2.18.1/MIGRATION.md +12 -0
  18. package/dist/{quote-C-Bs3XTE.mjs → quote-cjFdLn0m.mjs} +1 -0
  19. package/dist/{rentals-BokBV-ze.mjs → rentals-GfJVIv7i.mjs} +205 -12
  20. package/dist/{search-DkXUHJt8.mjs → search-CR1UjnmR.mjs} +3 -2
  21. package/dist/to-rental-highlights-CJvDxSLw.mjs +3035 -0
  22. package/dist/translations/v10/de-DE/core.json +1 -1
  23. package/dist/translations/v9/de-DE/core.json +7 -1
  24. package/dist/translations/v9/en-US/core.json +7 -1
  25. package/dist/{to-rental-highlights-BAsEuxoy.mjs → v9-data-BnYxa2Ek.mjs} +21 -2435
  26. package/instructions/CHANGELOG.md +26 -27
  27. package/instructions/MIGRATION.md +26 -27
  28. package/instructions/README.md +7 -2
  29. package/instructions/availability.md +9 -7
  30. package/instructions/creation.md +46 -1
  31. package/instructions/custom-attributes.md +7 -0
  32. package/instructions/quote.md +8 -0
  33. package/instructions/rentals.md +130 -2
  34. package/instructions/versions/2.18.0/CHANGELOG.md +133 -0
  35. package/instructions/versions/2.18.0/MIGRATION.md +364 -0
  36. package/instructions/versions/2.18.0/release.json +3 -0
  37. package/instructions/versions/2.18.1/CHANGELOG.md +16 -0
  38. package/instructions/versions/2.18.1/MIGRATION.md +12 -0
  39. package/instructions/versions/2.18.1/release.json +3 -0
  40. package/package.json +8 -7
  41. package/dist/instructions/versions/2.6.0/CHANGELOG.md +0 -6
  42. package/dist/instructions/versions/2.6.0/MIGRATION.md +0 -6
  43. package/instructions/versions/2.6.0/CHANGELOG.md +0 -6
  44. package/instructions/versions/2.6.0/MIGRATION.md +0 -6
@@ -0,0 +1,364 @@
1
+ # Migration: 2.17.1 to 2.18.0
2
+
3
+ 2.18.0 adds an opt-in legacy-v9 facility association for tenants whose
4
+ facilities reference vOffice object groups through a custom `p_*` data
5
+ attribute. It also exposes initial-availability calendar prices on available
6
+ dates that cannot be used as a start date, and combines repeated included
7
+ rent rows when a quoted stay crosses seasonal rates.
8
+
9
+ The default facility association and the public static rental result remain
10
+ unchanged when the new config field is omitted. Public availability and quote
11
+ types are unchanged.
12
+
13
+ ## 1. Upgrade
14
+
15
+ ```sh
16
+ pnpm add @v-office/website-sdk@2.18.0
17
+ ```
18
+
19
+ ## 2. Keep the Default Relation When Available
20
+
21
+ No configuration change is required when vOffice units already carry their
22
+ native facility relation:
23
+
24
+ ```ts
25
+ const sdk = createWebsiteSDK({
26
+ config: {
27
+ backend: "v9",
28
+ graphqlUrl: "https://example.com/graphql",
29
+ apiKey: "...",
30
+ imageProxyBaseUrl: "https://images.example.com",
31
+ },
32
+ });
33
+ ```
34
+
35
+ This continues to load the nested `voffice_facility` for each unit. It does not
36
+ request top-level facilities or object-group memberships.
37
+
38
+ ## 3. Enable Object-Group Association When Required
39
+
40
+ Set the numeric suffix of the facility custom attribute that contains the
41
+ related object-group ID or IDs:
42
+
43
+ ```ts
44
+ const sdk = createWebsiteSDK({
45
+ config: {
46
+ backend: "v9",
47
+ graphqlUrl: "https://example.com/graphql",
48
+ apiKey: "...",
49
+ imageProxyBaseUrl: "https://images.example.com",
50
+ facilityObjectGroupRelationAttributeId: 18962,
51
+ },
52
+ });
53
+ ```
54
+
55
+ The SDK converts `18962` to the GraphQL facility data attribute `p_18962`.
56
+ Do not pass `"p_18962"` or another string; the config value is a positive
57
+ integer.
58
+
59
+ When enabled, `getRentals`:
60
+
61
+ 1. Loads all units with `voffice_groups { voffice_group_id }`.
62
+ 2. Loads all facilities and requests `p_18962` alongside the normal facility
63
+ attributes.
64
+ 3. Associates each unit with the facility whose configured object-group IDs
65
+ contain one of the unit's group memberships.
66
+ 4. Exposes the resolved facility through the existing `rental.property`.
67
+ 5. Applies static rental scope and then loads feedbacks for retained rentals.
68
+
69
+ ### Expanded v9 property fields
70
+
71
+ Both native and object-group v9 facility relations now populate more of the
72
+ existing `rental.property` shape when the Hub provides the source data:
73
+
74
+ - `propertyType` from `type`
75
+ - `location` from the GeoJSON `loc`
76
+ - `address` from `address`, with `regionName` used as `province`
77
+ - all existing description components from their corresponding localized
78
+ facility fields
79
+ - `attributes` for `allergic`, `childrenWelcome`, `pets`, and `youthgroups`
80
+
81
+ `HOUSE_FAC` becomes `HOLIDAY_HOME_COMPLEX` and `FLAT_FAC` becomes
82
+ `APARTMENT_COMPLEX`. `HOTEL`, `HOSTEL`, `FARM`, and `CAMPGROUND` already match
83
+ the shared values. The normalized value is localized through
84
+ `Property.propertyType.option.*` before it is returned. Unknown facility types
85
+ are omitted.
86
+
87
+ This uses the existing core `RentalPropertySchema`; no public property field or
88
+ type was added. Missing, empty, zero-valued, or malformed optional source data
89
+ remains omitted. The release does not synthesize property `vicinity` from
90
+ unrelated facility fields.
91
+
92
+ To select and order v9 property highlights, use the existing website option:
93
+
94
+ ```ts
95
+ const sdk = createWebsiteSDK({
96
+ config,
97
+ options: {
98
+ rentalPropertyHighlightPrioritization: ["childrenWelcome", "pets", "youthgroups"],
99
+ },
100
+ });
101
+ ```
102
+
103
+ The v9 renderer supports the typed facility attributes `allergic`,
104
+ `childrenWelcome`, `pets`, and `youthgroups`. It preserves the configured order,
105
+ deduplicates keys, and ignores unsupported, missing, or non-renderable values.
106
+ Rental custom attributes are not considered for property highlights.
107
+
108
+ ## 4. Add Tenant-Specific Facility Highlight Text
109
+
110
+ Use a v9 property-highlight source when a facility custom field contains
111
+ editor-authored highlight lines, such as `p_18963` ("Anlagen Highlights"):
112
+
113
+ ```ts
114
+ import {
115
+ createWebsiteSDK,
116
+ defineV9PropertyHighlightSources,
117
+ defineWebsiteSDKOptions,
118
+ } from "@v-office/website-sdk";
119
+
120
+ const v9PropertyHighlightSources = defineV9PropertyHighlightSources({
121
+ facilityHighlights: {
122
+ v9: "p_18963",
123
+ type: "line-list",
124
+ },
125
+ });
126
+
127
+ const sdk = createWebsiteSDK({
128
+ config,
129
+ options: defineWebsiteSDKOptions({
130
+ v9PropertyHighlightSources,
131
+ rentalPropertyHighlightPrioritization: [
132
+ "childrenWelcome",
133
+ v9PropertyHighlightSources.keys.facilityHighlights,
134
+ "pets",
135
+ ],
136
+ }),
137
+ });
138
+ ```
139
+
140
+ The registry gives the tenant-specific field a stable public selection key.
141
+ Defining a source alone does not request or emit it; add the generated key to
142
+ `rentalPropertyHighlightPrioritization`. The selected `p_*` field is requested
143
+ for both native and object-group facility relations.
144
+
145
+ Source keys must not reuse the built-in v9 facility keys `allergic`,
146
+ `childrenWelcome`, `pets`, or `youthgroups`; registry creation rejects those
147
+ collisions.
148
+
149
+ `line-list` resolves the localized field value, splits it at Unix or Windows
150
+ newlines, trims each line, drops empty lines, and deduplicates exact visible
151
+ values while preserving order. Each remaining line becomes one
152
+ `property.highlights` item at the source key's position. Missing and `null`
153
+ values emit nothing. Semicolons remain text; use one highlight per line in
154
+ vOffice.
155
+
156
+ For CLI JSON, provide the authored source definitions directly:
157
+
158
+ ```json
159
+ {
160
+ "v9PropertyHighlightSources": {
161
+ "facilityHighlights": {
162
+ "v9": "p_18963",
163
+ "type": "line-list"
164
+ }
165
+ },
166
+ "rentalPropertyHighlightPrioritization": ["childrenWelcome", "facilityHighlights", "pets"]
167
+ }
168
+ ```
169
+
170
+ The CLI builds and validates the registry. TypeScript consumers should use
171
+ `defineV9PropertyHighlightSources` so `keys.facilityHighlights` is inferred and
172
+ checked.
173
+
174
+ ## 5. Prepare the GraphQL Hub
175
+
176
+ Before enabling the config, verify that the same API key can query:
177
+
178
+ ```graphql
179
+ query FacilityObjectGroupRelations {
180
+ facilities: voffice_facilities(language: "de") {
181
+ id
182
+ name
183
+ data(attributes: ["p_18962", "p_18963"])
184
+ }
185
+
186
+ all(language: "de") {
187
+ voffice_id
188
+ voffice_groups {
189
+ voffice_group_id
190
+ }
191
+ }
192
+ }
193
+ ```
194
+
195
+ The Hub must return:
196
+
197
+ - the required facilities from `voffice_facilities`
198
+ - a usable relation value in the configured `p_*` attribute
199
+ - any selected facility highlight `p_*` values
200
+ - matching `voffice_group_id` values on the intended units
201
+
202
+ Do not enable the mode while those resources are only available through v0 or
203
+ v1 and have not yet synchronized to GraphQL.
204
+
205
+ ## 6. Account for Relation Validation
206
+
207
+ The relation attribute can contain positive numeric IDs as numbers, numeric
208
+ strings, localized string records, arrays, or strings separated by whitespace,
209
+ commas, or semicolons.
210
+
211
+ The static request fails rather than selecting an arbitrary property when:
212
+
213
+ - no facility has a usable configured relation value
214
+ - a non-empty relation value cannot be interpreted as positive integer IDs
215
+ - two facilities claim the same object-group ID
216
+ - one unit matches object groups belonging to different facilities
217
+
218
+ One facility may reference several groups. One unit may match several groups
219
+ when all of them resolve to the same facility.
220
+
221
+ Facilities whose relation is empty contribute no association. Rentals with no
222
+ matching relation remain in the result and omit `property`. Compatibility mode
223
+ does not fall back to the native facility relation.
224
+
225
+ ## 7. Account for Batched Catalogue Requests
226
+
227
+ Legacy-v9 static rentals now load a small ordered rental-ID list first. Complete
228
+ rental details and feedbacks are then requested sequentially in batches of 128
229
+ IDs. Object-group mode loads the facility list once.
230
+
231
+ The public result and rental order are unchanged. HTTP mocks, request counters,
232
+ and GraphQL tracing that expected one detail query and one feedback query must
233
+ allow multiple requests for catalogues larger than 128 rentals.
234
+
235
+ Do not combine the detail batches into GraphQL aliases in one HTTP request. The
236
+ separate requests are intentional because they bound the amount of data the
237
+ Laravel/PHP Hub must construct and serialize at once.
238
+
239
+ ## 8. Keep Other APIs Unchanged
240
+
241
+ The facility-association config only changes legacy-v9 static `getRentals`.
242
+ It does not change:
243
+
244
+ - v9 live search or its `falicityid` rental-scope filter
245
+ - quote inputs, booking, or contact requests
246
+ - start-date-selected availability
247
+ - shared core rental types
248
+
249
+ If an application uses both static rental scope and live search, verify both
250
+ flows separately because object-group association is intentionally limited to
251
+ the static catalogue.
252
+
253
+ ## 9. Render Prices on Available Non-Start Dates
254
+
255
+ `getInitialAvailability` can now return a non-null `formattedPrice` on
256
+ available dates whose status is `check_in_not_allowed` or
257
+ `no_valid_check_out_from_start`:
258
+
259
+ ```ts
260
+ const availability = await sdk.live.availability.getInitialAvailability(input);
261
+
262
+ for (const [date, day] of Object.entries(availability.calendarDays)) {
263
+ renderCalendarDay({
264
+ date,
265
+ status: day.status,
266
+ price: day.formattedPrice,
267
+ });
268
+ }
269
+ ```
270
+
271
+ `formattedPrice` is already localized for the input locale. Render it
272
+ directly when non-null. Do not treat a missing price as the only signal that
273
+ a date is unavailable; use `isAvailableDate` and `status`.
274
+
275
+ The field remains `null` when:
276
+
277
+ - the calendar marks the day unavailable
278
+ - the source supplies no numeric price
279
+
280
+ Start-date-selected availability still has no calendar prices. The v10 change
281
+ is delivered by `@v-office/sdk-core`. Refresh the lockfile when upgrading the
282
+ website SDK so it resolves the coordinated core version shipped for 2.18.0.
283
+
284
+ ## 10. Render Combined Seasonal Rent Lines
285
+
286
+ Included booking-card subitems that share the same localized `position` and
287
+ `appliedCharge` are now one row on both v9 and v10. A stay that crosses
288
+ seasonal rental prices therefore shows a single rent line such as `Miete` /
289
+ `Inklusive` instead of repeating that row once per rate period.
290
+
291
+ Cleaning, linen, booking fees, and other distinct included labels remain
292
+ separate. The inclusive total still sums every backend rent period; only the
293
+ repeated display rows are combined. Quote inputs and the booking-card schema
294
+ are unchanged.
295
+
296
+ Render `subItems` as returned. Do not reconstruct a seasonal breakdown from
297
+ duplicate rent labels; those duplicates are no longer emitted. The v10 change
298
+ is delivered by `@v-office/sdk-core`. Refresh the lockfile when upgrading the
299
+ website SDK so it resolves the coordinated core version shipped for 2.18.0.
300
+
301
+ ## Required Consumer Work
302
+
303
+ Consumers using the default native facility relation and selected availability
304
+ fields only need to upgrade.
305
+
306
+ Consumers enabling object-group association should:
307
+
308
+ 1. Confirm GraphQL synchronization for facilities, relation values, and groups.
309
+ 2. Add `facilityObjectGroupRelationAttributeId` to the v9 config.
310
+ 3. Confirm expected rentals contain `property`.
311
+ 4. Keep unmatched rentals without `property` valid.
312
+ 5. Update complete-output snapshots that gain associated properties.
313
+ 6. Update v9 property snapshots that gain `propertyType`, `location`, `address`,
314
+ description components, or built-in attributes.
315
+
316
+ Consumers enabling custom v9 property highlight text should:
317
+
318
+ 1. Confirm the facility `p_*` field has synchronized to GraphQL.
319
+ 2. Define it with `defineV9PropertyHighlightSources`.
320
+ 3. Select its stable key in `rentalPropertyHighlightPrioritization`.
321
+ 4. Keep missing or `null` source values valid.
322
+ 5. Update complete rental snapshots that gain property highlight lines.
323
+
324
+ Consumers that snapshot or hide calendar prices should:
325
+
326
+ 1. Render `formattedPrice` directly when non-null, including on available
327
+ dates that cannot be used as a start date.
328
+ 2. Keep `formattedPrice: null` valid for unavailable and price-less dates.
329
+ 3. Refresh the lockfile so v10 resolves the coordinated core release.
330
+ 4. Update complete initial-availability snapshots that expected `null` on
331
+ `check_in_not_allowed` or `no_valid_check_out_from_start`.
332
+
333
+ Consumers that snapshot or expand included quote rows should:
334
+
335
+ 1. Keep rendering each included `subItem` as one row.
336
+ 2. Update complete quote snapshots that expected one rent row per seasonal
337
+ rate period.
338
+ 3. Refresh the lockfile so v10 resolves the coordinated core release.
339
+
340
+ ## Recommended Verification
341
+
342
+ 1. Run `getRentals` without the config and confirm native facility associations
343
+ remain unchanged.
344
+ 2. Run it with the configured attribute ID and count rentals by `property.id`.
345
+ 3. Verify a facility that references several groups receives every intended
346
+ rental once.
347
+ 4. Verify unmatched rentals remain present without `property`.
348
+ 5. Verify the configured `rentalScope.propertyId`, when used, returns only the
349
+ resolved facility's rentals.
350
+ 6. Verify populated facility location, address, type, descriptions, and
351
+ attributes are represented in `rental.property`.
352
+ 7. Test at least one catalogue larger than 128 rentals so detail and feedback
353
+ batching are exercised.
354
+ 8. Select a configured line-list source between two built-in property keys and
355
+ confirm its non-empty lines appear at that exact position without duplicates.
356
+ 9. Confirm an unselected source is not requested or emitted.
357
+ 10. Load initial availability for a stay-through or closed-on-arrival date that
358
+ has a source price and confirm `formattedPrice` is non-null.
359
+ 11. Confirm unavailable and price-less dates retain `formattedPrice: null`.
360
+ 12. Confirm start-date-selected availability still has no calendar prices.
361
+ 13. Quote a stay that crosses a seasonal rental-price change and confirm the
362
+ included section shows one rent row.
363
+ 14. Confirm other included labels still appear as separate rows and that the
364
+ inclusive total is unchanged.
@@ -0,0 +1,16 @@
1
+ # Changelog: 2.18.1
2
+
3
+ Maintenance release. `@v-office/website-sdk` now pins `@v-office/sdk-core`
4
+ exactly, so one website SDK version always resolves one core version. There
5
+ are no functional changes.
6
+
7
+ ## Changed
8
+
9
+ - `@v-office/sdk-core` is pinned to exactly `1.20.0` instead of the caret
10
+ range `^1.19.0`. Installs that had already resolved 1.20.0 through the
11
+ range see no change; consumers whose lockfile still held 1.19.x move to
12
+ 1.20.0 with this release.
13
+
14
+ ## Unchanged
15
+
16
+ - Every public API, type and behavior of 2.18.0.
@@ -0,0 +1,12 @@
1
+ # Migration: 2.18.0 to 2.18.1
2
+
3
+ No changes required.
4
+
5
+ ## 1. Upgrade
6
+
7
+ ```sh
8
+ pnpm add @v-office/website-sdk@2.18.1
9
+ ```
10
+
11
+ Future core updates arrive through a new website SDK version; the lockfile
12
+ no longer needs a separate refresh to pick up a coordinated core release.
@@ -182,6 +182,7 @@ const toSectionsIncluded = ({ input: _input, guestQuoteResult, locale, translati
182
182
  locale,
183
183
  translations
184
184
  });
185
+ if (subItems.some((item) => item.position === position)) continue;
185
186
  subItems.push({
186
187
  position,
187
188
  appliedCharge: yield* translations.translate(locale, "quote.sections.included")
@@ -1,5 +1,7 @@
1
- import { a as toAddress, c as rentalsAllFeedbacksQuery, d as VofficeUnitDataPropertyCategoryLabelKeys, f as VofficeUnitDataPropertyCategoryValues, g as parseVofficeUnitData, h as parseVofficeFeedbackData, i as toImages, l as rentalsAllWithoutFeedbacksQuery, m as parseVofficeFacilityData, n as renderVofficePropertyAttribute, o as toLocalizedString, p as VofficeUnitDataPropertyMetadata, r as toFacilityImages, t as toRentalHighlights, u as VofficeUnitDataFieldSchemas } from "./to-rental-highlights-BAsEuxoy.mjs";
2
- import { makeTranslate, renderResolvedCustomAttribute, toCustomAttributeList, toResolvedCustomAttributeCategoryTranslationKey } from "@v-office/sdk-core";
1
+ import { Ht as V9_FACILITY_ATTRIBUTE_KEYS } from "./client-BnA0oXd_.mjs";
2
+ import { c as parseVofficeUnitData, i as VofficeUnitDataPropertyMetadata, n as VofficeUnitDataPropertyCategoryLabelKeys, o as parseVofficeFacilityData, r as VofficeUnitDataPropertyCategoryValues, s as parseVofficeFeedbackData, t as VofficeUnitDataFieldSchemas } from "./v9-data-BnYxa2Ek.mjs";
3
+ import { a as toAddress, c as rentalFacilitiesForObjectGroupRelationQuery, d as rentalsAllFeedbacksQuery, f as rentalsAllWithoutFeedbacksQuery, i as toImages, l as rentalIdsQuery, n as renderVofficePropertyAttribute, o as toLocalizedString, r as toFacilityImages, t as toRentalHighlights, u as rentalsAllByFacilityObjectGroupsWithoutFeedbacksQuery } from "./to-rental-highlights-CJvDxSLw.mjs";
4
+ import { CoreSDKError, makeTranslate, renderResolvedCustomAttribute, toCustomAttributeList, toResolvedCustomAttributeCategoryTranslationKey } from "@v-office/sdk-core";
3
5
  import { Effect } from "effect";
4
6
  //#region src/legacy-v9/parser/rentals/to-description.ts
5
7
  const toDescription = (data, locale) => {
@@ -33,20 +35,128 @@ const toLocation = (location) => {
33
35
  };
34
36
  };
35
37
  //#endregion
38
+ //#region src/legacy-v9/parser/rentals/to-property-attributes.ts
39
+ const toPropertyAttributes = ({ attributes, locale, translations }) => Effect.gen(function* () {
40
+ const items = [];
41
+ const translate = makeTranslate(translations, locale);
42
+ for (const key of V9_FACILITY_ATTRIBUTE_KEYS) {
43
+ const item = yield* renderVofficePropertyAttribute({
44
+ key,
45
+ metadata: VofficeUnitDataPropertyMetadata[key],
46
+ locale,
47
+ translate,
48
+ value: attributes.known[key]
49
+ });
50
+ if (item != null) items.push(item);
51
+ }
52
+ if (items.length === 0) return [];
53
+ const label = yield* translate(VofficeUnitDataPropertyCategoryLabelKeys.GENERAL, "GENERAL");
54
+ return [{
55
+ label,
56
+ subCategories: [{
57
+ label,
58
+ items
59
+ }]
60
+ }];
61
+ });
62
+ //#endregion
63
+ //#region src/legacy-v9/parser/rentals/to-line-list.ts
64
+ const toLineList = (value, locale) => {
65
+ const text = toLocalizedString(value, locale);
66
+ if (text == null) return [];
67
+ return [...new Set(text.split(/\r?\n/).map((item) => item.trim()).filter((item) => item.length > 0))];
68
+ };
69
+ //#endregion
70
+ //#region src/legacy-v9/parser/rentals/to-property-highlights.ts
71
+ const facilityAttributeKeys = new Set(V9_FACILITY_ATTRIBUTE_KEYS);
72
+ const isFacilityAttributeKey = (key) => facilityAttributeKeys.has(key);
73
+ const toPropertyHighlights = ({ attributes, highlightPrioritization, highlightSources, locale, translations }) => Effect.gen(function* () {
74
+ const highlights = [];
75
+ const seenHighlights = /* @__PURE__ */ new Set();
76
+ const seenKeys = /* @__PURE__ */ new Set();
77
+ const translate = makeTranslate(translations, locale);
78
+ for (const key of highlightPrioritization) {
79
+ if (seenKeys.has(key)) continue;
80
+ seenKeys.add(key);
81
+ if (isFacilityAttributeKey(key)) {
82
+ const highlight = yield* renderVofficePropertyAttribute({
83
+ key,
84
+ metadata: VofficeUnitDataPropertyMetadata[key],
85
+ locale,
86
+ translate,
87
+ value: attributes.known[key]
88
+ });
89
+ if (highlight != null && !seenHighlights.has(highlight)) {
90
+ seenHighlights.add(highlight);
91
+ highlights.push(highlight);
92
+ }
93
+ continue;
94
+ }
95
+ const source = Object.hasOwn(highlightSources.byKey, key) ? highlightSources.byKey[key] : void 0;
96
+ if (source == null) continue;
97
+ for (const highlight of toLineList(attributes.unknown[source.v9], locale)) {
98
+ if (seenHighlights.has(highlight)) continue;
99
+ seenHighlights.add(highlight);
100
+ highlights.push(highlight);
101
+ }
102
+ }
103
+ return highlights;
104
+ });
105
+ //#endregion
106
+ //#region src/legacy-v9/parser/rentals/to-property-type.ts
107
+ const propertyTypeByV9FacilityType = {
108
+ CAMPGROUND: "CAMPGROUND",
109
+ FARM: "FARM",
110
+ FLAT_FAC: "APARTMENT_COMPLEX",
111
+ HOSTEL: "HOSTEL",
112
+ HOTEL: "HOTEL",
113
+ HOUSE_FAC: "HOLIDAY_HOME_COMPLEX"
114
+ };
115
+ const isV9FacilityType = (value) => Object.hasOwn(propertyTypeByV9FacilityType, value);
116
+ const toPropertyType = ({ facilityType, locale, translations }) => Effect.gen(function* () {
117
+ if (facilityType == null || !isV9FacilityType(facilityType)) return void 0;
118
+ const propertyType = propertyTypeByV9FacilityType[facilityType];
119
+ return yield* makeTranslate(translations, locale)(`Property.propertyType.option.${propertyType}`, propertyType);
120
+ });
121
+ //#endregion
36
122
  //#region src/legacy-v9/parser/rentals/to-property.ts
37
- const toProperty = (facility, locale) => {
123
+ const toProperty = ({ facility, highlightPrioritization, highlightSources, locale, translations }) => Effect.gen(function* () {
38
124
  const data = parseVofficeFacilityData(facility.data);
39
125
  const name = toLocalizedString(data.known.name, locale);
40
- const description = toLocalizedString(data.known.description, locale);
126
+ const description = toDescription(data, locale);
41
127
  const images = toFacilityImages(data.known.images ?? [], locale);
128
+ const propertyType = yield* toPropertyType({
129
+ facilityType: data.known.type,
130
+ locale,
131
+ translations
132
+ });
133
+ const location = toLocation(data.known.loc);
134
+ const address = toAddress(data, locale);
135
+ const attributes = yield* toPropertyAttributes({
136
+ attributes: data,
137
+ locale,
138
+ translations
139
+ });
140
+ const highlights = yield* toPropertyHighlights({
141
+ attributes: data,
142
+ highlightPrioritization,
143
+ highlightSources,
144
+ locale,
145
+ translations
146
+ });
42
147
  const property = {
43
148
  id: facility.id,
44
149
  nameOrLabel: name ?? facility.name
45
150
  };
46
- if (description != null) property.description = { description };
151
+ if (propertyType != null) property.propertyType = propertyType;
152
+ if (location != null) property.location = location;
153
+ if (Object.keys(address).length > 0) property.address = address;
154
+ if (Object.keys(description).length > 0) property.description = description;
47
155
  if (images.length > 0) property.images = images;
156
+ if (attributes.length > 0) property.attributes = attributes;
157
+ if (highlights.length > 0) property.highlights = highlights;
48
158
  return property;
49
- };
159
+ });
50
160
  //#endregion
51
161
  //#region src/legacy-v9/parser/rentals/to-rental-attributes.ts
52
162
  const GENERAL_CATEGORY$1 = {
@@ -243,21 +353,21 @@ const BED_ATTRIBUTE_LABEL_KEYS = {
243
353
  openFootsection: "Bed.openFootsection.label",
244
354
  raisedBeds: "Bed.raisedBeds.label"
245
355
  };
246
- const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
356
+ const isRecord$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
247
357
  const nonEmptyString = (value) => typeof value === "string" && value.length > 0 ? value : void 0;
248
358
  const positiveInteger = (value) => typeof value === "number" && Number.isInteger(value) && value > 0 ? value : 0;
249
359
  const toRooms = ({ locale, translations, roomDetails }) => Effect.gen(function* () {
250
360
  const result = [];
251
361
  const translate = makeTranslate(translations, locale);
252
362
  for (const room of Array.isArray(roomDetails) ? roomDetails : []) {
253
- if (!isRecord(room)) continue;
363
+ if (!isRecord$1(room)) continue;
254
364
  const roomType = nonEmptyString(room.type);
255
365
  if (roomType == null) continue;
256
366
  const attributes = [];
257
367
  for (const [key, labelKey] of Object.entries(ROOM_ATTRIBUTE_LABEL_KEYS)) if (room[key] === true) attributes.push(yield* translate(labelKey, key));
258
368
  const beds = [];
259
369
  for (const bed of Array.isArray(room.beds) ? room.beds : []) {
260
- if (!isRecord(bed)) continue;
370
+ if (!isRecord$1(bed)) continue;
261
371
  const bedType = nonEmptyString(bed.type);
262
372
  if (bedType == null) continue;
263
373
  const bedValue = { type: yield* translate(`${BED_TYPE_KEY_PREFIX}.${bedType}`, bedType) };
@@ -277,7 +387,7 @@ const toRooms = ({ locale, translations, roomDetails }) => Effect.gen(function*
277
387
  });
278
388
  //#endregion
279
389
  //#region src/legacy-v9/parser/rentals/to-rental-list-item.ts
280
- const toRentalRentalsOutput = ({ rental, locale, translations, imageProxyBaseUrl, rentalHighlightPrioritization, customAttributes }) => Effect.gen(function* () {
390
+ const toRentalRentalsOutput = ({ rental, locale, translations, imageProxyBaseUrl, rentalHighlightPrioritization, rentalPropertyHighlightPrioritization, customAttributes, v9PropertyHighlightSources }) => Effect.gen(function* () {
281
391
  const data = parseVofficeUnitData(rental.data);
282
392
  const rawRentalType = data.known.type?.trim();
283
393
  const translatedRentalType = rawRentalType != null && rawRentalType.length > 0 ? yield* toRentalType({
@@ -331,12 +441,95 @@ const toRentalRentalsOutput = ({ rental, locale, translations, imageProxyBaseUrl
331
441
  if (vicinity != null) item.vicinity = vicinity;
332
442
  if (highlights != null) item.highlights = highlights;
333
443
  if (rooms.length > 0) item.rooms = rooms;
334
- if (rental.voffice_facility != null) item.property = toProperty(rental.voffice_facility, locale);
444
+ if (rental.voffice_facility != null) item.property = yield* toProperty({
445
+ facility: rental.voffice_facility,
446
+ highlightPrioritization: rentalPropertyHighlightPrioritization,
447
+ highlightSources: v9PropertyHighlightSources,
448
+ locale,
449
+ translations
450
+ });
335
451
  if (reviews != null) item.reviews = reviews;
336
452
  return item;
337
453
  });
338
454
  //#endregion
455
+ //#region src/legacy-v9/static/associate-rentals-with-facilities-by-object-groups.ts
456
+ const operation = "v9.rentals.facility-object-group-relation";
457
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
458
+ const parseGroupIds = (value) => {
459
+ if (value == null) return [];
460
+ if (typeof value === "number") return Number.isSafeInteger(value) && value > 0 ? [value] : void 0;
461
+ if (typeof value === "string") {
462
+ const trimmed = value.trim();
463
+ if (trimmed.length === 0) return [];
464
+ if (!/^[\d\s,;]+$/.test(trimmed)) return void 0;
465
+ const groupIds = trimmed.split(/[\s,;]+/).filter((part) => part.length > 0).map(Number);
466
+ return groupIds.every((groupId) => Number.isSafeInteger(groupId) && groupId > 0) ? groupIds : void 0;
467
+ }
468
+ if (Array.isArray(value)) {
469
+ const groupIds = value.flatMap((part) => parseGroupIds(part) ?? [NaN]);
470
+ return groupIds.every((groupId) => Number.isSafeInteger(groupId) && groupId > 0) ? groupIds : void 0;
471
+ }
472
+ if (isRecord(value)) {
473
+ const groupIds = Object.values(value).flatMap((part) => parseGroupIds(part) ?? [NaN]);
474
+ return groupIds.every((groupId) => Number.isSafeInteger(groupId) && groupId > 0) ? groupIds : void 0;
475
+ }
476
+ };
477
+ const toFacilityId = (facility) => {
478
+ const facilityId = Number(facility.id);
479
+ if (Number.isSafeInteger(facilityId) && facilityId > 0) return facilityId;
480
+ throw new CoreSDKError({
481
+ source: "v9",
482
+ operation,
483
+ message: `Expected facility.id to be a positive integer, received "${facility.id}".`
484
+ });
485
+ };
486
+ const associateRentalsWithFacilitiesByObjectGroups = ({ attributeId, facilities, rentals }) => {
487
+ const attribute = `p_${attributeId}`;
488
+ const facilityByGroupId = /* @__PURE__ */ new Map();
489
+ for (const facility of facilities) {
490
+ const relation = parseVofficeFacilityData(facility.data).unknown[attribute];
491
+ const groupIds = parseGroupIds(relation);
492
+ if (groupIds === void 0) throw new CoreSDKError({
493
+ source: "v9",
494
+ operation,
495
+ message: `Facility "${facility.id}" has an invalid ${attribute} value.`
496
+ });
497
+ for (const groupId of new Set(groupIds)) {
498
+ const existingFacility = facilityByGroupId.get(groupId);
499
+ if (existingFacility != null && existingFacility.id !== facility.id) throw new CoreSDKError({
500
+ source: "v9",
501
+ operation,
502
+ message: `Object group "${groupId}" is assigned to facilities "${existingFacility.id}" and "${facility.id}".`
503
+ });
504
+ facilityByGroupId.set(groupId, facility);
505
+ }
506
+ }
507
+ if (facilityByGroupId.size === 0) throw new CoreSDKError({
508
+ source: "v9",
509
+ operation,
510
+ message: `No facility has a usable ${attribute} object-group relation.`
511
+ });
512
+ return rentals.map((rental) => {
513
+ const matchingFacilities = /* @__PURE__ */ new Map();
514
+ for (const group of rental.voffice_groups ?? []) {
515
+ const facility = facilityByGroupId.get(group.voffice_group_id);
516
+ if (facility != null) matchingFacilities.set(facility.id, facility);
517
+ }
518
+ if (matchingFacilities.size > 1) throw new CoreSDKError({
519
+ source: "v9",
520
+ operation,
521
+ message: `Rental "${rental.voffice_id}" matches several facilities through ${attribute}.`
522
+ });
523
+ const facility = matchingFacilities.values().next().value;
524
+ return {
525
+ ...rental,
526
+ voffice_facility_id: facility == null ? null : toFacilityId(facility),
527
+ voffice_facility: facility ?? null
528
+ };
529
+ });
530
+ };
531
+ //#endregion
339
532
  //#region src/legacy-v9/runtime/rentals.ts
340
533
  const defaultRentalDataAttributes = Object.keys(VofficeUnitDataFieldSchemas);
341
534
  //#endregion
342
- export { defaultRentalDataAttributes, rentalsAllFeedbacksQuery, rentalsAllWithoutFeedbacksQuery, toRentalRentalsOutput };
535
+ export { associateRentalsWithFacilitiesByObjectGroups, defaultRentalDataAttributes, rentalFacilitiesForObjectGroupRelationQuery, rentalIdsQuery, rentalsAllByFacilityObjectGroupsWithoutFeedbacksQuery, rentalsAllFeedbacksQuery, rentalsAllWithoutFeedbacksQuery, toRentalRentalsOutput };
@@ -1,5 +1,6 @@
1
- import { a as parseQueryParameters, i as collectQueryParameters, n as makeV9CustomAttributeFilterCapabilities, o as toQueryString, r as toBasicQueryInputs, s as applyVofficeFilterDerivedBasicQueryInputs } from "./client-yYl7H82t.mjs";
2
- import { a as toAddress, g as parseVofficeUnitData, i as toImages, s as searchQuery, t as toRentalHighlights, u as VofficeUnitDataFieldSchemas } from "./to-rental-highlights-BAsEuxoy.mjs";
1
+ import { a as parseQueryParameters, i as collectQueryParameters, n as makeV9CustomAttributeFilterCapabilities, o as toQueryString, r as toBasicQueryInputs, s as applyVofficeFilterDerivedBasicQueryInputs } from "./client-BnA0oXd_.mjs";
2
+ import { c as parseVofficeUnitData, t as VofficeUnitDataFieldSchemas } from "./v9-data-BnYxa2Ek.mjs";
3
+ import { a as toAddress, i as toImages, s as searchQuery, t as toRentalHighlights } from "./to-rental-highlights-CJvDxSLw.mjs";
3
4
  import { CoreSDKError, STABLE_SEARCH_INPUTS, daysBetweenLocalDates, expandCustomAttributeFilterCompositions, parseChildrenAges, parseOccupancyCount, resolveExpandedCompositions, toFormattedSearchPrice, toIsoDateFromPeriodQueryDate, toStableSearchInputBackendQueryKeys, toStableSearchInputParameterValues, toStableSearchInputQueryKeys, toUnusedFilterKeys } from "@v-office/sdk-core";
4
5
  import { Effect } from "effect";
5
6
  //#region src/legacy-v9/parser/search/input/search-input-error.ts