001// -------------------------------------------------------------------------------- 002// Copyright 2002-2026 Echo Three, LLC 003// 004// Licensed under the Apache License, Version 2.0 (the "License"); 005// you may not use this file except in compliance with the License. 006// You may obtain a copy of the License at 007// 008// http://www.apache.org/licenses/LICENSE-2.0 009// 010// Unless required by applicable law or agreed to in writing, software 011// distributed under the License is distributed on an "AS IS" BASIS, 012// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013// See the License for the specific language governing permissions and 014// limitations under the License. 015// -------------------------------------------------------------------------------- 016 017package com.echothree.control.user.training.server.command; 018 019import com.echothree.control.user.training.common.edit.TrainingClassEdit; 020import com.echothree.control.user.training.common.edit.TrainingEditFactory; 021import com.echothree.control.user.training.common.form.EditTrainingClassForm; 022import com.echothree.control.user.training.common.result.EditTrainingClassResult; 023import com.echothree.control.user.training.common.result.TrainingResultFactory; 024import com.echothree.control.user.training.common.spec.TrainingClassSpec; 025import com.echothree.model.control.core.common.MimeTypeUsageTypes; 026import com.echothree.model.control.core.server.logic.MimeTypeLogic; 027import com.echothree.model.control.party.common.PartyTypes; 028import com.echothree.model.control.security.common.SecurityRoleGroups; 029import com.echothree.model.control.security.common.SecurityRoles; 030import com.echothree.model.control.training.server.control.TrainingControl; 031import com.echothree.model.control.uom.common.UomConstants; 032import com.echothree.model.control.uom.server.logic.UnitOfMeasureTypeLogic; 033import com.echothree.model.control.workeffort.common.workeffort.TrainingConstants; 034import com.echothree.model.control.workeffort.server.control.WorkEffortControl; 035import com.echothree.model.data.core.server.entity.MimeType; 036import com.echothree.model.data.training.server.entity.TrainingClass; 037import com.echothree.model.data.user.common.pk.UserVisitPK; 038import com.echothree.model.data.workeffort.server.entity.WorkEffortScope; 039import com.echothree.util.common.message.ExecutionErrors; 040import com.echothree.util.common.validation.FieldDefinition; 041import com.echothree.util.common.validation.FieldType; 042import com.echothree.util.common.command.EditMode; 043import com.echothree.util.server.control.BaseAbstractEditCommand; 044import com.echothree.util.server.control.CommandSecurityDefinition; 045import com.echothree.util.server.control.PartyTypeDefinition; 046import com.echothree.util.server.control.SecurityRoleDefinition; 047import com.echothree.util.server.string.PercentUtils; 048import java.util.List; 049import javax.enterprise.context.Dependent; 050import javax.inject.Inject; 051 052@Dependent 053public class EditTrainingClassCommand 054 extends BaseAbstractEditCommand<TrainingClassSpec, TrainingClassEdit, EditTrainingClassResult, TrainingClass, TrainingClass> { 055 056 private final static CommandSecurityDefinition COMMAND_SECURITY_DEFINITION; 057 private final static List<FieldDefinition> SPEC_FIELD_DEFINITIONS; 058 private final static List<FieldDefinition> EDIT_FIELD_DEFINITIONS; 059 060 static { 061 COMMAND_SECURITY_DEFINITION = new CommandSecurityDefinition(List.of( 062 new PartyTypeDefinition(PartyTypes.UTILITY.name(), null), 063 new PartyTypeDefinition(PartyTypes.EMPLOYEE.name(), List.of( 064 new SecurityRoleDefinition(SecurityRoleGroups.TrainingClass.name(), SecurityRoles.Edit.name()) 065 )) 066 )); 067 068 SPEC_FIELD_DEFINITIONS = List.of( 069 new FieldDefinition("TrainingClassName", FieldType.ENTITY_NAME, true, null, null) 070 ); 071 072 EDIT_FIELD_DEFINITIONS = List.of( 073 new FieldDefinition("TrainingClassName", FieldType.ENTITY_NAME, true, null, null), 074 new FieldDefinition("EstimatedReadingTime", FieldType.UNSIGNED_LONG, false, null, null), 075 new FieldDefinition("EstimatedReadingTimeUnitOfMeasureTypeName", FieldType.ENTITY_NAME, false, null, null), 076 new FieldDefinition("ReadingTimeAllowed", FieldType.UNSIGNED_LONG, false, null, null), 077 new FieldDefinition("ReadingTimeAllowedUnitOfMeasureTypeName", FieldType.ENTITY_NAME, false, null, null), 078 new FieldDefinition("EstimatedTestingTime", FieldType.UNSIGNED_LONG, false, null, null), 079 new FieldDefinition("EstimatedTestingTimeUnitOfMeasureTypeName", FieldType.ENTITY_NAME, false, null, null), 080 new FieldDefinition("TestingTimeAllowed", FieldType.UNSIGNED_LONG, false, null, null), 081 new FieldDefinition("TestingTimeAllowedUnitOfMeasureTypeName", FieldType.ENTITY_NAME, false, null, null), 082 new FieldDefinition("RequiredCompletionTime", FieldType.UNSIGNED_LONG, false, null, null), 083 new FieldDefinition("RequiredCompletionTimeUnitOfMeasureTypeName", FieldType.ENTITY_NAME, false, null, null), 084 new FieldDefinition("WorkEffortScopeName", FieldType.ENTITY_NAME, false, null, null), 085 new FieldDefinition("DefaultPercentageToPass", FieldType.FRACTIONAL_PERCENT, false, null, null), 086 new FieldDefinition("OverallQuestionCount", FieldType.UNSIGNED_INTEGER, false, null, null), 087 new FieldDefinition("TestingValidityTime", FieldType.UNSIGNED_LONG, false, null, null), 088 new FieldDefinition("TestingValidityTimeUnitOfMeasureTypeName", FieldType.ENTITY_NAME, false, null, null), 089 new FieldDefinition("ExpiredRetentionTime", FieldType.UNSIGNED_LONG, false, null, null), 090 new FieldDefinition("ExpiredRetentionTimeUnitOfMeasureTypeName", FieldType.ENTITY_NAME, false, null, null), 091 new FieldDefinition("AlwaysReassignOnExpiration", FieldType.BOOLEAN, true, null, null), 092 new FieldDefinition("IsDefault", FieldType.BOOLEAN, true, null, null), 093 new FieldDefinition("SortOrder", FieldType.SIGNED_INTEGER, true, null, null), 094 new FieldDefinition("Description", FieldType.STRING, false, 1L, 132L), 095 new FieldDefinition("OverviewMimeTypeName", FieldType.MIME_TYPE, false, null, null), 096 new FieldDefinition("Overview", FieldType.STRING, false, null, null), 097 new FieldDefinition("IntroductionMimeTypeName", FieldType.MIME_TYPE, false, null, null), 098 new FieldDefinition("Introduction", FieldType.STRING, false, null, null) 099 ); 100 } 101 102 @Inject 103 TrainingControl trainingControl; 104 105 @Inject 106 WorkEffortControl workEffortControl; 107 108 @Inject 109 MimeTypeLogic mimeTypeLogic; 110 111 @Inject 112 UnitOfMeasureTypeLogic unitOfMeasureTypeLogic; 113 114 115 /** Creates a new instance of EditTrainingClassCommand */ 116 public EditTrainingClassCommand() { 117 super(COMMAND_SECURITY_DEFINITION, SPEC_FIELD_DEFINITIONS, EDIT_FIELD_DEFINITIONS); 118 } 119 120 @Override 121 public EditTrainingClassResult getResult() { 122 return TrainingResultFactory.getEditTrainingClassResult(); 123 } 124 125 @Override 126 public TrainingClassEdit getEdit() { 127 return TrainingEditFactory.getTrainingClassEdit(); 128 } 129 130 @Override 131 public TrainingClass getEntity(EditTrainingClassResult result) { 132 TrainingClass trainingClass; 133 var trainingClassName = spec.getTrainingClassName(); 134 135 if(editMode.equals(EditMode.LOCK) || editMode.equals(EditMode.ABANDON)) { 136 trainingClass = trainingControl.getTrainingClassByName(trainingClassName); 137 } else { // EditMode.UPDATE 138 trainingClass = trainingControl.getTrainingClassByNameForUpdate(trainingClassName); 139 } 140 141 if(trainingClass != null) { 142 result.setTrainingClass(trainingControl.getTrainingClassTransfer(getUserVisit(), trainingClass)); 143 } else { 144 addExecutionError(ExecutionErrors.UnknownTrainingClassName.name(), trainingClassName); 145 } 146 147 return trainingClass; 148 } 149 150 @Override 151 public TrainingClass getLockEntity(TrainingClass trainingClass) { 152 return trainingClass; 153 } 154 155 @Override 156 public void fillInResult(EditTrainingClassResult result, TrainingClass trainingClass) { 157 result.setTrainingClass(trainingControl.getTrainingClassTransfer(getUserVisit(), trainingClass)); 158 } 159 160 MimeType overviewMimeType; 161 MimeType introductionMimeType; 162 163 @Override 164 public void doLock(TrainingClassEdit edit, TrainingClass trainingClass) { 165 var trainingClassTranslation = trainingControl.getTrainingClassTranslation(trainingClass, getPreferredLanguage()); 166 var trainingClassDetail = trainingClass.getLastDetail(); 167 var workEffortScope = trainingClassDetail.getWorkEffortScope(); 168 var overallQuestionCount = trainingClassDetail.getOverallQuestionCount(); 169 UnitOfMeasureTypeLogic.StringUnitOfMeasure stringUnitOfMeasure; 170 171 edit.setTrainingClassName(trainingClassDetail.getTrainingClassName()); 172 stringUnitOfMeasure = unitOfMeasureTypeLogic.unitOfMeasureToString(this, UomConstants.UnitOfMeasureKindUseType_TIME, trainingClassDetail.getEstimatedReadingTime()); 173 edit.setEstimatedReadingTimeUnitOfMeasureTypeName(stringUnitOfMeasure.getUnitOfMeasureTypeName()); 174 edit.setEstimatedReadingTime(stringUnitOfMeasure.getValue()); 175 stringUnitOfMeasure = unitOfMeasureTypeLogic.unitOfMeasureToString(this, UomConstants.UnitOfMeasureKindUseType_TIME, trainingClassDetail.getReadingTimeAllowed()); 176 edit.setReadingTimeAllowedUnitOfMeasureTypeName(stringUnitOfMeasure.getUnitOfMeasureTypeName()); 177 edit.setReadingTimeAllowed(stringUnitOfMeasure.getValue()); 178 stringUnitOfMeasure = unitOfMeasureTypeLogic.unitOfMeasureToString(this, UomConstants.UnitOfMeasureKindUseType_TIME, trainingClassDetail.getEstimatedTestingTime()); 179 edit.setEstimatedTestingTimeUnitOfMeasureTypeName(stringUnitOfMeasure.getUnitOfMeasureTypeName()); 180 edit.setEstimatedTestingTime(stringUnitOfMeasure.getValue()); 181 stringUnitOfMeasure = unitOfMeasureTypeLogic.unitOfMeasureToString(this, UomConstants.UnitOfMeasureKindUseType_TIME, trainingClassDetail.getTestingTimeAllowed()); 182 edit.setTestingTimeAllowedUnitOfMeasureTypeName(stringUnitOfMeasure.getUnitOfMeasureTypeName()); 183 edit.setTestingTimeAllowed(stringUnitOfMeasure.getValue()); 184 stringUnitOfMeasure = unitOfMeasureTypeLogic.unitOfMeasureToString(this, UomConstants.UnitOfMeasureKindUseType_TIME, trainingClassDetail.getRequiredCompletionTime()); 185 edit.setRequiredCompletionTimeUnitOfMeasureTypeName(stringUnitOfMeasure.getUnitOfMeasureTypeName()); 186 edit.setRequiredCompletionTime(stringUnitOfMeasure.getValue()); 187 edit.setWorkEffortScopeName(workEffortScope == null ? null : workEffortScope.getLastDetail().getWorkEffortScopeName()); 188 edit.setDefaultPercentageToPass(PercentUtils.getInstance().formatFractionalPercent(trainingClassDetail.getDefaultPercentageToPass())); 189 edit.setOverallQuestionCount(overallQuestionCount == null? null: overallQuestionCount.toString()); 190 stringUnitOfMeasure = unitOfMeasureTypeLogic.unitOfMeasureToString(this, UomConstants.UnitOfMeasureKindUseType_TIME, trainingClassDetail.getTestingValidityTime()); 191 edit.setTestingValidityTimeUnitOfMeasureTypeName(stringUnitOfMeasure.getUnitOfMeasureTypeName()); 192 edit.setTestingValidityTime(stringUnitOfMeasure.getValue()); 193 stringUnitOfMeasure = unitOfMeasureTypeLogic.unitOfMeasureToString(this, UomConstants.UnitOfMeasureKindUseType_TIME, trainingClassDetail.getExpiredRetentionTime()); 194 edit.setExpiredRetentionTimeUnitOfMeasureTypeName(stringUnitOfMeasure.getUnitOfMeasureTypeName()); 195 edit.setExpiredRetentionTime(stringUnitOfMeasure.getValue()); 196 edit.setAlwaysReassignOnExpiration(trainingClassDetail.getAlwaysReassignOnExpiration().toString()); 197 edit.setIsDefault(trainingClassDetail.getIsDefault().toString()); 198 edit.setSortOrder(trainingClassDetail.getSortOrder().toString()); 199 200 if(trainingClassTranslation != null) { 201 overviewMimeType = trainingClassTranslation.getOverviewMimeType(); 202 introductionMimeType = trainingClassTranslation.getIntroductionMimeType(); 203 204 edit.setDescription(trainingClassTranslation.getDescription()); 205 edit.setOverviewMimeTypeName(overviewMimeType == null? null: overviewMimeType.getLastDetail().getMimeTypeName()); 206 edit.setOverview(trainingClassTranslation.getOverview()); 207 edit.setIntroductionMimeTypeName(introductionMimeType == null? null: introductionMimeType.getLastDetail().getMimeTypeName()); 208 edit.setIntroduction(trainingClassTranslation.getIntroduction()); 209 } 210 } 211 212 Long estimatedReadingTime; 213 Long readingTimeAllowed; 214 Long estimatedTestingTime; 215 Long testingTimeAllowed; 216 Long requiredCompletionTime; 217 WorkEffortScope workEffortScope; 218 Long testingValidityTime; 219 Long expiredRetentionTime; 220 221 @Override 222 public void canUpdate(TrainingClass trainingClass) { 223 var trainingClassName = edit.getTrainingClassName(); 224 var duplicateTrainingClass = trainingControl.getTrainingClassByName(trainingClassName); 225 226 if(duplicateTrainingClass != null && !trainingClass.equals(duplicateTrainingClass)) { 227 addExecutionError(ExecutionErrors.DuplicateTrainingClassName.name(), trainingClassName); 228 } else { 229 var overviewMimeTypeName = edit.getOverviewMimeTypeName(); 230 var overview = edit.getOverview(); 231 232 overviewMimeType = mimeTypeLogic.checkMimeType(this, overviewMimeTypeName, overview, MimeTypeUsageTypes.TEXT.name(), 233 ExecutionErrors.MissingRequiredOverviewMimeTypeName.name(), ExecutionErrors.MissingRequiredOverview.name(), 234 ExecutionErrors.UnknownOverviewMimeTypeName.name(), ExecutionErrors.UnknownOverviewMimeTypeUsage.name()); 235 236 if(!hasExecutionErrors()) { 237 var introductionMimeTypeName = edit.getIntroductionMimeTypeName(); 238 var introduction = edit.getIntroduction(); 239 240 introductionMimeType = mimeTypeLogic.checkMimeType(this, introductionMimeTypeName, introduction, MimeTypeUsageTypes.TEXT.name(), 241 ExecutionErrors.MissingRequiredIntroductionMimeTypeName.name(), ExecutionErrors.MissingRequiredIntroduction.name(), 242 ExecutionErrors.UnknownIntroductionMimeTypeName.name(), ExecutionErrors.UnknownIntroductionMimeTypeUsage.name()); 243 244 if(!hasExecutionErrors()) { 245 estimatedReadingTime = unitOfMeasureTypeLogic.checkUnitOfMeasure(this, UomConstants.UnitOfMeasureKindUseType_TIME, 246 edit.getEstimatedReadingTime(), edit.getEstimatedReadingTimeUnitOfMeasureTypeName(), 247 null, ExecutionErrors.MissingRequiredEstimatedReadingTime.name(), null, ExecutionErrors.MissingRequiredEstimatedReadingTimeUnitOfMeasureTypeName.name(), 248 null, ExecutionErrors.UnknownEstimatedReadingTimeUnitOfMeasureTypeName.name()); 249 250 if(!hasExecutionErrors()) { 251 readingTimeAllowed = unitOfMeasureTypeLogic.checkUnitOfMeasure(this, UomConstants.UnitOfMeasureKindUseType_TIME, 252 edit.getReadingTimeAllowed(), edit.getReadingTimeAllowedUnitOfMeasureTypeName(), 253 null, ExecutionErrors.MissingRequiredReadingTimeAllowed.name(), null, ExecutionErrors.MissingRequiredReadingTimeAllowedUnitOfMeasureTypeName.name(), 254 null, ExecutionErrors.UnknownReadingTimeAllowedUnitOfMeasureTypeName.name()); 255 256 if(!hasExecutionErrors()) { 257 estimatedTestingTime = unitOfMeasureTypeLogic.checkUnitOfMeasure(this, UomConstants.UnitOfMeasureKindUseType_TIME, 258 edit.getEstimatedTestingTime(), edit.getEstimatedTestingTimeUnitOfMeasureTypeName(), 259 null, ExecutionErrors.MissingRequiredEstimatedTestingTime.name(), null, ExecutionErrors.MissingRequiredEstimatedTestingTimeUnitOfMeasureTypeName.name(), 260 null, ExecutionErrors.UnknownEstimatedTestingTimeUnitOfMeasureTypeName.name()); 261 262 if(!hasExecutionErrors()) { 263 testingTimeAllowed = unitOfMeasureTypeLogic.checkUnitOfMeasure(this, UomConstants.UnitOfMeasureKindUseType_TIME, 264 edit.getTestingTimeAllowed(), edit.getTestingTimeAllowedUnitOfMeasureTypeName(), 265 null, ExecutionErrors.MissingRequiredTestingTimeAllowed.name(), null, ExecutionErrors.MissingRequiredTestingTimeAllowedUnitOfMeasureTypeName.name(), 266 null, ExecutionErrors.UnknownTestingTimeAllowedUnitOfMeasureTypeName.name()); 267 268 if(!hasExecutionErrors()) { 269 requiredCompletionTime = unitOfMeasureTypeLogic.checkUnitOfMeasure(this, UomConstants.UnitOfMeasureKindUseType_TIME, 270 edit.getRequiredCompletionTime(), edit.getRequiredCompletionTimeUnitOfMeasureTypeName(), 271 null, ExecutionErrors.MissingRequiredRequiredCompletionTime.name(), null, ExecutionErrors.MissingRequiredRequiredCompletionTimeUnitOfMeasureTypeName.name(), 272 null, ExecutionErrors.UnknownRequiredCompletionTimeUnitOfMeasureTypeName.name()); 273 274 if(!hasExecutionErrors()) { 275 var workEffortScopeName = edit.getWorkEffortScopeName(); 276 277 if(workEffortScopeName != null) { 278 var workEffortType = workEffortControl.getWorkEffortTypeByName(TrainingConstants.WorkEffortType_TRAINING); 279 280 if(workEffortType != null) { 281 workEffortScope = workEffortControl.getWorkEffortScopeByName(workEffortType, workEffortScopeName); 282 283 if(workEffortScope == null) { 284 addExecutionError(ExecutionErrors.UnknownWorkEffortScopeName.name(), TrainingConstants.WorkEffortType_TRAINING, 285 workEffortScopeName); 286 } 287 } else { 288 addExecutionError(ExecutionErrors.UnknownWorkEffortTypeName.name(), TrainingConstants.WorkEffortType_TRAINING); 289 } 290 } 291 292 if(!hasExecutionErrors()) { 293 testingValidityTime = unitOfMeasureTypeLogic.checkUnitOfMeasure(this, UomConstants.UnitOfMeasureKindUseType_TIME, 294 edit.getTestingValidityTime(), edit.getTestingValidityTimeUnitOfMeasureTypeName(), 295 null, ExecutionErrors.MissingRequiredTestingValidityTime.name(), null, ExecutionErrors.MissingRequiredTestingValidityTimeUnitOfMeasureTypeName.name(), 296 null, ExecutionErrors.UnknownTestingValidityTimeUnitOfMeasureTypeName.name()); 297 298 if(!hasExecutionErrors()) { 299 expiredRetentionTime = unitOfMeasureTypeLogic.checkUnitOfMeasure(this, UomConstants.UnitOfMeasureKindUseType_TIME, 300 edit.getExpiredRetentionTime(), edit.getExpiredRetentionTimeUnitOfMeasureTypeName(), 301 null, ExecutionErrors.MissingRequiredExpiredRetentionTime.name(), null, ExecutionErrors.MissingRequiredExpiredRetentionTimeUnitOfMeasureTypeName.name(), 302 null, ExecutionErrors.UnknownExpiredRetentionTimeUnitOfMeasureTypeName.name()); 303 } 304 } 305 } 306 } 307 } 308 } 309 } 310 } 311 } 312 } 313 } 314 315 @Override 316 public void doUpdate(TrainingClass trainingClass) { 317 var partyPK = getPartyPK(); 318 var trainingClassDetailValue = trainingControl.getTrainingClassDetailValueForUpdate(trainingClass); 319 var trainingClassTranslation = trainingControl.getTrainingClassTranslationForUpdate(trainingClass, getPreferredLanguage()); 320 var defaultPercentageToPass = edit.getDefaultPercentageToPass(); 321 var overallQuestionCount = edit.getOverallQuestionCount(); 322 var description = edit.getDescription(); 323 var overview = edit.getOverview(); 324 var introduction = edit.getIntroduction(); 325 326 trainingClassDetailValue.setTrainingClassName(edit.getTrainingClassName()); 327 trainingClassDetailValue.setEstimatedReadingTime(estimatedReadingTime); 328 trainingClassDetailValue.setReadingTimeAllowed(readingTimeAllowed); 329 trainingClassDetailValue.setEstimatedTestingTime(estimatedTestingTime); 330 trainingClassDetailValue.setTestingTimeAllowed(testingTimeAllowed); 331 trainingClassDetailValue.setRequiredCompletionTime(requiredCompletionTime); 332 trainingClassDetailValue.setWorkEffortScopePK(workEffortScope == null ? null : workEffortScope.getPrimaryKey()); 333 trainingClassDetailValue.setDefaultPercentageToPass(defaultPercentageToPass == null? null: Integer.valueOf(defaultPercentageToPass)); 334 trainingClassDetailValue.setOverallQuestionCount(overallQuestionCount == null? null: Integer.valueOf(overallQuestionCount)); 335 trainingClassDetailValue.setTestingValidityTime(testingValidityTime); 336 trainingClassDetailValue.setExpiredRetentionTime(expiredRetentionTime); 337 trainingClassDetailValue.setAlwaysReassignOnExpiration(Boolean.valueOf(edit.getAlwaysReassignOnExpiration())); 338 trainingClassDetailValue.setIsDefault(Boolean.valueOf(edit.getIsDefault())); 339 trainingClassDetailValue.setSortOrder(Integer.valueOf(edit.getSortOrder())); 340 341 trainingControl.updateTrainingClassFromValue(trainingClassDetailValue, partyPK); 342 343 if(trainingClassTranslation == null && (description != null || overview != null || introduction != null)) { 344 trainingControl.createTrainingClassTranslation(trainingClass, getPreferredLanguage(), description, overviewMimeType, overview, introductionMimeType, 345 introduction, partyPK); 346 } else if(trainingClassTranslation != null && (description == null && overview == null && introduction == null)) { 347 trainingControl.deleteTrainingClassTranslation(trainingClassTranslation, partyPK); 348 } else if(trainingClassTranslation != null && (description != null || overview != null || introduction != null)) { 349 var trainingClassTranslationValue = trainingControl.getTrainingClassTranslationValue(trainingClassTranslation); 350 351 trainingClassTranslationValue.setDescription(description); 352 trainingClassTranslationValue.setOverviewMimeTypePK(overviewMimeType == null? null: overviewMimeType.getPrimaryKey()); 353 trainingClassTranslationValue.setOverview(overview); 354 trainingClassTranslationValue.setIntroductionMimeTypePK(introductionMimeType == null? null: introductionMimeType.getPrimaryKey()); 355 trainingClassTranslationValue.setIntroduction(introduction); 356 trainingControl.updateTrainingClassTranslationFromValue(trainingClassTranslationValue, partyPK); 357 } 358 } 359 360}