@sapui5/sap.fe.core 1.136.7 → 1.136.9
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/package.json +1 -1
- package/src/sap/fe/core/.library +1 -1
- package/src/sap/fe/core/AppComponent.js +28 -16
- package/src/sap/fe/core/AppComponent.ts +36 -20
- package/src/sap/fe/core/AppStateHandler.js +6 -5
- package/src/sap/fe/core/AppStateHandler.ts +5 -5
- package/src/sap/fe/core/CommonUtils.js +22 -2
- package/src/sap/fe/core/CommonUtils.ts +22 -4
- package/src/sap/fe/core/ExtensionAPI.js +5 -2
- package/src/sap/fe/core/ExtensionAPI.ts +7 -1
- package/src/sap/fe/core/PageController.js +4 -4
- package/src/sap/fe/core/PageController.ts +3 -3
- package/src/sap/fe/core/TemplateComponent.js +3 -3
- package/src/sap/fe/core/TemplateComponent.ts +2 -2
- package/src/sap/fe/core/buildingBlocks/BuildingBlock.js +6 -2
- package/src/sap/fe/core/buildingBlocks/BuildingBlock.ts +8 -1
- package/src/sap/fe/core/controllerextensions/EditFlow.js +159 -34
- package/src/sap/fe/core/controllerextensions/EditFlow.ts +177 -39
- package/src/sap/fe/core/controllerextensions/SideEffects.js +7 -3
- package/src/sap/fe/core/controllerextensions/SideEffects.ts +2 -2
- package/src/sap/fe/core/controllerextensions/editFlow/draftDataLossPopup.js +28 -17
- package/src/sap/fe/core/controllerextensions/editFlow/draftDataLossPopup.ts +35 -18
- package/src/sap/fe/core/controllerextensions/editFlow/operations/ODataOperation.js +5 -2
- package/src/sap/fe/core/controllerextensions/editFlow/operations/ODataOperation.ts +3 -1
- package/src/sap/fe/core/controllerextensions/routing/RouterProxy.js +13 -9
- package/src/sap/fe/core/controllerextensions/routing/RouterProxy.ts +13 -9
- package/src/sap/fe/core/converters/annotations/DataField.js +6 -2
- package/src/sap/fe/core/converters/annotations/DataField.ts +5 -1
- package/src/sap/fe/core/converters/controls/Common/Action.js +5 -5
- package/src/sap/fe/core/converters/controls/Common/Action.ts +7 -4
- package/src/sap/fe/core/converters/controls/Common/table/Columns.js +6 -5
- package/src/sap/fe/core/converters/controls/Common/table/Columns.ts +5 -4
- package/src/sap/fe/core/converters/templates/ObjectPageConverter.js +2 -2
- package/src/sap/fe/core/converters/templates/ObjectPageConverter.ts +1 -1
- package/src/sap/fe/core/fpm/manifest.json +1 -1
- package/src/sap/fe/core/helpers/DeleteHelper.js +8 -9
- package/src/sap/fe/core/helpers/DeleteHelper.ts +10 -8
- package/src/sap/fe/core/library.js +3 -7
- package/src/sap/fe/core/library.ts +5 -21
- package/src/sap/fe/core/messagebundle_da.properties +3 -3
- package/src/sap/fe/core/messagebundle_es.properties +1 -1
- package/src/sap/fe/core/messagebundle_fr.properties +9 -9
- package/src/sap/fe/core/messagebundle_hu.properties +1 -1
- package/src/sap/fe/core/messagebundle_id.properties +1 -1
- package/src/sap/fe/core/messagebundle_ja.properties +1 -1
- package/src/sap/fe/core/messagebundle_lv.properties +1 -1
- package/src/sap/fe/core/messagebundle_uk.properties +1 -1
- package/src/sap/fe/core/messagebundle_zh_CN.properties +1 -1
- package/src/sap/fe/core/rootView/RootViewBaseController.js +17 -17
- package/src/sap/fe/core/rootView/RootViewBaseController.ts +24 -29
- package/src/sap/fe/core/services/SideEffectsServiceFactory.js +13 -6
- package/src/sap/fe/core/services/SideEffectsServiceFactory.ts +17 -5
- package/src/sap/fe/core/templating/DisplayModeFormatter.js +4 -5
- package/src/sap/fe/core/templating/DisplayModeFormatter.ts +4 -6
- package/src/sap/fe/core/templating/UIFormatters.js +2 -2
- package/src/sap/fe/core/templating/UIFormatters.ts +1 -1
|
@@ -71,6 +71,7 @@ import ActionRuntime from "../ActionRuntime";
|
|
|
71
71
|
import { ConfirmRecommendationDialog, RecommendationDialogDecision } from "../controls/Recommendations/ConfirmRecommendationDialog";
|
|
72
72
|
import { CreationMode, type BaseManifestSettings } from "../converters/ManifestSettings";
|
|
73
73
|
import { isFulfilled } from "../helpers/TypeGuards";
|
|
74
|
+
import draftDataLossPopup from "./editFlow/draftDataLossPopup";
|
|
74
75
|
import type { OperationResult } from "./editFlow/operations/ODataOperation";
|
|
75
76
|
import NavigationReason from "./routing/NavigationReason";
|
|
76
77
|
const ProgrammingModel = FELibrary.ProgrammingModel,
|
|
@@ -245,6 +246,8 @@ class EditFlow extends BaseControllerExtension {
|
|
|
245
246
|
stickyContext = newDocumentContext;
|
|
246
247
|
}
|
|
247
248
|
await this.handleStickyOn(stickyContext);
|
|
249
|
+
} else if (this._hiddenDraft) {
|
|
250
|
+
await this.handleHiddenDraftEdit(newDocumentContext);
|
|
248
251
|
}
|
|
249
252
|
overrideMessageUIDecision = true;
|
|
250
253
|
|
|
@@ -755,6 +758,11 @@ class EditFlow extends BaseControllerExtension {
|
|
|
755
758
|
}
|
|
756
759
|
}
|
|
757
760
|
if (newDocumentContext) {
|
|
761
|
+
// Handle hidden draft session for created documents, similar to editDocument
|
|
762
|
+
if (this._hiddenDraft && programmingModel === ProgrammingModel.Draft) {
|
|
763
|
+
await this.handleHiddenDraftEdit(newDocumentContext);
|
|
764
|
+
}
|
|
765
|
+
|
|
758
766
|
this.setDocumentModifiedOnCreate(listBinding);
|
|
759
767
|
if (!this._isFclEnabled()) {
|
|
760
768
|
EditState.setEditStateDirty();
|
|
@@ -1775,7 +1783,7 @@ class EditFlow extends BaseControllerExtension {
|
|
|
1775
1783
|
if (
|
|
1776
1784
|
((sProgrammingModel === ProgrammingModel.Sticky || oContext.getProperty("HasActiveEntity")) &&
|
|
1777
1785
|
rootViewController.isFclEnabled()) ||
|
|
1778
|
-
this._hiddenDraft
|
|
1786
|
+
(this._hiddenDraft && oContext.getProperty("HasActiveEntity"))
|
|
1779
1787
|
) {
|
|
1780
1788
|
siblingInfo = await this._computeSiblingInformation(
|
|
1781
1789
|
rootContext,
|
|
@@ -1808,6 +1816,9 @@ class EditFlow extends BaseControllerExtension {
|
|
|
1808
1816
|
mParameters?.isStandardSave
|
|
1809
1817
|
);
|
|
1810
1818
|
this._removeStickySessionInternalProperties(sProgrammingModel);
|
|
1819
|
+
if (this._hiddenDraft) {
|
|
1820
|
+
this.handleSessionOff();
|
|
1821
|
+
}
|
|
1811
1822
|
|
|
1812
1823
|
this._sendActivity(Activity.Activate, activeDocumentContext);
|
|
1813
1824
|
this.base.collaborativeDraft.disconnect();
|
|
@@ -1958,8 +1969,17 @@ class EditFlow extends BaseControllerExtension {
|
|
|
1958
1969
|
|
|
1959
1970
|
try {
|
|
1960
1971
|
await this.syncTask();
|
|
1972
|
+
let rootContext: Context;
|
|
1961
1973
|
const sProgrammingModel = this.getProgrammingModel(oContext);
|
|
1962
|
-
|
|
1974
|
+
|
|
1975
|
+
// Determine the root context based on hidden draft mode and programming model
|
|
1976
|
+
if (this._hiddenDraft && sProgrammingModel === "Draft") {
|
|
1977
|
+
const rootContextPath = ModelHelper.getDraftRootPath(oContext);
|
|
1978
|
+
rootContext = oContext.getPath() === rootContextPath ? oContext : await this.getRootContext(oContext);
|
|
1979
|
+
} else {
|
|
1980
|
+
rootContext = await this.getRootContext(oContext);
|
|
1981
|
+
}
|
|
1982
|
+
|
|
1963
1983
|
if (
|
|
1964
1984
|
((sProgrammingModel === ProgrammingModel.Sticky || oContext.getProperty("HasActiveEntity")) && this._isFclEnabled()) ||
|
|
1965
1985
|
this._hiddenDraft
|
|
@@ -1993,6 +2013,9 @@ class EditFlow extends BaseControllerExtension {
|
|
|
1993
2013
|
}
|
|
1994
2014
|
});
|
|
1995
2015
|
this._removeStickySessionInternalProperties(sProgrammingModel);
|
|
2016
|
+
if (this._hiddenDraft) {
|
|
2017
|
+
this.handleSessionOff();
|
|
2018
|
+
}
|
|
1996
2019
|
|
|
1997
2020
|
this.setEditMode(EditMode.Display, false);
|
|
1998
2021
|
this.setDocumentModified(false);
|
|
@@ -3069,26 +3092,77 @@ class EditFlow extends BaseControllerExtension {
|
|
|
3069
3092
|
}
|
|
3070
3093
|
|
|
3071
3094
|
if (!appComponent.getRouterProxy().hasNavigationGuard()) {
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3095
|
+
await this.registerCallbacks(context, appComponent);
|
|
3096
|
+
}
|
|
3097
|
+
} catch (error: unknown) {
|
|
3098
|
+
Log.info(error as string);
|
|
3099
|
+
return false;
|
|
3100
|
+
}
|
|
3101
|
+
|
|
3102
|
+
return true;
|
|
3103
|
+
}
|
|
3104
|
+
|
|
3105
|
+
/**
|
|
3106
|
+
* Registers session callbacks for draft handling.
|
|
3107
|
+
* @param context The context being edited
|
|
3108
|
+
* @param appComponent The application component
|
|
3109
|
+
* @param semanticPath Optional semantic path for navigation guard
|
|
3110
|
+
*/
|
|
3111
|
+
private async registerCallbacks(context: Context, appComponent: AppComponent, semanticPath?: string): Promise<void> {
|
|
3112
|
+
const hashTracker = appComponent.getRouterProxy().getHash();
|
|
3113
|
+
const internalModel = this.getInternalModel();
|
|
3114
|
+
|
|
3115
|
+
// Set a guard in the RouterProxy
|
|
3116
|
+
// A timeout is necessary, as with deferred creation the hashChanger is not updated yet with
|
|
3117
|
+
// the new hash, and the guard cannot be found in the managed history of the router proxy
|
|
3118
|
+
setTimeout(function () {
|
|
3119
|
+
appComponent.getRouterProxy().setNavigationGuard(context.getPath().substring(1), semanticPath);
|
|
3120
|
+
}, 0);
|
|
3121
|
+
|
|
3122
|
+
// Setting back navigation on shell service, to get the discard message box in case of sticky
|
|
3123
|
+
await appComponent.getShellServices().setBackNavigation(this.onBackNavigationInSession.bind(this, context));
|
|
3124
|
+
|
|
3125
|
+
// Attach sticky session callbacks
|
|
3126
|
+
const i18nModel = this.base.getView().getModel("sap.fe.i18n");
|
|
3127
|
+
appComponent.registerCallbacks(
|
|
3128
|
+
this.getDirtyStateProvider(appComponent, internalModel, hashTracker),
|
|
3129
|
+
this.getRouteMatchedFunction(context, appComponent),
|
|
3130
|
+
!this._hiddenDraft ? this.getSessionTimeoutFunction(context, i18nModel) : undefined
|
|
3131
|
+
);
|
|
3132
|
+
}
|
|
3133
|
+
|
|
3134
|
+
/**
|
|
3135
|
+
* Enables hidden draft edit session handling.
|
|
3136
|
+
* Sets up the necessary session callbacks and navigation guards for hidden draft scenarios.
|
|
3137
|
+
* @param context The context being edited in the hidden draft session
|
|
3138
|
+
* @returns Promise resolving to true if session setup succeeds, false otherwise
|
|
3139
|
+
*/
|
|
3140
|
+
private async handleHiddenDraftEdit(context: Context): Promise<boolean> {
|
|
3141
|
+
const appComponent = this.getAppComponent();
|
|
3142
|
+
|
|
3143
|
+
try {
|
|
3144
|
+
if (appComponent === undefined) {
|
|
3145
|
+
throw new Error("undefined AppComponent for function handleHiddenDraftEdit");
|
|
3146
|
+
}
|
|
3147
|
+
|
|
3148
|
+
// Ensure we have the correct context for hidden draft handling
|
|
3149
|
+
let hiddenDraftContext: Context;
|
|
3150
|
+
if (!this._isFclEnabled()) {
|
|
3151
|
+
hiddenDraftContext = context;
|
|
3152
|
+
} else {
|
|
3153
|
+
hiddenDraftContext = context?.getModel().getKeepAliveContext(context.getPath());
|
|
3154
|
+
}
|
|
3155
|
+
|
|
3156
|
+
//Create can be handled from any context, but we need to create the guard only for the create in root Context.
|
|
3157
|
+
if (this.getCreationMode()) {
|
|
3158
|
+
const rootContextPath = ModelHelper.getDraftRootPath(hiddenDraftContext);
|
|
3159
|
+
hiddenDraftContext =
|
|
3160
|
+
hiddenDraftContext.getPath() === rootContextPath ? hiddenDraftContext : await this.getRootContext(hiddenDraftContext);
|
|
3161
|
+
}
|
|
3162
|
+
|
|
3163
|
+
if (!appComponent.getRouterProxy().hasNavigationGuard()) {
|
|
3164
|
+
const semanticPath = SemanticKeyHelper.getSemanticPath(hiddenDraftContext, true)?.substring(1);
|
|
3165
|
+
await this.registerCallbacks(hiddenDraftContext, appComponent, semanticPath);
|
|
3092
3166
|
}
|
|
3093
3167
|
} catch (error: unknown) {
|
|
3094
3168
|
Log.info(error as string);
|
|
@@ -3102,7 +3176,7 @@ class EditFlow extends BaseControllerExtension {
|
|
|
3102
3176
|
* Disables the sticky edit session.
|
|
3103
3177
|
* @returns True in case of success, false otherwise
|
|
3104
3178
|
*/
|
|
3105
|
-
private
|
|
3179
|
+
private handleSessionOff(): boolean {
|
|
3106
3180
|
const appComponent = this.getAppComponent();
|
|
3107
3181
|
try {
|
|
3108
3182
|
if (appComponent === undefined) {
|
|
@@ -3115,10 +3189,10 @@ class EditFlow extends BaseControllerExtension {
|
|
|
3115
3189
|
appComponent.getRouterProxy().discardNavigationGuard();
|
|
3116
3190
|
}
|
|
3117
3191
|
|
|
3118
|
-
if (appComponent.
|
|
3192
|
+
if (appComponent.unregisterCallbacks) {
|
|
3119
3193
|
// If we have exited from the app, CommonUtils.getAppComponent doesn't return a
|
|
3120
3194
|
// sap.fe.core.AppComponent, hence the 'if' above
|
|
3121
|
-
appComponent.
|
|
3195
|
+
appComponent.unregisterCallbacks();
|
|
3122
3196
|
}
|
|
3123
3197
|
|
|
3124
3198
|
this.setEditMode(EditMode.Display, false);
|
|
@@ -3164,7 +3238,7 @@ class EditFlow extends BaseControllerExtension {
|
|
|
3164
3238
|
let isDirty: boolean;
|
|
3165
3239
|
const isSessionOn = internalModel.getProperty("/sessionOn");
|
|
3166
3240
|
|
|
3167
|
-
if (!isSessionOn) {
|
|
3241
|
+
if (!isSessionOn && !this._hiddenDraft) {
|
|
3168
3242
|
// If the sticky session was terminated before hand.
|
|
3169
3243
|
// Example in case of navigating away from application using IBN.
|
|
3170
3244
|
return undefined;
|
|
@@ -3183,6 +3257,10 @@ class EditFlow extends BaseControllerExtension {
|
|
|
3183
3257
|
// or crossing the guard has already been allowed by the RouterProxy
|
|
3184
3258
|
lclHashTracker = targetHash;
|
|
3185
3259
|
isDirty = false;
|
|
3260
|
+
} else if (this._hiddenDraft && !this.isDocumentModified()) {
|
|
3261
|
+
// the user attempts to navigate within the hidden draft when the document is not modified
|
|
3262
|
+
isDirty = false;
|
|
3263
|
+
lclHashTracker = targetHash;
|
|
3186
3264
|
} else {
|
|
3187
3265
|
// the user attempts to navigate within the app, for example back to the list report
|
|
3188
3266
|
isDirty = true;
|
|
@@ -3230,7 +3308,7 @@ class EditFlow extends BaseControllerExtension {
|
|
|
3230
3308
|
type: "Emphasized",
|
|
3231
3309
|
press: (): void => {
|
|
3232
3310
|
// remove sticky handling after navigation since session has already been terminated
|
|
3233
|
-
this.
|
|
3311
|
+
this.handleSessionOff();
|
|
3234
3312
|
this.getInternalRouting().navigateBackFromContext(stickyContext);
|
|
3235
3313
|
}
|
|
3236
3314
|
}),
|
|
@@ -3256,32 +3334,92 @@ class EditFlow extends BaseControllerExtension {
|
|
|
3256
3334
|
* @param appComponent The app component
|
|
3257
3335
|
* @returns The callback function
|
|
3258
3336
|
*/
|
|
3259
|
-
private getRouteMatchedFunction(context: Context, appComponent: AppComponent) {
|
|
3260
|
-
|
|
3337
|
+
private getRouteMatchedFunction(context: Context, appComponent: AppComponent): () => Promise<void> {
|
|
3338
|
+
const programmingModel = this.getProgrammingModel(context);
|
|
3339
|
+
const model = context.getModel();
|
|
3340
|
+
const contextPath = context.getPath();
|
|
3341
|
+
return async (): Promise<void> => {
|
|
3261
3342
|
const currentHash = appComponent.getRouterProxy().getHash();
|
|
3262
|
-
//
|
|
3343
|
+
// If the current hash is empty or the navigation is outside the guard, discard the session.
|
|
3263
3344
|
if (!currentHash || !appComponent.getRouterProxy().checkHashWithGuard(currentHash)) {
|
|
3264
|
-
this.
|
|
3265
|
-
|
|
3345
|
+
if (this._hiddenDraft && programmingModel === ProgrammingModel.Draft) {
|
|
3346
|
+
return this.handleHiddenDraftDiscard(context, model, contextPath);
|
|
3347
|
+
} else {
|
|
3348
|
+
// For sticky session, discard changes and clear session context.
|
|
3349
|
+
await this.discardStickySession(context);
|
|
3350
|
+
context.getModel().clearSessionContext();
|
|
3351
|
+
}
|
|
3266
3352
|
}
|
|
3267
3353
|
};
|
|
3268
3354
|
}
|
|
3269
3355
|
|
|
3356
|
+
/**
|
|
3357
|
+
* Handles the discard process for hidden draft sessions.
|
|
3358
|
+
* Prepares the context and ensures we have the required properties before discarding.
|
|
3359
|
+
* @param context The context being edited.
|
|
3360
|
+
* @param model The OData model instance used for binding the context.
|
|
3361
|
+
* @param contextPath The path of the context being edited.
|
|
3362
|
+
* @returns A promise that resolves when the hidden draft session is discarded.
|
|
3363
|
+
*/
|
|
3364
|
+
private async handleHiddenDraftDiscard(context: ODataV4Context, model: ODataModel, contextPath: string): Promise<void> {
|
|
3365
|
+
// Prepare the context for discard operation
|
|
3366
|
+
let discardContext: Context;
|
|
3367
|
+
if (context?.getBinding()) {
|
|
3368
|
+
discardContext = context;
|
|
3369
|
+
} else {
|
|
3370
|
+
discardContext = model.bindContext(contextPath, undefined, {}).getBoundContext();
|
|
3371
|
+
}
|
|
3372
|
+
|
|
3373
|
+
// Request necessary properties before discarding the draft session.
|
|
3374
|
+
await discardContext.requestProperty(["HasActiveEntity", "IsActiveEntity"]);
|
|
3375
|
+
return this.discardHiddenDraftSession(discardContext);
|
|
3376
|
+
}
|
|
3377
|
+
|
|
3378
|
+
/**
|
|
3379
|
+
* Discards the current context changes and refreshes the context if it is not a creation operation.
|
|
3380
|
+
* @param context The context to be discarded.
|
|
3381
|
+
* @param isCreation A boolean indicating if the context is for a creation operation.
|
|
3382
|
+
*/
|
|
3383
|
+
private discardContext(context: Context, isCreation: boolean): void {
|
|
3384
|
+
if (context?.hasPendingChanges()) {
|
|
3385
|
+
context.getBinding().resetChanges();
|
|
3386
|
+
}
|
|
3387
|
+
if (!isCreation) {
|
|
3388
|
+
context.refresh();
|
|
3389
|
+
}
|
|
3390
|
+
}
|
|
3391
|
+
|
|
3270
3392
|
/**
|
|
3271
3393
|
* Ends a sticky session by discarding changes.
|
|
3272
3394
|
* @param context The context being edited (root of the sticky session)
|
|
3273
3395
|
*/
|
|
3274
3396
|
private async discardStickySession(context: Context): Promise<void> {
|
|
3275
|
-
const isCreation = this.getCreationMode();
|
|
3276
|
-
this.
|
|
3397
|
+
const isCreation = this.getCreationMode();
|
|
3398
|
+
this.handleSessionOff();
|
|
3277
3399
|
|
|
3278
3400
|
const discardedContext = await sticky.discardDocument(context);
|
|
3279
|
-
if (discardedContext
|
|
3280
|
-
|
|
3401
|
+
if (discardedContext !== undefined && discardedContext !== null) {
|
|
3402
|
+
this.discardContext(discardedContext, isCreation);
|
|
3281
3403
|
}
|
|
3282
|
-
|
|
3283
|
-
|
|
3404
|
+
}
|
|
3405
|
+
|
|
3406
|
+
/**
|
|
3407
|
+
* Discards the hidden draft session for the given context.
|
|
3408
|
+
* @param context The context from which the draft session will be discarded.
|
|
3409
|
+
* @returns A promise that resolves when the discard operation is complete.
|
|
3410
|
+
*/
|
|
3411
|
+
private async discardHiddenDraftSession(context: Context): Promise<void> {
|
|
3412
|
+
const isCreation = this.getCreationMode();
|
|
3413
|
+
const discardedContext = (await draftDataLossPopup.discardDraft(
|
|
3414
|
+
this.getView().getController() as PageController,
|
|
3415
|
+
undefined,
|
|
3416
|
+
context,
|
|
3417
|
+
true
|
|
3418
|
+
)) as Context;
|
|
3419
|
+
if (discardedContext !== undefined && discardedContext !== null) {
|
|
3420
|
+
this.discardContext(discardedContext, isCreation);
|
|
3284
3421
|
}
|
|
3422
|
+
this.handleSessionOff();
|
|
3285
3423
|
}
|
|
3286
3424
|
|
|
3287
3425
|
/**
|
|
@@ -3455,7 +3593,7 @@ class EditFlow extends BaseControllerExtension {
|
|
|
3455
3593
|
const internalModel = this.getInternalModel();
|
|
3456
3594
|
internalModel.setProperty("/sessionOn", false);
|
|
3457
3595
|
internalModel.setProperty("/stickySessionToken", undefined);
|
|
3458
|
-
this.
|
|
3596
|
+
this.handleSessionOff();
|
|
3459
3597
|
}
|
|
3460
3598
|
}
|
|
3461
3599
|
|