// ********************************************************
// An event fired by the application when it is initialized
// ********************************************************
event QappCore.Initialize()
{
  // connect to databse should be the first method of the initialize event for the java connection
  DatabaseConnector.connectToDatabases()
  QappCore.initializeGlobalVariables()
  QappCore.startQappCoreSessionsAndTimers()
  QappCore.setLoopsDebugBehavior()
   
   
   
  boolean inWebApiRequest = WebAPIService.isWebApiRequest()
   
  if (!(inWebApiRequest))
  {
     
    // initialize the fixed fields of Tbluserparameters
    Tbluserparameters.Winuser = "WebUser"
    Tbluserparameters.Computername = "WebPC"
    DevTools.RefactoringOpportunity("bluserparameters should be removed and become onlineuserinfo object")
     
    QappCore.populateLoginFormUIFromCookies()
    QappCore.displayStoredLoginMessageFromPreviousSession()
  }
   
  QappCore.QappCommands = new()
  QappCore.QappCommands.clear()
   
  // make sure any server session is always authenticated
  if (QappCore.sessionName() != "")
    QappCore.userRole = Administrator
   
  // setting logoffURL in a component does not affect the webapplications using the component
  QappCore.logoffURL = "???"
  QappCore.LoggedInFromQmobile = false
   
  // DEPRECATION WARNINGS
  DevTools.DEPRECATE("remove", "mainmodule.loadDocumentiComunicazioniOBSOLETE method is used by many qapps and must be removed", "loda module documents collection from db instead", "26.5", toDate(2026, 09, 1))
  DevTools.DEPRECATE("remove", "mainmodule.loadRiferimentiOBSOLETE method is used by many qapps and must be removed", "load riferimenti collection from db instead", "26.5", toDate(2026, 09, 1))
  DevTools.DEPRECATE("DuplicateAnagrafica obsolete and use only for OCS", "in OCS we avoid calling it and we call only duplicateCespite, than we manually set the needed fields", "delete this method", "26.5", 
        toDate(2026, 09, 1))
  DevTools.DEPRECATE("clifor.getCollectionWithoutBlobs has been written and unit tested but no one uses it, may be some qapp so we deprecate", "", "delete the method in qappcore and delete its tests", "26.5", 
        toDate(2026, 09, 1))
   
  // DEPRECATIONS from Dato personalizzato refactoring
  DevTools.DEPRECATE("remove", "datoPersonalizzato.getValue method is used by many qapps and must be removed", "use the new getValueAsString instead", "26.5", toDate(2026, 09, 1))
  DevTools.DEPRECATE("remove", "datoPersonalizzato.setValue method is used by many qapps and must be removed", "use the new setValueAsString instead", "26.5", toDate(2026, 09, 1))
  DevTools.DEPRECATE("remove", "rDatoPersonalizzato.setValue method is used by many qapps and must be removed", "use the new setValueAsString instead", "26.5", toDate(2026, 09, 1))
  DevTools.DEPRECATE("remove", "rDatoPersonalizzato.getValue method is used by many qapps and must be removed", "use the new getValueAsString instead", "26.5", toDate(2026, 09, 1))
  DevTools.DEPRECATE("remove", "cdValue.setIsModelloDEPRECATED likley no one uses it and we can now simply delete it", "simply delete the method", "26.5", toDate(2026, 09, 1))
  DevTools.DEPRECATE("remove", "datoPersonalizzato.getDescriptioDEPRECATED must be replaced with getValueAsString", "simply delete the method in QappCore, if you are still using in a Qapp use 
        getVAlueAsAString instead", "26.5", toDate(2026, 09, 1))
  DevTools.DEPRECATE("remove", "All the method in RefDataInfo and CdataInfo class which has suffix as "TOBEREMOVED" must be removed", "Simply delete them and use the same method of superclass", "26.5", toDate(
        2026, 09, 1))
}


// ──────────────────────────────────

// ****************************************************************
// An event fired by the application when the user performs a login
// ****************************************************************
event QappCore.OnLogin(
  inout string UserName   // It is a string containing the username entered by the user to access the system.
  inout string Password   // It is a string containing the password entered by the user to access the system.
  inout boolean DataValid // If set to True it informs the framework that the data entered by the user is valid: setting it to False indicates that the username and password are incorrect
)
{
  QappCore.QappCommandHandler.setQappCoreSessionInitiatorType(manualLogin)
  string errorMessage = ""
  QappCore.doLogin(UserName, Password, DataValid, errorMessage, ...)
   
  if (errorMessage != "")
  {
    QappCore.QappCommandHandler.writeLoginMessage(errorMessage)
  }
}


// ──────────────────────────────────

// **********************************************************************
// An event fired by the application when a command is passed via the URL
// **********************************************************************
event QappCore.OnCommand(
  string Command // It is a string containing the value of the URL parameter named CMD
)
{
  IDMap urlParametersMap = new()
  urlParametersMap.setValue("TKN", QappCore.getURLParam("TKN"))
  urlParametersMap.setValue("QAPP_CMD", QappCore.getURLParam("QAPP_CMD"))
  urlParametersMap.setValue("MAIN_ID", QappCore.getURLParam("MAIN_ID"))
  urlParametersMap.setValue("BACK_URL", QappCore.getURLParam("BACK_URL"))
  urlParametersMap.setValue("commandName", QappCore.getURLParam("commandName"))
  urlParametersMap.setValue("loggedUserId", toInteger(QappCore.getURLParam("loggedUserId")))
  urlParametersMap.setValue("jsonObject", QappCore.getURLParam("jsonObject"))
  urlParametersMap.setValue("PASSWORD", QappCore.getURLParam("PASSWORD"))
  urlParametersMap.setValue("AUTH", QappCore.getURLParam("AUTH"))
  urlParametersMap.setValue("KORDAPP", QappCore.getURLParam("KORDAPP"))
   
  QappcoreOnCommandEventHandler qoceh = QappcoreOnCommandEventHandler.create(Command, urlParametersMap)
  qoceh.processCommand()
}


// ──────────────────────────────────

// ***************************************************************
// An event fired by the application after the system was accessed
// ***************************************************************
event QappCore.AfterLogin()
{
  if (X.inBrowserSession())
  {
    // check on ionic done to avoid showing the mobileloginform in case of bootstrap app were by mistake mobileFirst is set to true
    boolean ionicAppBeingExecuted = QappCore.isIonic()
    if (QappCore.QappRuntimeBehavior.MobileFirst and ionicAppBeingExecuted)
      MobileLoginForm.showForm()
    else 
    {
      QappCore.DoAfterLogin()
      if (QappCore.QappWeb.UserCommandSet)
         QappCore.prepareUserCommandSet()
    }
     
//    CookieHelper.SaveLastLoggedUsrIdCookie(QappCore.Loggeduser.IDUTENTE)
  }
}


// ──────────────────────────────────

// *****************************************************************************
// An event fired by the application when it receives a message from the browser
// *****************************************************************************
event QappCore.OnBrowserMessage(
  string Message     // This indicates the name of the message received from the browser.
  XMLNode Node       // This is the XML node received from the browser. It contains all the information collected by the client that will be managed by the system
  inout boolean Skip // A boolean output parameter. If set to True, it makes sure the framework does not manage this message
)
{
  // based on the settings done in setQappRunTimeBehavior the matching javascript functions are executed
   
  if (Message == "start")
  {
//    if (QappCore.QappRuntimeBehavior.DisplayFooter)
//      QappCore.executeOnClient("appendFooter();")
     
    if (QappCore.QappRuntimeBehavior.HomeLinkEnabled)
      QappCore.executeOnClient("attachHomeLink();")
     
    if (QappCore.QappRuntimeBehavior.HandleSelectedRowHighligted)
      QappCore.executeOnClient("selectedRow();")
     
     
    // we always load our custom communication js functions
    QappCore.addChromiumToDelphiCommunicationGlobalJavascriptFunction()
  }
}


// ──────────────────────────────────

// *************************************************************************************************************************************************
// Event raised to the application or to a component when something, an application or component, has sent a message using the SendAppMessage method
// *************************************************************************************************************************************************
event QappCore.OnAppMessage(
  string Message   // Name of the message
  object Parameter // An object-type parameter that contains the information about the message sent. This parameter is the one provided to the SendAppMessage method
)
{
  if (Message == "CloseMobileLogin")
  {
    int formsCount = QappCore.openFormsCount()
    for (int idx = 0; idx < formsCount; idx = idx + 1)
    {
      IDForm currentForm = QappCore.getOpenForm(idx)
      boolean isLoginForm = currentForm.getTag("loginTag")
       
      if (isLoginForm)
      {
         currentForm.sendMessage("CLOSE_FORM", ...)
      }
    }
  }
  else if (Message == "SET_LOGIN_MESSAGE")
  {
    IDMap idm = cast(Parameter)
    string loginMessage = idm.getValue("LOGIN_MESSAGE")
    QappCore.setLoginMessage(loginMessage, ...)
  }
}


// ──────────────────────────────────

// *******************************************************************
// An event fired by the application when any command is used by users
// *******************************************************************
event QappCore.GlobalCmdSetCommand(
  IDCommand CommandSet // It's an IDCommand type instance representing the command set that contains the command used by users.
  IDCommand Command    // It's an IDCommand type instance representing the command used by users.
  inout boolean Cancel // If set to True it indicates to the system that it must not notify the specific events of the command sets if they are implemented.
)
{
  if (CommandSet.code == UserMenu)
  {
    switch (Command.code)
    {
      case Help:
         QappCore.openDocument(QappCore.QappWeb.HelpUrl, true, "target="_blank"")
      break
      case Assistenza:
         QappCore.openDocument("mailto:support@nordest.systems", true, "target="_blank"")
      break
      case SitoQualibus:
         QappCore.openDocument("https://www.qualibus.it", true, "target="_blank"")
      break
      case Info:
         string appInfoMessage = formatMessage("|1 versione |2, copyright |3 Nord Est Systems Srl\n", QappCore.QappWeb.Name, getQappVersion(), year(today()), ...)
         Privilegio p = QappCore.Loggeduser.PrivilegeForCurrentQapp
         string privilegeString = p.getPrivilegeDescription()
         if (privilegeString == "")
           privilegeString = "Non hai privilegi su questa Qapp"
         else 
           privilegeString = "I tuoi privilegi su questa Qapp: " + privilegeString
         string userInfoMessage = formatMessage("Sei loggato come |1\n|2", QappCore.Loggeduser.DESCRUTENTE, privilegeString, ...)
          
         X.MessageBox(appInfoMessage + userInfoMessage, "Informazioni sulla Qapp", ...)
      break
      case CambiaPassword:
         ChangePasswordForm.showFor(QappCore.Loggeduser, false, false)
      break
      case Logout:
         Tools.performExit(...)
      break
    }
  }
}


// ──────────────────────────────────

// **************************************************************************************
// Event raised to the form when it is activated, meaning when it is brought to the front
// **************************************************************************************
event QappCore.GlobalActivate(
  IDForm Form // Object that fires this event
)
{
}


// ──────────────────────────────────

// ***********************************************
// Event raised to the form when loading in memory
// ***********************************************
event QappCore.GlobalLoad(
  IDForm Form // Object that fires this event
)
{
}


// ──────────────────────────────────

// *****************************************************
// Global event corresponding to the panel Before Insert
// *****************************************************
event QappCore.GlobalBeforeInsert(
  IDPanel Panel        // Panel that generated this event
  inout boolean Cancel // If set to True, the data for this row is not inserted. The saving step for other rows proceeds as normal.
)
{
}


// ──────────────────────────────────

// ***********************************************************************
// An event fired to the application when a message is sent to the session
// ***********************************************************************
event QappCore.OnSessionMessage(
  string SessionName  // The name of the session that sent the message
  string Message      // Text of the message
  IDArray Parameters  // An IDArray type parameter containing any message parameters
  inout string Result // An output parameter in which the message result is written
)
{
   
  // handling of messages supported by QAPP_CORE_SESSION only
  if (QappCore.sessionName() == QappCoreSession)
  {
    if (Message == "RECONCILE_TOKENS")
    {
      LoginToken.reconcileTokens()
    }
  }
   
  BackgroundWorker.onSessionMessageHandler(Message)
}


// ──────────────────────────────────

// **********************************************************************
// Allows the visual properties of individual panel cells to be adjusted.
// **********************************************************************
event QappCore.GlobalPanelDynamicProperties(
  IDPanel Panel // Object that fires this event
)
{
}


// ──────────────────────────────────

// ****************************************************
// Event raised by the panel when a field is activated.
// ****************************************************
event QappCore.GlobalPanelActivateField(
  IDPanel Panel        // Object that fires this event
  int FieldIndex       // An integer representing the panel field that has been activated. It should be compared with the Me property of the panel field.
  inout boolean Cancel // This can be set to True to cancel activation by any activation object associated with the panel field.
)
{
}


// ──────────────────────────────────

// **********************************************
// Raised by the panel when the user clicks on it
// **********************************************
event QappCore.GlobalPanelMouseClick(
  IDPanel Panel        // Object that fires this event
  int:mouseButtons Button // Specifies which mouse button has been pressed. Refer also to the MouseButtons value list
  int X                // X position in pixels relative to the panel in which the mouse was clicked
  int Y                // Y position in pixels relative to the panel in which the mouse was clicked
  int XB               // X position in pixels relative to the browser in which the mouse was clicked
  int YB               // Y position in pixels relative to the browser in which the mouse was clicked
  int Column           // Index of the field that was clicked (-1 if the click occurred outside the fields)
  int Row              // Row clicked, from 0 to VisibleRows -1. This is -1 if the click occurred outside the fields or on the caption of the column or field
  inout boolean Cancel // Can be set to True to prevent the default action associated with the click from being run, such as execution of the field activation object.
)
{
}


// ──────────────────────────────────

// ****************************************************************************************************************************************************************************************************************
// does the onLogin event needed work, it sets the DataValid param to be used in the login event if needed
// 
// it hangles a human readable string (errorMessage inout param) that describes the login problem found, it may be used in setLoginMessage on a webapp or on some throw or showmessage implementation in the mobile
// apps
// 
// loginByUsernameOnly is needed when we want to check if a user can login wihout passing a password (tipical case is check if anonymous user can login)
// ****************************************************************************************************************************************************************************************************************
public void QappCore.doLogin(
  string userName           // 
  string password           // pass any value if logInByUsernameOnly is true
  inout boolean DataValid   // 
  inout string errorMessage // 
  optional boolean logInByUsernameonly = 0 // 
)
{
  if (logInByUsernameonly)
  {
    QappCore.DTTLogMessage("loginByUsernameOnly is true, password will be ignored", ..., DTTWarning)
  }
   
  if (QappCore.Bypasscomponentonlogin)
  {
    return 
  }
  boolean applicationLocked = LockHandler.isLocked()
  if (applicationLocked)
  {
    errorMessage = "L'applicazione è stata disattivata. Contatta un amministratore per ulteriori informazioni"
    return 
  }
   
  string nonMatchingDBVersionErrorMessage = ""
  int supportedDBVersion = VersionInfo.getSupportedScriptNumber()
   
  boolean DBVersionIsOk = SW9DB.isTheExpectedScriptNumber(supportedDBVersion, nonMatchingDBVersionErrorMessage)
   
  // DB version check
  if (!(DBVersionIsOk))
  {
    errorMessage = nonMatchingDBVersionErrorMessage
    return 
  }
   
   
  boolean mandatoryUsernameAndPasswordCheckPassed = false
   
  if (!(logInByUsernameonly))
  {
    mandatoryUsernameAndPasswordCheckPassed = checkCredentials(userName, password, ...)
  }
  if (mandatoryUsernameAndPasswordCheckPassed or logInByUsernameonly)
  {
    int vIDUTENTE = 0
    string:flagYN vATTIVOUtente = ""
    string:flagYN vCANLOGINUtente = ""
    string:flagYN vISNESUTENTE = ""
    select into variables (found variable)
      set vIDUTENTE = IDUTENTE
      set vATTIVOUtente = ATTIVO
      set vCANLOGINUtente = CANLOGIN
      set vISNESUTENTE = ISNESUTENTE
    from 
      Utenti // master table
    where
      Username == userName
     
    // check on attivo to proceed (or special runtime behavior config to avoid checking attivo)...
    if (vATTIVOUtente == Yes or (QappCore.QappRuntimeBehavior.AllowLoginToUnlinmitedUsers and QappCore.QappRuntimeBehavior.IgnoreAttivoForUnlimitedUsersLogin))
    {
       
      boolean userHasOneOfTheLoginFlags = vCANLOGINUtente == Yes or vISNESUTENTE == Yes
      //  
      // ... check on CanLogin to proceed (or special runtime behavior config to avoid checking for CanLogin)
      if (userHasOneOfTheLoginFlags or QappCore.QappRuntimeBehavior.AllowLoginToUnlinmitedUsers)
      {
         QappCore.Loggeduser = new()
         QappCore.Loggeduser.IDUTENTE = vIDUTENTE
         QappCore.Loggeduser.loadFromDB(0)
          
         // inout parameter to check whether the qapp already exists in NGT APPLICAZIONI, we want to pass the login if it does not exist so it can be inserted
         boolean qappNotYetInserted = false
         QappCore.Loggeduser.loadPrivilegeForCurrentQapp(qappNotYetInserted)
          
         if (QappCore.Loggeduser.canExecuteCurrentQapp() or qappNotYetInserted)
         {
           QappRuntimeBehavior qrb = getQappRuntimeBehavior()
           boolean qappDataJustBeingInserted = qrb.QappJustInserted
            
           // we do not want to execute the postLoginCode when the app has just being inserted because otherwise some validation logic should fail
            
           boolean customPostLoginCodeExecuted = false
           if (!(qappDataJustBeingInserted))
           {
             customPostLoginCodeExecuted = QappCore.QappCommandHandler.handleCustomPostLoginCodeIfNeeded(DataValid, errorMessage)
           }
           if (customPostLoginCodeExecuted == true)
           {
             if (QappCore.userRole == null)
             {
                string errorMessage = "It is necessary to set the userrole in doCustomPostLoginCode"
                QappCore.QappCommandHandler.writeLoginMessage(errorMessage)
                QappCore.DTTLogMessage(errorMessage, ..., DTTError)
             }
           }
           else 
           {
             // in case customPostLoginCode is not run, the login is passed so we set DataValid to true and we assign the userRole
             DataValid = true
             QappCore.userRole = Administrator
           }
            
           if (DataValid)
           {
             QappCore.setSessionAsStartedFRomLOGINURLCommand(false)
             QappCore.writeAccessLog()
             CookieHelper.SaveLastLoggedUsrIdCookie(QappCore.Loggeduser.IDUTENTE)
           }
            
         }
         else 
         {
           errorMessage = "L'utente non ha il privilegio Esegui"
         }
      }
      else 
      {
         errorMessage = "L'utente non ha il privilegio Login"
      }
    }
    else 
    {
      errorMessage = "Utente non attivo"
    }
     
    // store user and password in Cookies if necessary
    if (DataValid)
    {
      QappCore.StoreLoginFormValuesInCookies(userName, password)
    }
  }
  else 
  {
    string Credentilals = "User: " + userName
    errorMessage = "Login errata per " + Credentilals
  }
}


// ──────────────────────────────────

// *************************************************************************************************************************************
// code that must be run after the login is passed, since it needs to work for both mobile first and normal apps it has been centralized
// *************************************************************************************************************************************
public void QappCore.DoAfterLogin()
{
  if (QappCore.QappRuntimeBehavior.QappInitialized)
  {
    if (QappCore.QappWeb.Name == "")
    {
      throw 0, "QAppWeb.Name must be set into Initialize!"
    }
    QappCore.QappCommandHandler.initializeAll()
    QappCore.QappCommandHandler.ProcessDatabase(doNotForce)
    string processDatabaseErrorMessages = QappCore.QappCommandHandler.getProcessDatabaseErrorMessages()
    if (processDatabaseErrorMessages != "")
    {
      QappCore.messageBox("Errors in processing database: " + processDatabaseErrorMessages)
    }
     
    string remoteCommand = QappCore.CommandData.getValue("Command")
    string remoteSourceId = QappCore.CommandData.getValue("MainId")
     
    QappCore.DTTLogMessage("Command recieved was " + remoteCommand, ..., DTTInfo)
     
    if (remoteCommand != "")
    {
      QappCore.DTTLogMessage("Now sending command through inheritance", ..., DTTInfo)
      QappCommandHandler.ForwardCommand(remoteCommand, remoteSourceId, ...)
    }
  }
   
  if ((QappCore.Loggeduser.shouldPasswordBeChanged()) and (!(QappCore.Loggeduser.isAnonymous())))
  {
    // in case the logged user needs to change the password we tipically show the change password form, but we do not want to do it when the webpage is being displayed as part of the Qualibus UI or when a
    // cruscotto is being snapshotted by MailSender. If the session is NOT started by WebApi we know that we are in one of those cases. 
    // 
    // when command is a cruscotto command, we want to take a screenshot without the change password form
    // this might happen in case a cruscotto is run as a user who has ForceChangePassword Y (it might happen when "mostra dati come")
     
    boolean sessionStartedByWebApi = isSessionStartedByApiCall()
     
    if (!(sessionStartedByWebApi))
    {
      ShowChangePasswordTimer.enabled = true
    }
  }
   
  QappCore.checkThatSettingsJsonExistsAsExpected()
   
  // call RTCReset so if userrole has been set in doCustomPostloginCode, the framework recomputes the visibility attached to roles
  QappCore.RTCReset()
}


// ──────────────────────────────────

// ***************************************************************************************************
// performs logout in a mobile first applcation, showing the login again
// 
// IMPORTANT: this method does not close the open forms they must be closed before calling this method
// ***************************************************************************************************
public void QappCore.doMobileLogout()
{
  QappRuntimeBehavior qrb = getQappRuntimeBehavior()
   
  if (qrb.MobileFirst)
  {
    QappCore.closeAllForms(...)
    QappCore.Loggeduser = null
     
    MobileLoginForm.showForm()
     
  }
  else 
  {
    QappCore.DTTLogMessage("doMobileLogout is supported only on a mobile first application", ..., DTTError)
  }
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void QappCore.CloseQappTimer()
{
  // wait 2 seconds to avoid noticing the return to the login screen
   
  QappCore.sleep(2)
   
  if (QappCore.QappRuntimeBehavior.SupportInteractionWithQualibusForms)
  {
    Tools.performExit(QappCore.logoffURL)
  }
  else 
  {
    throw 0, "SupportFormClosure is not set in QappRuntimeBehavior, it is not possible to close the form"
  }
   
   
   
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public void QappCore.setQappVersion(
  string QappVersion // 
)
{
  QappCore.QappVersion = QappVersion
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public IDCollection QappCore.getQappCommands()
{
  return QappCore.QappCommands
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void QappCore.setQappCommands(
  IDCollection QappCommands of QappCommand // 
)
{
  QappCore.QappCommands = QappCommands
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void QappCore.addCommand(
  QappCommand QappCommand // 
)
{
  QappCore.QappCommands.add(QappCommand)
}


// ──────────────────────────────────

// **************************
// Returns the ID of the QApp
// **************************
public int QappCore.findQAppID()
{
   
  // Looks in the web.xml file
  int QAppID = toInteger(QappCore.getSetting(Application, "QAppID"))
   
  // or try to guess from the list of QApps by checking QAppName
  if (QAppID == 0)
  {
    int vID = 0
    select into variables (found variable)
      set vID = IDAPPLICAZIONE
    from 
      NGTAPPLICAZIONI // master table
    where
      QAPPNAME == QappCore.QappWeb.Name
      ISQAPPWEB == Yes
    QAppID = vID
  }
   
  return QAppID
}


// ──────────────────────────────────

// ***********************************************************
// returns the QappCommand whose execution started the session
// ***********************************************************
private QappCommand QappCore.getCommandBeingExecuted()
{
  string currentUrlCommand = QappCore.getURLParam("QAPP_CMD")
   
  QappCommand commandBeingExecuted = null
   
  for each QappCommand qc in QappCore.QappCommands
  {
    QappCommandParameters qcp = qc.getQAppCommandParameters()
    if (qcp.UrlCommand == currentUrlCommand)
    {
      commandBeingExecuted = qc
      break 
    }
  }
  return commandBeingExecuted
}


// ──────────────────────────────────

// *********************************************************************************************************************************************
// method to be called on the Iniialize of the QApp project, it is necessary to set in a developer friendly way the main properties of the QApp:
// Name
// Description
// Decide if the Qapp is enabled in Qualibus or in Qmobile
// 
// jsonSettingsFilename defaults to "settings.json" anyway it can be renamed to match any filename located in the output path
// *********************************************************************************************************************************************
public void QappCore.initializeQApp(
  QappCommandHandler qappCommandHandler                       // 
  string Name // 
  string Description                                          // 
  string QappVersion                                          // 
  string MainIconFilename                                     // 
  string MenuIconFilename                                     // 
  string MenuQmobileIconFilename                              // 
  optional boolean allowUseOnFutureQualibusVersion = 0        // NOT SUPPORTED AT PRESENT
  optional boolean showInQualibusMenu = -1                    // 
  optional boolean showInQMobileMenu = 0                      // 
  optional string:neededPrivilegeTypes privilegeToSeeQappInMenu = "N" // 
  optional boolean EnableQAppData = 0                         // 
  optional boolean isDefaultQApp = 0                          // 
  optional string jsonSettingsFilename = ""                   // 
  optional string jsonSettingsPassword = ""                   // 
  optional string helpUrl = "https://docs.nordest.systems"    // 
  optional boolean generateUserMenu = 0                       // 
  optional boolean supportCustomizationOfQualibusDatabase = 0 //  if true inizialitation scripts will be executed
  optional int customQualibusVersionNumber = -1               // numer of the last script (e.g. 3 for init3.sql, 0 for no)
)
{
  if (!(qappCommandHandler))
  {
    QappCore.DTTLogMessage("qappCommandHandler object is not valid, create it with new() before using it", ..., DTTError)
  }
  QappCore.QappCommandHandler = qappCommandHandler
   
  // as first thing we set the settings related properties...
  if (jsonSettingsFilename != "")
  {
    QappCore.QappCommandHandler.setSettingsFilename(jsonSettingsFilename)
    QappCore.QappCommandHandler.setSettingsPassword(jsonSettingsPassword)
  }
  if (jsonSettingsFilename != "" and jsonSettingsPassword == "")
  {
    QappCore.DTTLogMessage("You specified a jsonSettingsFilename but not a jsonSettingsPassword: are you sure you want this? It is advisable to choose a password.", ..., DTTWarning)
  }
  if (supportCustomizationOfQualibusDatabase)
  {
    if (customQualibusVersionNumber < 0)
    {
      QappCore.DTTLogMessage("If 'supportCusomizationOQUalibusDatabase' is true you must specify a number equals or greater to zero in 'customQualibusVersionNumber'. If only init.sql exists specify 0, 
            otherwise specify N where N matches initN.sql", ..., DTTError)
    }
    else 
    {
      QappCore.writeInfoMessagesInDebugForInitalizationScripts(customQualibusVersionNumber)
    }
  }
   
  // ... so on init they are initialized
  QappCore.QappCommandHandler.init()
   
  QappCore.QappWeb.Name = left(Name, 20)
  QappCore.QappWeb.Description = SH.ShortenWithEllipsis(Description, 50, ...)
  QappCore.setQappVersion(QappVersion)
  QappCore.QappWeb.setMainIconFilename(MainIconFilename)
  QappCore.QappWeb.setMenuIconFilename(MenuIconFilename)
  QappCore.QappWeb.setMenuQMobileIconFilename(MenuQmobileIconFilename)
   
  QappCore.setForceIgnoreDBVersion(allowUseOnFutureQualibusVersion)
  QappCore.QappWeb.SHOWINQUALIBUSMENU = if(showInQualibusMenu, Yes, No)
  QappCore.QappWeb.SHOWINQMOBILEMENU = if(showInQMobileMenu, Yes, No)
  if (decode(privilegeToSeeQappInMenu, NeededPrivilegeTypes) != null)
  {
    QappCore.QappWeb.PRIVILEGETOSEEQAPPINMENU = privilegeToSeeQappInMenu
  }
  else 
  {
     
    // since choosing a wrong pers is quite common we check explicitly
    QappCore.DTTLogMessage(formatMessage("'|1' is not a valid valuelist item!", privilegeToSeeQappInMenu, ...), ..., DTTError)
  }
   
  QappCore.QappWeb.ENABLEQAPPDATA = if(EnableQAppData, Yes, No)
  QappCore.QappWeb.DEFAULTQAPP = if(isDefaultQApp, Yes, No)
  QappCore.QappWeb.HelpUrl = helpUrl
   
  // since once it occured that 'null' in generateUserMenu was passed and this caused an odd runtime exception we intentionally look for null and replace with false so the case is handled
  boolean userMenuMustBeGenerated = nullValue(generateUserMenu, false)
   
  if (userMenuMustBeGenerated)
  {
    IDCommand userCommandSet = new()
    QappCore.addCommandSet(userCommandSet)
    QappCore.QappWeb.UserCommandSet = userCommandSet
  }
   
  QappCore.QappWeb.UsesCustomQualibusTables = supportCustomizationOfQualibusDatabase
  QappCore.QappWeb.MaxSupportedScriptNumber = customQualibusVersionNumber
   
  QappCore.QappRuntimeBehavior.init()
  QappCore.QappRuntimeBehavior.QappInitialized = true
   
   
  if (allowUseOnFutureQualibusVersion)
  {
    DevTools.ToBeReviewed("in principle if true doLogin should avoid the DB version check")
    QappCore.DTTLogMessage("allowUseOnFutureQualibusVersion is not yet supported", ..., DTTError)
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void QappCore.setQappRuntimeBehavior(
  boolean allowLoginToUnlimitedUsersOnly                     // 
  optional boolean ignoreAttivoForLoginForUnlimitedUsers = 0 // 
  optional boolean enableHomeLink = 0                        // 
  optional boolean displayFooter = 0                         // 
  optional boolean handleSelectedRowHiglighted = 0           // 
  optional boolean permanentTokens = 0                       // if true tokens are consumed, needed in case the same link must be handled more times
  optional boolean mobileFirst = 0                           // 
  optional boolean executeCustomPostLoginCode = 0            // 
  optional boolean supportInteractionWithQualibusForms = 0   // 
)
{
  boolean initalizationError = false
  if (!(QappCore.QappRuntimeBehavior.QappInitialized))
    initalizationError = true
   
  QappCore.QappRuntimeBehavior.AllowLoginToUnlinmitedUsers = allowLoginToUnlimitedUsersOnly
  QappCore.QappRuntimeBehavior.IgnoreAttivoForUnlimitedUsersLogin = ignoreAttivoForLoginForUnlimitedUsers
  QappCore.QappRuntimeBehavior.HomeLinkEnabled = enableHomeLink
  QappCore.QappRuntimeBehavior.DisplayFooter = displayFooter
  QappCore.QappRuntimeBehavior.HandleSelectedRowHighligted = handleSelectedRowHiglighted
  QappCore.QappRuntimeBehavior.PermanentTokens = permanentTokens
  QappCore.QappRuntimeBehavior.MobileFirst = mobileFirst
  QappCore.QappRuntimeBehavior.ExecuteCustomPostLoginCode = executeCustomPostLoginCode
  QappCore.QappRuntimeBehavior.SupportInteractionWithQualibusForms = supportInteractionWithQualibusForms
   
  // throw at the end of the method to make sure the above code executes
  if (initalizationError)
    throw 0, "setQappRuntimeBehavior must be called after initalizeQApp!"
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public QappRuntimeBehavior QappCore.getQappRuntimeBehavior()
{
  return QappCore.QappRuntimeBehavior
}


// ──────────────────────────────────

// ************************************************************************************************************************************************************************************
// helper method that returns true if the command that started the session has Cruscotto Destination.
// 
// NOTE: This method works fine but at the moment of writing it is not used. it was written to solve a problem that has been solved in a simpler way, anyway since it is ok we keep it.
// ************************************************************************************************************************************************************************************
private boolean QappCore.isCruscottoCommandBeingExecuted()
{
   
  boolean cruscottoCommandBeingExecuted = false
   
  QappCommand currentCommand = getCommandBeingExecuted()
   
  QappCommandParameters qcp = currentCommand.getQAppCommandParameters()
   
  if (qcp.Destination == Cruscotto)
  {
    cruscottoCommandBeingExecuted = true
  }
   
  return cruscottoCommandBeingExecuted
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void QappCore.checkThatSettingsJsonExistsAsExpected()
{
  string settingsFileName = QappCore.QappCommandHandler.getSettingsFilename()
  string expectedSettingsFileFullPath = QappCore.path() + FH.getSeparator() + settingsFileName
   
  if (settingsFileName != "")
  {
    if (!(fileExists(expectedSettingsFileFullPath)))
    {
      string errorMessage = formatMessage("The file '|1' specified in InitializeQapp has not been found in the output directory (expected path: '|2')", settingsFileName, expectedSettingsFileFullPath, ...)
       
      QappCore.DTTLogMessage(errorMessage, ..., DTTError)
    }
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void QappCore.addStyle(
  QappDataStyle qappDataStyle // 
)
{
  QappCore.QappDataStyles.add(qappDataStyle)
}


// ──────────────────────────────────

// ****************************************************************************************************************************************************
// close all open forms, the optional parameter formToSkipIndex allows to pass a form inded (obtained with form.me()) that can optionally not be closed
// ****************************************************************************************************************************************************
public void QappCore.closeAllForms(
  optional int formToSkipIndex = 0 // formindex of a specific form that should not be closed
)
{
  int numberOfOpenForms = QappCore.openFormsCount()
  for (int i = numberOfOpenForms - 1; i >= 0; i = i - 1)
  {
    IDForm idf = QappCore.getOpenForm(i)
    if (idf.index == formToSkipIndex)
      continue 
    idf.close(false)
  }
}


// ──────────────────────────────────

// **********************************************************************************
// this method creates on the DOM the js functions we need to communicate with Delphi
// **********************************************************************************
public void QappCore.addChromiumToDelphiCommunicationGlobalJavascriptFunction()
{
  string closeAppFunctionDefinition = "window.closePopup = function closePopup() {var obj = window.chrome.webview.hostObjects.sync.QAppBridge;console.log(obj);if (obj) {obj.ObjectMessage 
           ="<cmd_closeApp>";}}//# sourceURL=browsertools://customFunctions/closePopup.js"
  QappCore.executeOnClient(closeAppFunctionDefinition)
   
  string closeConfigFormFunctionDefinition = "window.closeConfigForm = function closeConfigForm(url) {var obj = window.chrome.webview.hostObjects.sync.QAppBridge; console.log(obj);    if (obj) {obj.
           ObjectMessage = "<cmd_closeConfig_" + url + ">";  }}//# sourceURL=browsertools://customFunctions/closeConfigForm.js"
  QappCore.executeOnClient(closeConfigFormFunctionDefinition)
   
  // this is for testing without delphi and without closure timer
  string closeConfigFormConsoleLogOnlyFunctionDefinition = "window.closeConfigFormConsoleLog = function closeConfigFormConsoleLog(url) {console.log("<cmd_closeConfig_" + url + ">");}//# 
           sourceURL=browsertools://customFunctions/closeConfigFormConsolLogOnly.js"
  QappCore.executeOnClient(closeConfigFormConsoleLogOnlyFunctionDefinition)
   
  string openModuleFunctionDefinition = "window.openModule = function openModule(kordId,mainId) {var obj = window.chrome.webview.hostObjects.sync.QAppBridge;console.log(obj);if (obj) {obj.ObjectMessage 
           ='<cmd_openModule|'+kordId+'|'+mainId+'|>';}}//# sourceURL=browsertools://customFunctions/openModule.js"
   
  QappCore.executeOnClient(openModuleFunctionDefinition)
   
  string openBrowserFunctionDefinition = "window.openBrowser = function openBrowser(url) {var obj = window.chrome.webview.hostObjects.sync.QAppBridge;console.log(obj);if (obj) {obj.ObjectMessage 
           ='<cmd_openBrowser|'+url+'|>';}}//# sourceURL=browsertools://customFunctions/openBrowser.js"
  QappCore.executeOnClient(openBrowserFunctionDefinition)
   
  DevTools.temporaryWorkaround("these js functions make sense to send messages to Delphi froma Qapp, so when we will have Qualibusweb only we can remove this method completely", "27.0", "not a workaround, 
        just a solution that makes sense if delphi app is in use", "if Delphi app is only LTS and Qualibus web is used by all customers  this method can be deleted (it must anyway survive in the old Qappcore we 
        use for LTS. If we keep updating inde version to maintain qappcore lts we will likely need to write "30.0" in the inde version parameter to avoid problems")
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void QappCore.setSessionAsStartedFRomLOGINURLCommand(
  boolean value // 
)
{
  QappCore.SessionStartedFromLOGINURLCommand = value
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean QappCore.sessionStartedFromLoginUrlCommand()
{
  return QappCore.SessionStartedFromLOGINURLCommand
}


// ──────────────────────────────────

// **********************************************************************
// returns true if the current session has been started by a web api call
// **********************************************************************
public boolean QappCore.isSessionStartedByApiCall()
{
  return QappCore.SessionStartedByWebapi
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void QappCore.markSessionAsStartedByWebApi()
{
  QappCore.SessionStartedByWebapi = true
}


// ──────────────────────────────────

// *************************************************************
// returns true if the current session is a Qualibus Web session
// *************************************************************
public boolean QappCore.isSessionAQualibusWebSession()
{
  return QappCore.InQualibusWebSession
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void QappCore.prepareUserCommandSet()
{
  string vCompanyName = ""
  select into variables (found variable)
    set vCompanyName = DESCRDITTA1
  from 
    TABPARAMETRI1 // master table
   
  IDCommand userCommandSet = QappCore.QappWeb.UserCommandSet
   
  userCommandSet.setIsCommandSet(true)
  userCommandSet.caption = "{{icon-fa-cogs}} " + vCompanyName
  userCommandSet.setIsMenu(true)
  userCommandSet.visible = true
  userCommandSet.enabled = true
  userCommandSet.code = UserMenu
  userCommandSet.className = "right-commandset"
   
  IDCommand greetingCommand = new()
  greetingCommand.caption = "Ciao " + QappCore.Loggeduser.DESCRUTENTE
  greetingCommand.visible = true
  greetingCommand.enabled = false
  userCommandSet.addCommand(greetingCommand)
   
  IDCommand helpCommand = new()
  helpCommand.caption = "{{icon-fa-question-circle-o}} Help"
  helpCommand.visible = true
  helpCommand.enabled = true
  helpCommand.code = Help
  userCommandSet.addCommand(helpCommand)
   
  IDCommand assistenzaCommand = new()
  assistenzaCommand.caption = "{{icon-fa-info-circle}} Assistenza"
  assistenzaCommand.visible = true
  assistenzaCommand.enabled = true
  assistenzaCommand.code = Assistenza
  userCommandSet.addCommand(assistenzaCommand)
   
  IDCommand infoCommand = new()
  infoCommand.caption = "{{icon-fa-info-circle}} Info"
  infoCommand.visible = true
  infoCommand.enabled = true
  infoCommand.code = Info
  userCommandSet.addCommand(infoCommand)
   
  IDCommand qualibusCommand = new()
  qualibusCommand.caption = "www.qualibus.it"
  qualibusCommand.visible = true
  qualibusCommand.enabled = true
  qualibusCommand.code = SitoQualibus
  userCommandSet.addCommand(qualibusCommand)
   
  IDCommand passwordCommand = new()
  passwordCommand.caption = "{{icon-fa-key}} Cambia password"
  passwordCommand.visible = true
  passwordCommand.enabled = true
  passwordCommand.code = CambiaPassword
  userCommandSet.addCommand(passwordCommand)
   
  IDCommand logoffCommand = new()
  logoffCommand.caption = "{{icon-fa-sign-out}} Esci"
  logoffCommand.visible = true
  logoffCommand.enabled = true
  logoffCommand.code = Logout
  userCommandSet.addCommand(logoffCommand)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void QappCore.handleUserMenuCommands(
  IDCommand CommandSet // 
  IDCommand Command    // 
  inout boolean Cancel // 
)
{
  QappCore.DTTLogMessage(Command.code, ...)
  if (CommandSet.code == UserMenu)
  {
    switch (Command.code)
    {
      case Help:
         QappCore.DTTLogMessage(QappCore.QappWeb.HelpUrl, ...)
         QappCore.openDocument(QappCore.QappWeb.HelpUrl, true, "target="_blank"")
      break
      case Assistenza:
         QappCore.openDocument("mailto:support@nordest.systems", true, "target="_blank"")
      break
      case SitoQualibus:
         QappCore.openDocument("https://www.qualibus.it", true, "target="_blank"")
      break
      case Info:
         string appInfoMessage = formatMessage("|1 versione |2, copyright |3 Nord Est Systems Srl\n", QappCore.QappWeb.Name, getQappVersion(), year(today()), ...)
         Privilegio p = QappCore.Loggeduser.PrivilegeForCurrentQapp
         string privilegeString = p.getPrivilegeDescription()
         if (privilegeString == "")
           privilegeString = "Non hai privilegi su questa Qapp"
         else 
           privilegeString = "I tuoi privilegi su questa Qapp: " + privilegeString
         string userInfoMessage = formatMessage("Sei loggato come |1\n|2", QappCore.Loggeduser.DESCRUTENTE, privilegeString, ...)
          
          
         QappCore.messageBox(appInfoMessage + userInfoMessage)
      break
      case CambiaPassword:
         ChangePasswordForm.showFor(QappCore.Loggeduser, false, false)
      break
      case Logout:
         Tools.performExit(...)
      break
    }
  }
}


// ──────────────────────────────────

// ***************************
// Create reference for eventi
// ***************************
private void QappCore.CreateRiferimenti()
{
}


// ──────────────────────────────────

// ******************************
// Create disposizioni for eventi
// ******************************
private void QappCore.CreateDisposizioni()
{
}


// ──────────────────────────────────

// ***********************
// Create Costi for eventi
// ***********************
private void QappCore.CreateCosti()
{
}


// ──────────────────────────────────

// ******************************************************
// Initialize Default Values for new evento to be created
// ******************************************************
private void QappCore.InitializeDefaultValuesforNewEvento()
{
   
}


// ──────────────────────────────────

// **********************************************************
// returns an array with hthe expected script names
// for 0: init0.sql only
// for 3(example): init0.sql, init1.sql, init2.sql, init3.sql
// **********************************************************
private IDArray QappCore.getInitializationScriptsFileNames(
  int maxScriptNumber // 
)
{
  if (maxScriptNumber < 0)
  {
    QappCore.DTTLogMessage("getInitializationScriptsFielNames: maxScriptNumber must be positive", ..., DTTError)
  }
  IDArray filenames = new()
   
  for (int i = 0; i <= maxScriptNumber; i = i + 1)
  {
    filenames.addValue(formatMessage("init|1.sql", i, ...))
  }
   
  return filenames
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void QappCore.setLoopsDebugBehavior()
{
  // to avoid that an app published with Debug truncates the loops to 1000 (this is what happens with Debug on) we force the MaxLoopCycles to be super big so the behavior of the app with debug will be the same
  // than the app without debug (remember the bad experience with GOrtani where a deletion loop was truncated causing all records in a table being deleted: if this code would have been in place it would not have
  // happened. This is a Corrective Action)
  QappCore.DTTMaxLoopCycles = 10000000
   
}


// ──────────────────────────────────

// ***********************************************************************************
// adds info messages to the debug to inform the developer about the init. sql scripts
// 
// this is used internally by QappCore, no need to call it while developing a Qapp
// ***********************************************************************************
public void QappCore.writeInfoMessagesInDebugForInitalizationScripts(
  int maxScriptNumber // 
)
{
  if (maxScriptNumber >= 0)
  {
    QappCore.DTTLogMessage(formatMessage("maxScriptNumber is |1, QappCore expects the following files exist:", maxScriptNumber, ...), ..., DTTInfo)
     
    IDArray initScriptsFilenames = getInitializationScriptsFileNames(maxScriptNumber)
    for (int i = 0; i < initScriptsFilenames.length(); i = i + 1)
    {
      QappCore.DTTLogMessage(initScriptsFilenames.getValue(i), ..., DTTInfo)
    }
  }
  else 
  {
    QappCore.DTTLogMessage("maxScriptNumber is less than 0, this case is not handled", ..., DTTError)
  }
}


// ──────────────────────────────────



// ──────────────────────────────────

// ***********************************************
// call this method when initializing Qualibus web
// ***********************************************
public void QappCore.initializeQualibusWeb()
{
   
  string mainCaptionInLowerCase = lower(QappCore.mainCaption)
  boolean applicationNameContainsQualibus = find(mainCaptionInLowerCase, "qualibus", ...) > 0
   
   
  // protection to avoid unexpected usage of the method
  if (!(applicationNameContainsQualibus))
  {
    QappCore.DTTLogMessage("it is impossible to call initializeQualibusWeb outside of Qualibus web!", ..., DTTError)
    return 
  }
   
  // marking current session as Qualibus web session
  QappCore.InQualibusWebSession = true
}


// ──────────────────────────────────

// ******************************************************************************************************************************************************
//    starts QAPP_CORE_SESSION session that will be used for multiple things, at the moment of writing it is used only for 
// 1. token reconciliation (to avoid a problem with database transactions we postpone the update of NGT_TOKENS table, see LoginToken.reconcileToken() for
//  details
// 2. clearReportsFolder (see timer below)
// 3. in java only runs the filestream workaround (see timer below)
//   QappCore.startSession("QAPP_CORE_SESSION", ...)
// ******************************************************************************************************************************************************
public void QappCore.startQappCoreSessionsAndTimers()
{
  QappCore.startSession(QappCoreSession, ...)
   
  // ONE_WAY_UPGRADER_SESSION started only for Qualibus, non Qapps
  boolean oneWayUpgraderSessionShouldBeStarted = OneWayUpgrader.dedicatedServerSessionShouldBeStarted()
  if (oneWayUpgraderSessionShouldBeStarted)
  {
    QappCore.startSession(OneWayUpgraderSession, ...)
  }
   
  if (X.inServerSession())
  {
    string currentServerSessionName = QappCore.sessionName()
     
    switch (currentServerSessionName)
    {
      case QappCoreSession:
         ClearReportsFolderTimer.enabled = true
          
//         java
//         {
//           FilestreamWorkaroundHourlyQuery.enabled = true
//         }
          
         BackgroundWorker.executeCommand(createMissingPrivilegi)
      break
      case OneWayUpgraderSession:
         OneWayUpgraderTimer.enabled = true
      break
    }
  }
   
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public void QappCore.setHelper(
  IDDocumentHelper helper // 
)
{
  QappCore.documentHelper = helper
}


// ──────────────────────────────────

// ***************************************************************************************************************************************************************************************
// Store login info into cookies, it is meant to be called always, in case no data needs to be saved it will simply write empty cokies, this is done to ensure cookies are somehow cleared
// ***************************************************************************************************************************************************************************************
private void QappCore.setCookieLogin(
  string pUsername // 
  string pPassword // 
  string pCheckbox // 
)
{
  if (pCheckbox == "on")
  {
     
    SimpleLoginInfoEncrypter slie = new()
     
    // user and password are encoded in base64 with a unique separator
    string encryptedLoginInfo = slie.encrypt(pUsername, pPassword)
     
    QappCore.saveSetting("qualibus", "cookie", encryptedLoginInfo)
    QappCore.saveSetting("qualibus", "savecheckbox", "checked")
  }
  else 
  {
    QappCore.saveSetting("qualibus", "cookie", "")
    QappCore.saveSetting("qualibus", "savecheckbox", "")
  }
}


// ──────────────────────────────────

// ***************************************************************************************************************************************************************************************
// Store login info into cookies, it is meant to be called always, in case no data needs to be saved it will simply write empty cokies, this is done to ensure cookies are somehow cleared
// ***************************************************************************************************************************************************************************************
private void QappCore.getCookieLogin(
  inout string pUsername // 
  inout string pPassword // 
  inout string pCheckbox // 
)
{
  string retrievedUsername = ""
  string retrievedPassword = ""
  string retrievedCookie = CookieHelper.safelyReadCookies("qualibus", "cookie")
  string retrievedBlock = retrievedCookie
   
   
  SimpleLoginInfoEncrypter slie = new()
  slie.decrypt(retrievedBlock, retrievedUsername, retrievedPassword)
   
  pUsername = retrievedUsername
  pPassword = retrievedPassword
  retrievedCookie = CookieHelper.safelyReadCookies("qualibus", "savecheckbox")
  pCheckbox = retrievedCookie
}


// ──────────────────────────────────

// ***********************************************************
// TEMPORARLY COMMENTED UNTIL WE FIND A WAY TO USE IT COMMONLY
// ***********************************************************
public void QappCore.StoreLoginFormValuesInCookies(
  inout string userName // 
  inout string password // 
)
{
  if (X.inServerSession())
  {
    QappCore.DTTLogMessage("since this code deals with cookies and those do not make sense in server session we return immediately", ..., DTTInfo)
    return 
  }
  string valCheckBox = ""
   
  string retrievedCookie = CookieHelper.safelyReadCookies(Form, "remme")
  valCheckBox = retrievedCookie
  QappCore.setCookieLogin(userName, password, valCheckBox)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void QappCore.populateLoginFormUIFromCookies()
{
  if (X.inServerSession())
  {
    QappCore.DTTLogMessage("since this code deals with cookies and those do not make sense in server session we return immediately", ..., DTTInfo)
    return 
  }
   
  string username = ""
  string password = ""
  string checkbox = ""
   
  QappCore.getCookieLogin(username, password, checkbox)
   
  if (checkbox == "checked")
  {
    QappCore.userName = username
     
    // PWDCOOKIE only goal is to be read by login screen
    QappCore.PWDCOOKIE = password
    QappCore.REMMECOOKIE = checkbox
  }
}


// ──────────────────────────────────

// ****************************************************************************************************************************************************************************************
// Stores in cookies a loginMessage that at the next execution it will be displayed at the login form.
// 
// Suggested usage:
// before terminating the application with Exit("???") call this method passing a meaningful login message
// 
// to display the message at the next login you must call displayStoredLoginMessageFromPreviousSession, anyway it is called already in DOQualibus.Initialize so there is no need to call it
// ****************************************************************************************************************************************************************************************
public void QappCore.setLoginMessageForNextSession(
  string loginMessage // 
)
{
  string spacesFreeMesssage = replace(loginMessage, " ", "_$*$_")
   
  QappCore.saveSetting("qualibus", "loginmessage", spacesFreeMesssage)
}


// ──────────────────────────────────

// ******************************************************************************
// reads from cookies any stored message and if found display at the login screen
// 
// No need to call this since it is called in component Intialize
// 
// NB: this method is public otherwise seLoginMessage would not be applied
// ******************************************************************************
public void QappCore.displayStoredLoginMessageFromPreviousSession()
{
  string retrievedCookie = CookieHelper.safelyReadCookies("qualibus", "loginmessage")
  string storedMessage = retrievedCookie
  if (storedMessage != "")
  {
    storedMessage = replace(storedMessage, "_$*$_", " ")
     
    QappCore.QappCommandHandler.writeLoginMessage(storedMessage)
    //  
    // clears the cookie since already used
    QappCore.saveSetting("qualibus", "loginmessage", "")
  }
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void ChangePasswordForm.FocusTimer()
{
  changeutentepassword.Password.setFocus(...)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void ChangePasswordForm.showFor(
  Utente utente                      // Write a comment for this parameter or press backspace to delete this comment
  optional boolean forcedChange = 0  // 
  optional boolean exitOnCancel = -1 // 
)
{
  if (!(utente))
  {
    this.close(...)
    return 
  }
   
  this.show(Modal)
   
  utente.Password = ""
  utente.NewPassword = ""
  utente.ConfirmNewPassword = ""
   
  changeutentepassword.setDocument(utente, true)
   
   
  changeutentepassword.Labelinfolabel.caption = formatMessage("Modifica password per <b>|1 (|2)</b>", utente.Username, utente.DESCRUTENTE, ...)
   
  this.ForcedChange = forcedChange
  this.Exitoncancel = nullValue(exitOnCancel, true)
  if (forcedChange)
  {
    QappCore.messageBox("La tua password è stata reimpostata ed è necessario cambiarla")
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void ChangePasswordForm.ButtonCancel()
{
   
  if (this.Exitoncancel)
    Tools.performExit(...)
  else 
  {
    this.close(false)
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void ChangePasswordForm.ButtonChangePassword(
  inout boolean cancel // 
)
{
  Utente u = changeutentepassword.document
   
  string message = ""
   
  boolean currentPasswordTypedCorrectly = u.isPasswordCorrect()
   
  if (currentPasswordTypedCorrectly)
  {
    if (u.NewPassword == u.ConfirmNewPassword)
    {
      string changePasswordErrorMessage = ""
      boolean passwordChangedSuccesfully = u.SetNewPasswordInDatabase(changePasswordErrorMessage)
       
      if (passwordChangedSuccesfully)
      {
         message = "Password modificata con successo"
         cancel = false
      }
      else 
      {
         message = changePasswordErrorMessage
         cancel = true
      }
    }
    else 
    {
      message = "La nuova password non coincide con la conferma password"
      cancel = true
    }
  }
  else 
  {
    message = "La password digitata non è corretta"
    cancel = true
  }
   
  QappCore.messageBox(message)
//  this.showMessage(ERROR, message, "", "", false)
}


// ──────────────────────────────────

// ********************************************
// Event raised by the form before it is closed
// ********************************************
event ChangePasswordForm.Unload(
  inout int Cancel // If set to True, closure of the form is canceled.
  boolean Confirm  // Represents the user's selection for closing modal forms. True if the user confirms their choice, otherwise False.
)
{
  if (Confirm)
  {
    boolean cancelClosure = false
    this.ButtonChangePassword(cancelClosure)
    Cancel = cancelClosure
  }
}


// ──────────────────────────────────

// ***********************************************
// Event raised to the form when loading in memory
// ***********************************************
event ChangePasswordForm.Load()
{
  changeutentepassword.showInfoMessages = false
  changeutentepassword.collapsable = false
  JavascriptTimer.enabled = true
  changeutentepassword.Password = ""
  changeutentepassword.NewPassword = ""
  changeutentepassword.ConfirmNewPassword = ""
   
  this.formHandledKeys = KeysEnterEsc | KeysAlphaNumerical | KeysMovement
   
  boolean inFluid = X.isFluidInUse()
  this.CSSClass = if(inFluid, "change-password-form-fluid", "change-password-form-rd3")
}


// ──────────────────────────────────

// **************************************************************************************************
// Event raised by the form when the user has clicked a button it contains for which it is registered
// **************************************************************************************************
event ChangePasswordForm.OnKeyPress(
  int Frame          // Specifies the frame on which the key was pressed
  int:keySet KeySet  // Grouping of the pressed key
  int KeyCode        // Code of the pressed key; this is the code supplied by the Javascript engine.
  inout boolean Skip // Set to True to prevent the event from being raised to the application.
)
{
  // This is a trick to force the password to be empty on pressing DEL, it is a workaround because sometimes on Edge the autocomplete feature is "too aggressive" and this is the only way to have a really empty
  // password field, note: this makes sense when the current passoword is ''
   
  QappCore.DTTLogMessage("you pressed " + toString(KeyCode), ..., DTTInfo)
  int delKeyCode = 46
  if (KeyCode == delKeyCode)
  {
    changeutentepassword.Password = ""
  }
}


// ──────────────────────────────────

// **********************************************************************
// Allows the visual properties of individual panel cells to be adjusted.
// **********************************************************************
event ChangePasswordForm.changeutentepassword.OnDynamicProperties()
{
  Utente u = changeutentepassword.document
   
  if (u)
  {
    boolean passwordsHasBeenTyped = (length(u.Password) > 0) and (length(u.NewPassword) > 0)
    boolean passwordsMatch = u.NewPassword == u.ConfirmNewPassword
    boolean newPasswordIsDifferent = u.Password != u.NewPassword
     
  }
   
}


// ──────────────────────────────────

// ***************************************************************************************************************************************************************************
// this code clears the password field in the browser, it is done in a 500 ms timer to execute after 350ms, that is inde timing to set the autocomplete to on, at least in rd3
// ***************************************************************************************************************************************************************************
public void ChangePasswordForm.JavascriptTimer()
{
  string rd3Id = changeutentepassword.Password.getRD3ID(...)
  string jsCommand = formatMessage("*document.getElementById("|1").value = "";document.getElementById("|1").setAttribute('autocomplete', 'off');", rd3Id, ...)
  QappCore.executeOnClient(jsCommand)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void ChangePasswordForm.Buttonok()
{
  this.close(true)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void QappCore.ShowChangePasswordTimer()
{
  ChangePasswordForm.showFor(QappCore.Loggeduser, true, ...)
}


// ──────────────────────────────────

// **********************************************************************************
// the connection of CustomQualibus database is set copying it from Qualibus database
// **********************************************************************************
public void QappCore.forceSqlServerDBConnection(
  string server   // ip,port
  string database // 
  string username // admin username
  string password // 
)
{
   
  // 
  .net
  {
    string connectionString = formatMessage("Data Source=|1;Initial Catalog=|2;Persist Security Info=False", server, database, ...)
     
    QualibusDB.defaultConnectionString = connectionString
    QualibusDB.defaultUserID = username
    QualibusDB.defaultPassword = password
     
    DatabaseConnector.setCustomQualibusDatabaseConnection()
     
  }
//  // 
//  java
//  {
//    QappCore.DTTLogMessage("forceDBConnection is intended for use in .net", ..., DTTError)
//  }
}


// ──────────────────────────────────

// ************************************************************
// Writes Access Info  in SW9_ACCESS_LOG for the logged in user
// ************************************************************
public void QappCore.writeAccessLog()
{
  string:sequenza accessLogSequenceName = "NE9N_ACCESS_LOG"
   
  boolean sequenceFound = false
  int maxAccessLogId = 0
  select into variables (sequenceFound)
    set maxAccessLogId = LASTID
  from 
    SW9SEQUENCES // master table
  where
    SEQNAME == accessLogSequenceName
   
  if (sequenceFound)
  {
    insert values into SW9ACCESSLOG
      set IDACCESSLOG = maxAccessLogId + 1
      set WINUSER = Tbluserparameters.Winuser
      set DBUSER = Tbluserparameters.Codutente
      set COMPUTERNAME = Tbluserparameters.Computername
      set ACCESSDATE = now()
      set OPTYPE = "O"
    update SW9SEQUENCES
      set LASTID = maxAccessLogId + 1
    where
      SEQNAME == accessLogSequenceName
     
     
    DevTools.RefactoringOpportunity("This method was copied from Intranetdocs to use DO Qualibus there, it clearly weak and improvable")
     
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean QappCore.ignoreDBVersionCheck()
{
  boolean ignoreDBVersion = VersionInfo.getIgnoreDBVersion()
   
  return ignoreDBVersion
}


// ──────────────────────────────────

// ****************************************************************************************************
// checks whether username and passowrd are good crdentials: done for sql server and windows user
// 
// if setLoggedUsed is true the loggeduser is loaded from he username (not clear who uses this feature)
// ****************************************************************************************************
public boolean QappCore.checkCredentials(
  string username                    // 
  string password                    // 
  optional boolean setLoggedUsed = 0 // 
)
{
  boolean validCredentials = false
   
  boolean usernameContainsBackSlash = find(username, "\\", ...) > 0
  if (usernameContainsBackSlash)
  {
    validCredentials = checkCredentialsForWindowsUser(username, password)
  }
  else 
  {
    validCredentials = checkCredentialsForSqlServerUser(username, password)
  }
   
  boolean displayErrorForSetLoggedUser = false
  string checkCredentialsDTTMessage = formatMessage("valid Credentials for |1/|2", username, password, ...)
  int:DTTMessageTypes dttMessageType = DTTInfo
  if (validCredentials)
  {
    if (setLoggedUsed)
    {
      Utente retrievedUtente = new()
      retrievedUtente.Username = username
      try 
      {
         retrievedUtente.loadFromDB(...)
      }
      catch 
      {
         retrievedUtente = null
      }
      if (retrievedUtente)
      {
         QappCore.Loggeduser = retrievedUtente
      }
      else 
      {
         displayErrorForSetLoggedUser = true
      }
    }
  }
  else 
  {
    checkCredentialsDTTMessage = "NON " + checkCredentialsDTTMessage
    boolean inUnitTestApp = find(lower(QappCore.mainCaption), "unit", ...) > 0
    dttMessageType = if(inUnitTestApp, DTTInfo, DTTError)
  }
  QappCore.DTTLogMessage(checkCredentialsDTTMessage, ..., dttMessageType)
   
  if (displayErrorForSetLoggedUser)
  {
    QappCore.DTTLogMessage(formatMessage("Impossible to setLoggedUser for username |1", username, ...), ..., dttMessageType)
  }
   
  return validCredentials
}


// ──────────────────────────────────

// *****************************************************************************************
// the passed parameters are used to perform a login, it returns true for valid credentials 
// if setLoggedUser is true the loggedUser is also set
// *****************************************************************************************
private boolean QappCore.checkCredentialsForSqlServerUser(
  string username // 
  string password // 
)
{
  boolean validCredentials = false
   
  QualibusDatabaseHelper qdh = QualibusDatabaseHelper.create()
   
  qdh.takeSnapshotOfConnection()
   
  QualibusDB.defaultUserID = username
  QualibusDB.defaultPassword = password
   
  int maxDBVers = 0
  try 
  {
    select into variables (found variable)
      set maxDBVers = max(DBVERS)
    from 
      SW9DB // master table
     
    // restore the connection string even in case of success...
    qdh.applyStoredSettings()
  }
  catch 
  {
    maxDBVers = 0
     
    // ...than in case of failure
    qdh.applyStoredSettings()
  }
   
  if (maxDBVers > 0)
  {
    validCredentials = true
  }
  else 
  {
    validCredentials = false
  }
  return validCredentials
}


// ──────────────────────────────────

// *****************************************************************************************
// the passed parameters are used to perform a login, it returns true for valid credentials 
// if setLoggedUser is true the loggedUser is also set
// *****************************************************************************************
private boolean QappCore.checkCredentialsForWindowsUser(
  string username // 
  string password // 
)
{
  boolean validCredentials = false
   
  TabParametri tp = TabParametri.getInstance()
   
  string ldapServerAddress = tp.getLdapCompleteAddress()
   
  boolean canConnectToLdap = canConnectToLdapServer(ldapServerAddress, username, password)
   
  validCredentials = canConnectToLdap
   
  return validCredentials
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public void QappCore.setForceIgnoreDBVersion(
  boolean value // 
)
{
  QappCore.ForceIgnoreDBVersion = value
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public boolean QappCore.getForceIgnoreDBVersion()
{
  return QappCore.ForceIgnoreDBVersion
}


// ──────────────────────────────────

// ********************************************************************************************************************************************************
// method that given the 4 classic connection parameters(server,database, user and password) allows to connect to the Qualibus and CustomQualibus databases
// 
// this method is conveinent if used in setConnectionString into the unit test connection managers of Qapps
// ********************************************************************************************************************************************************
public void QappCore.connectToQualibusDatabase(
  string server   // 
  string database // 
  string username // 
  string password // 
)
{
  QualibusDB.closeConnection()
  CustomQualibus.closeConnection()
  QualibusDB.defaultUserID = username
  QualibusDB.defaultPassword = password
   
  net code
  {
    QualibusDB.defaultConnectionString = formatMessage("Data Source=|1;Initial Catalog=|2;Persist Security Info=False;", server, database, ...)
  }
//  java code
//  {
//    QualibusDB.defaultConnectionString = "driver=com.microsoft.sqlserver.jdbc.SQLServerDriver\n        url=jdbc:sqlserver://" + server + ";databaseName=" + database + ";encrypt=true;trustServerCertificate=t­
//             rue;"
//    QappCore.DTTLogMessage(formatMessage("connectToQualibusDatabase, jdbc connection string: |1", QualibusDB.defaultConnectionString, ...), ..., DTTInfo)
//  }
   
  CustomQualibus.defaultConnectionString = QualibusDB.defaultConnectionString
  CustomQualibus.defaultUserID = QualibusDB.defaultUserID
  CustomQualibus.defaultPassword = QualibusDB.defaultPassword
}


// ──────────────────────────────────

// ***********************************************************************
// Event raised to the document to determine the value of a named property
// ***********************************************************************
event VersionInfo.OnGetNamedPropertyValue(
  string PropertyName        // The name of the named property whose value to retrieve.
  inout string PropertyValue // An output parameter containing the value of the named property.
)
{
  if (PropertyName == "version")
  {
    PropertyValue = "Q2026.260.DEV"
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static int VersionInfo.getSupportedScriptNumber()
{
  // this number should be changed only in dev so the branches (that are "fixed script versions" will have the correct value)
  return 978
}


// ──────────────────────────────────



// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static string VersionInfo.getQualibusVersion()
{
  // this string should be changed only in dev so the branches (that are about a "specific Qualibus version" will have the correct value)
   
   
  VersionInfo vi = new()
  string qappCoreVersion = vi.getNamedPropertyValue("version")
   
  return qappCoreVersion
}


// ──────────────────────────────────

// ****************************************************
// Method to retrieve Customdata type from ID CDATA FLD
// ****************************************************
private int AltreAnagrafiche.GetCustomDataType(
  int IDCDFld // 
)
{
  int:cdataType CDType = 0
  MainModuleDatoPersonalizzatoInfo cfi = new()
  cfi.IDCDATAFLD = IDCDFld
  try 
  {
    cfi.loadFromDB(0)
    CDType = cfi.Type
  }
  return CDType
}


// ──────────────────────────────────

// **********************************************************
// Method to find out if any Custom data is inserted /updated
// **********************************************************
public boolean AltreAnagrafiche.AnyCustomDataModified()
{
  boolean AnyCustomDataModified = false
  AnyCustomDataModified = ((CDATABOOLEANVALUES.isModified()) or (CDATAMODULEVALUES.isModified()) or (CDATATEXTVALUES.isModified()) or (isModified(CDATAMEMOVALUES.isModified())) or (CDATADATEVALUES.isModified(
           )) or (CDATAINTEGERVALUES.isModified()) or (CDATAFLOATVALUES.isModified()) or (CDATACOMBOVALUES.isModified()))
   
  return AnyCustomDataModified
}


// ──────────────────────────────────

// *********************************************************
// Duplicate exisiting infrastrucure and return it to caller
// *********************************************************
public AltreAnagrafiche AltreAnagrafiche.DuplicateAnagraficantDEPRECATED(
  int DuplicatedByUserId // 
)
{
   
  // IN OCS WE STILL USE THE DEPRECATED METHOD (29/01/2026) because it is used in a NON TESTED METHOD, we need to add a UT of that method so we can finally use duplciateCespie and remove this
  DevTools.DEPRECATE("DuplicateAnagrafica obsolete and use only for OCS", "in OCS we avoid calling it and we call only duplicateCespite, than we manually set the needed fields", "delete this method", "26.0", 
        toDate(2026, 6, 1))
   
  // create new cespite by using inde duplicate (without child so it will copy all properties to new one)
  AltreAnagrafiche c = new()
  c.init()
   
  // create new by duplicating existing cespite
  // caution init must be done before it
   
  c = duplicate(0, ...)
   
  // change relevant Properties values
  c.ID = Sequence.getNextSequence(CESN_ID_CESPITE, ...)
  c.Code = Code + " - copy"
  c.Description = trim(Description) + " - copy"
  c.Insertedbyuserid = DuplicatedByUserId
   
  // duplicate rest of the properties
  c.DuplicateCespite(this)
   
  return c
   
}


// ──────────────────────────────────

// **************************************************************
// Create Custom data of all types by reading modello customdata 
// **************************************************************
private void AltreAnagrafiche.DuplicateCustomDataFromSource(
  AltreAnagrafiche SourceInfr // 
)
{
  base.loadDatiPers()
  for each DatoPersonalizzato MainModuleDatiPersonalizzatiBase in SourceInfr.DatiPersonalizzati
  {
    string custodataValue = MainModuleDatiPersonalizzatiBase.getValueAsString()
    if (custodataValue != "")
    {
      MainModuleDatoPersonalizzatoInfo cfi = cast(MainModuleDatiPersonalizzatiBase.getCustomDataInfo())
      base.setDatoPersonalizzato(custodataValue, cfi)
    }
  }
   
}


// ──────────────────────────────────

// ***********************************************
// duplicate programmi intervento rows from source
// ***********************************************
public void AltreAnagrafiche.DuplicateProgrammiIntervento(
  AltreAnagrafiche SourceInfr // 
)
{
  // loop programma rows and create copy of new programma intervento to add in to new collection
  for each ProgrammaIntervento p in SourceInfr.ProgrammiIntervento
  {
    ProgrammaIntervento pi = new()
    pi.init()
     
    // create new by duplicating source programma intervento
    // caution init must be done before it
    pi = (ProgrammaIntervento)p.duplicate(0, ...)
     
    // we dont used create because the duplicate set's the main information based on the modello directly
     
    pi.IDCESPITE = p.IDCESPITE
    pi.ChecklistTemplate = Checklist.factory(ProgrammaIntervento, pi, ...)
     
    // update information to now (we don't want old data)
    pi.IDUTENTEINS = Insertedbyuserid
    pi.DATAINS = now()
    pi.IDUTENTEULTMOD = Insertedbyuserid
    pi.DATAULTIMAMOD = now()
     
//    this.addChecklistItemsFromSource(p, pi)
    this.DuplicateMailsenderNTF(pi, p)
    ProgrammiIntervento.add(pi)
  }
}


// ──────────────────────────────────

// ***************************
// duplicate Roles from source
// ***************************
public void AltreAnagrafiche.DuplicateRoles(
  AltreAnagrafiche SourceInfr // 
)
{
  // Duplicate and add all permission record to new collection from source
  for each CESPERMESSI cespermessi in SourceInfr.CESPERMESSI
  {
    CESPERMESSI cp = (CESPERMESSI)cespermessi.duplicate(0, ...)
    cp.init()
    cp.ID = ID
    CESPERMESSI.add(cp)
  }
   
}


// ──────────────────────────────────

// ********************************
// Duplicate references from source
// ********************************
public void AltreAnagrafiche.DuplicateReferences(
  AltreAnagrafiche SourceInfr // 
)
{
  for each Riferimento r in SourceInfr.Riferimenti
  {
    Riferimento ref = new()
    ref.init()
    ref.IDRiferimento = Sequence.getNextSequence(EVAN_ID_EVA_REFERENCES, ...)
    ref.IDTipoRiferimento = r.IDTipoRiferimento
    ref.IDEvento = ID
    ref.IDForAll = r.IDForAll
    ref.ISMODELLO = No
    Riferimenti.add(ref)
  }
}


// ──────────────────────────────────

// ***********************************************
// duplicate all properties of cespite from source
// ***********************************************
public void AltreAnagrafiche.DuplicateCespite(
  AltreAnagrafiche SourceInfr // 
)
{
  // duplicate CES_PERMESSI properties
  this.DuplicateRoles(SourceInfr)
   
  // duplicate references
  this.DuplicateReferences(SourceInfr)
   
  // duplicate custom data
  this.DuplicateCustomDataFromSource(SourceInfr)
   
  // duplicate programmi intervento
  this.DuplicateProgrammiIntervento(SourceInfr)
}


// ──────────────────────────────────

// **************************************************************************************************************************
// method to Duplicate all NTF recipients and create corresponding status programmi intervento's mailsender NTF notifications
// **************************************************************************************************************************
private void AltreAnagrafiche.DuplicateMailsenderNTF(
  ProgrammaIntervento programma        // Programma for which NTF to be added
  ProgrammaIntervento ModelloProgramma // Modello programma to get collection of NTF recipients
)
{
  Ntfrecipient ntf = null
   
  // create NTF object based on the modello NTF (for all types of Recipents)
  for each Ntfrecipient n in ModelloProgramma.NTFRECIPIENTS
  {
    ntf = this.CreateNTFRecipient(programma, n)
    programma.NTFRECIPIENTS.add(ntf)
  }
   
  // create NTF object based on the modello NTF (Recipent type <> others)
  // IMPORTANT : How to create NTF for Rif personale? because we dont have ID Dipendente of reference, moreover there can be multiple perosnale in reference
  // this must be handled from outside after calling Create Evento method.
   
}


// ──────────────────────────────────

// **********************************************
// method to NTF recipient from Modello recipient
// **********************************************
private Ntfrecipient AltreAnagrafiche.CreateNTFRecipient(
  ProgrammaIntervento programma   // Programma intervento where NTF to be added
  Ntfrecipient ModelloNTFRecpient // 
)
{
  // create NTF object based on the modello NTF 
  Ntfrecipient ntf = new()
  ntf.init()
  ntf.IDRECIPIENT = Sequence.getNextSequence(NTFN_ID_RECIPIENT, ...)
  ntf.KORDAPP = AltreAnagrafiche
  ntf.MAINID = programma.IDCESPITE
  ntf.DETAILID = programma.IDPROGOPERAZIONE
  ntf.RECIPIENTTYPE = ModelloNTFRecpient.RECIPIENTTYPE
  ntf.RECPIENTVALUE = ModelloNTFRecpient.RECPIENTVALUE
  ntf.EMAIL = ModelloNTFRecpient.EMAIL
  ntf.NOTES = ModelloNTFRecpient.NOTES
  ntf.ISFUNCTION = ModelloNTFRecpient.ISFUNCTION
  this.CreateNTFStatus(programma, ntf)
  return ntf
}


// ──────────────────────────────────

// ************************************************************
// Create NTF Status collection for Given Disposizioni and NTF 
// ************************************************************
private void AltreAnagrafiche.CreateNTFStatus(
  ProgrammaIntervento Programma // Programma where NTF status to be added
  Ntfrecipient NTFRecipient     // 
)
{
  Ntfstatus ntfStatus = null
   
  // In advance notification status
  if (Programma.NOTIFYINADVANCE == Yes)
  {
    ntfStatus = this.CreateSingleNTFStatus(NTFRecipient.IDRECIPIENT, "P")
    NTFRecipient.NTFSTATUS.add(ntfStatus)
  }
   
  // In on execution notification status
  if (Programma.NOTIFYONEXECUTION == Yes)
  {
    ntfStatus = this.CreateSingleNTFStatus(NTFRecipient.IDRECIPIENT, "E")
    NTFRecipient.NTFSTATUS.add(ntfStatus)
  }
   
  // In on Closure notification status
  if (Programma.NOTIFYONCLOSE == Yes)
  {
    ntfStatus = this.CreateSingleNTFStatus(NTFRecipient.IDRECIPIENT, "C")
    NTFRecipient.NTFSTATUS.add(ntfStatus)
  }
   
  // In on Delay notification status
  if (Programma.NOTIFYDELAYS == Yes)
  {
    ntfStatus = this.CreateSingleNTFStatus(NTFRecipient.IDRECIPIENT, "D")
    NTFRecipient.NTFSTATUS.add(ntfStatus)
  }
   
  // In on repeated delay notification status
  if (Programma.NOTIFYDELAYSCONTINUE == Yes)
  {
    ntfStatus = this.CreateSingleNTFStatus(NTFRecipient.IDRECIPIENT, "R")
    NTFRecipient.NTFSTATUS.add(ntfStatus)
  }
}


// ──────────────────────────────────

// *********************************************************
// Create NTF Single Status object based on given parameters
// *********************************************************
private Ntfstatus AltreAnagrafiche.CreateSingleNTFStatus(
  int IdRecipient        // 
  string NotifcationType // 
)
{
  Ntfstatus n = new()
  n.init()
  n.IDRECIPIENT = IdRecipient
  n.NOTIFICATIONTYPE = NotifcationType
  n.STATUS = 4
  return n
}


// ──────────────────────────────────

// **************************************************************************************************************
// create a new Link document for given document and Idrevision and add in to collection of Cespite doc collegati
// **************************************************************************************************************
public void AltreAnagrafiche.AddDocCollegati(
  Documento Document // 
  int IdRevision     // 
)
{
  CESDOCUMENTI cd = new()
  cd.init()
  cd.IDCESPITE = ID
  cd.IDDOCUMENTO = Document.IDDOCUMENTO
  cd.IDREVISIONE = null
  if (IdRevision <> 0)
  {
    cd.IDREVISIONE = IdRevision
  }
  CESDOCUMENTI.add(cd)
}


// ──────────────────────────────────

// **********************************************************************************************************
// Check if Document link already exists in collegati collection
// this method is require to validate/check when we create Document link to prevent Duplicate error on saving
// **********************************************************************************************************
public boolean AltreAnagrafiche.DocumentLinkAlreadyExists(
  Documento document // 
)
{
  boolean sameDocumentLinkAlreadyExists = false
  this.loadCollectionFromDB(CESDOCUMENTI, 0)
  for each CESDOCUMENTI cesdocumenti in CESDOCUMENTI
  {
    if (cesdocumenti.IDDOCUMENTO == document.IDDOCUMENTO)
    {
      sameDocumentLinkAlreadyExists = true
      break 
    }
  }
  return sameDocumentLinkAlreadyExists
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void AltreAnagrafiche.computeStandardImportMetadata()
{
  base.addImportMetadato(codice, toPropertyIndex(Code), true, ...)
  base.addImportMetadato(descrizione, toPropertyIndex(Description), true, ...)
  base.addImportMetadato(data_acquisto, toPropertyIndex(DATAACQUISTO), false, ...)
  base.addImportMetadato(data_entrata_funzione, toPropertyIndex(DATAENTRATAFUNZIONE), false, ...)
  base.addImportMetadato(data_dismissione, toPropertyIndex(DATADISMISSIONE), false, ...)
  base.addImportMetadato(ubicazione, toPropertyIndex(IDUBICAZIONE), false, null, Ubicazioni.className(...), ...)
  base.addImportMetadato(identificativo, toPropertyIndex(NROIDENTIFICAZIONE), false, ...)
  base.addImportMetadato(marca, toPropertyIndex(MARCA), false, ...)
  base.addImportMetadato(modello, toPropertyIndex(MODELLO), false, ...)
  base.addImportMetadato(anno_costruzione, toPropertyIndex(ANNOCOSTRUZIONE), false, ...)
  base.addImportMetadato(matricola, toPropertyIndex(MATRICOLACOSTRUTTORE), false, ...)
  base.addImportMetadato(costruttore, toPropertyIndex(COSTRUTTORE), false, ...)
  base.addImportMetadato(fornitore, toPropertyIndex(IDCONTOFORNITORE), false, null, Clifor.className(...), ...)
  base.addImportMetadato(notetxt, toPropertyIndex(NOTETXT), false, ...)
  base.addImportMetadato(note, toPropertyIndex(NOTE), false, ...)
  base.addImportMetadato(padre, toPropertyIndex(IDPADRE), true, null, this.className(...), ...)
  base.addImportMetadato(tipo, toPropertyIndex(IDTIPOCESPITE), true, null, TipoCespite.className(...), ...)
  base.addImportMetadato(stato_anagrafica, toPropertyIndex(IDSTATOCESPITE), true, null, null, IDMap.fromEnum(AnaisStati))
  base.addImportMetadato(proprietario, toPropertyIndex(IDCONTOPROPRIETA), false, null, Clifor.className(...), ...)
  base.addImportMetadato(responsabile, toPropertyIndex(IDRESPONSABILE), false, null, Personale.className(...), ...)
  base.addImportMetadato(data_scadenza_garanzia, toPropertyIndex(DATASCADGARANZIA), false, ...)
  base.addImportMetadato(campo_misura, toPropertyIndex(CAMPOMISURA), false, ...)
  base.addImportMetadato(criteri_accettabilita, toPropertyIndex(CRITERIACC), false, ...)
  base.addImportMetadato(risoluzione, toPropertyIndex(RISOLUZIONE), false, ...)
  base.addImportMetadato(referente, toPropertyIndex(IDUTENTEREFERENTE), false, ..., Utente.className(...))
  base.addImportMetadato(tipo_utilizzo, toPropertyIndex(IDUSOSTRUMENTO), false, ..., UsoStrumento.className(...))
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public int AltreAnagrafiche.IDfromDescription(
  string description // 
)
{
   
  string cleanDescription = replace(upper(description), " ", "")
   
  QualibusDB.maxRows = 1
  int matchingID = 0
  select into variables (found variable)
    set matchingID = ID
  from 
    AltreAnagrafiche // master table
  where
    (replace(upper(Code), " ", "") = cleanDescription) or (replace(upper(Description), " ", "") = cleanDescription)
   
  return matchingID
   
   
}


// ──────────────────────────────────

// *******************************************************************************************************************
// it returns the property index of the property linked to the main image field of the class (e.g. ART_ANAGRAFICA.FOTO
// *******************************************************************************************************************
public int AltreAnagrafiche.PROTECTEDgetMainImagePropertyIndex()
{
  return toPropertyIndex(FOTO)
}


// ──────────────────────────────────

// ****************************************************************************************************************************
// it returns the property index of the property linked to the second image field of the class (e.g. ART_ANAGRAFICA.FOTO_SECOND
// ****************************************************************************************************************************
public int AltreAnagrafiche.PROTECTEDgetSecondImagePropertyIndex()
{
  return null
}


// ──────────────────────────────────

// *********************************************************************************
// giveen the query it returns a IDCollection of results for which the query matches
// it must be extended in every mainModule subclass
// *********************************************************************************
public IDCollection AltreAnagrafiche.search(
  string query // 
)
{
  query = upper(query)
   
  IDCollection foundItems of AltreAnagrafiche = new()
  select into collection (foundItems)
  from 
    AltreAnagrafiche // master table
  where
    (Code like "%" + query + "%") or (Description like "%" + query + "%")
   
   
  return foundItems
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean AltreAnagrafiche.isClosed()
{
  return DATADISMISSIONE == null
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public int AltreAnagrafiche.setMainTxtFieldPropertyIndex()
{
  return toPropertyIndex(NOTETXT)
}


// ──────────────────────────────────

// *********************
// Method to be extended
// *********************
public int AltreAnagrafiche.setMainHtmlFieldPropertyIndex()
{
  return toPropertyIndex(NOTEHTML)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public int AltreAnagrafiche.getMainSCMType()
{
  TipoCespite tc = new()
   
  // warning: odd fk naming!!!!
  tc.IDTIPIINFRSTR = IDTIPOCESPITE
  tc.loadFromDB(...)
  int:altreAnagraficheSCMTypes SCMType = tc.IDTIPOCESPITE
   
  return SCMType
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public void AltreAnagrafiche.addChecklistItemsFromSource(
  ProgrammaIntervento source      // 
  ProgrammaIntervento destination // 
)
{
  if (destination.ChecklistTemplate and destination.ChecklistTemplate.ChecklistTemplateType == ProgrammaIntervento)
  {
    IDCollection sourceItems of ChecklistItem = source.ChecklistTemplate.ChecklistInstanceItemCollection
    for each ChecklistItem ctiabstract in sourceItems
    {
      destination.ChecklistTemplate.AddChecklistItem()
    }
  }
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string AltreAnagrafiche.getCaption(
  string additionalDescription // 
)
{
  return "Infrastrutture e Strumenti " + "{{icon-fa-chevron-right}}"
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event AltreAnagrafiche.OnInit()
{
  ID = Sequence.getNextSequence(CESN_ID_CESPITE, ...)
  IMMATERIALE = No
  BENEUSATO = No
  MARCATURACE = No
  IDSTATOCESPITE = Attivo-InServizio
  PROFID = 0
  ISLOCKED = No
  TAGAPPSYNC = No
  TAGAPPDOCSYNC = No
}


// ──────────────────────────────────

// ************************************************************
// Event raised by the document after loading from the database
// ************************************************************
event AltreAnagrafiche.AfterLoad(
  boolean AlreadyLoaded     // A boolean parameter that specifies whether the document was previously loaded.
  IDArray LoadedCollections // IDArray type parameter that contains the collections loaded by the framework. If the value of the Already Loaded parameter is False, the Loaded Collections parameter is NULL.
)
{
   
  base.AfterLoad(AlreadyLoaded, LoadedCollections)
  boolean IsQuickLoadRequested = base.quickLoadRequested()
   
  if (IsQuickLoadRequested)
  {
    return 
  }
}


// ──────────────────────────────────

// ***************************************
// Descrivi lo scopo di questa funzione...
// ***************************************
public int AltreAnagrafiche.getMainID()
{
  return ID
}


// ──────────────────────────────────

// ***************************************
// Descrivi lo scopo di questa funzione...
// ***************************************
public int AltreAnagrafiche.getTypeID()
{
  return IDTIPOCESPITE
}


// ──────────────────────────────────

// ***************************************
// Descrivi lo scopo di questa funzione...
// ***************************************
public int AltreAnagrafiche.getKordApp()
{
  return AltreAnagrafiche
}


// ──────────────────────────────────

// ****************************************
// Describe the purpose of this function...
// ****************************************
public string AltreAnagrafiche.getDescription()
{
  string description = SH.Concat(Code, Description, " ")
  return description
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static AltreAnagrafiche AltreAnagrafiche.getFromDB(
  int mainId                                                             // 
  optional string:getFromDBLoadingModes loadingMode = "normalZeroLevels" // 
)
{
  AltreAnagrafiche aa = cast(MainModule.retrieve(AltreAnagrafiche, mainId, loadingMode))
  return aa
}


// ──────────────────────────────────

// ***************************************
// Descrivi lo scopo di questa funzione...
// ***************************************
public int ProgrammaIntervento.getMainID()
{
  return IDPROGOPERAZIONE
}


// ──────────────────────────────────

// ***************************************
// Descrivi lo scopo di questa funzione...
// ***************************************
public int ProgrammaIntervento.getTypeID()
{
  return IDTIPOOPERAZIONE
}


// ──────────────────────────────────

// ***************************************
// Descrivi lo scopo di questa funzione...
// ***************************************
public int ProgrammaIntervento.getKordApp()
{
  return 0
}


// ──────────────────────────────────

// ****************************************
// Describe the purpose of this function...
// ****************************************
public string ProgrammaIntervento.getDescription()
{
  return DESCRTITOLOOPERAZIONE
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public static IDCollection ProgrammaIntervento.getUserProgrammaIntervento(
  Utente Utente     // Scrivi un commento per questo parametro o premi backspace per eliminare questo commento
  date StartDate    // 
  date time EndDate // 
)
{
  IDCollection UserProgrammiInterventi of ProgrammaIntervento = new()
  select into collection (UserProgrammiInterventi)
    set IDPROGOPERAZIONE = ProgrammiIntervento.IDPROGOPERAZIONE
    set IDCESPITE = ProgrammiIntervento.IDCESPITE
    set IDTIPOOPERAZIONE = ProgrammiIntervento.IDTIPOOPERAZIONE
    set SEQUENZA = ProgrammiIntervento.SEQUENZA
    set IDUTENTERESPONSABILE = ProgrammiIntervento.IDUTENTERESPONSABILE
    set DATASCADENZA = ProgrammiIntervento.DATASCADENZA
    set DATAULTIMA = ProgrammiIntervento.DATAULTIMA
    set IDTIPOFREQUENZA = ProgrammiIntervento.IDTIPOFREQUENZA
    set FREQUENZA = ProgrammiIntervento.FREQUENZA
    set NOTIFICASCADENZA = ProgrammiIntervento.NOTIFICASCADENZA
    set COSTOFISSO = ProgrammiIntervento.COSTOFISSO
    set Descrizione = ProgrammiIntervento.NOTE
    set IDCONTOFORNITORE = ProgrammiIntervento.IDCONTOFORNITORE
    set IDESECUTORE = ProgrammiIntervento.IDESECUTORE
    set GIORNIINTERVENTOPREVISTI = ProgrammiIntervento.GIORNIINTERVENTOPREVISTI
    set RIPETIZIONE = ProgrammiIntervento.RIPETIZIONE
    set RIPETIZIONECONTINUA = ProgrammiIntervento.RIPETIZIONECONTINUA
    set RIPETIFINOA = ProgrammiIntervento.RIPETIFINOA
    set SOSPESO = ProgrammiIntervento.SOSPESO
    set ORELAVOROPREVISTE = ProgrammiIntervento.ORELAVOROPREVISTE
    set DESCRTITOLOOPERAZIONE = ProgrammiIntervento.DESCRTITOLOOPERAZIONE
    set IDTEMPLATE = ProgrammiIntervento.IDTEMPLATE
    set TEMPLATE = ProgrammiIntervento.TEMPLATE
    set IDPARENTTEMPLATEREMOTE = ProgrammiIntervento.IDPARENTTEMPLATEREMOTE
    set ATTIVO = ProgrammiIntervento.ATTIVO
    set IDPARENTTEMPLATE = ProgrammiIntervento.IDPARENTTEMPLATE
    set NOTIFYRESPONSIBLE = ProgrammiIntervento.NOTIFYRESPONSIBLE
    set NOTIFYEXECUTOR = ProgrammiIntervento.NOTIFYEXECUTOR
    set NOTIFYOTHERS = ProgrammiIntervento.NOTIFYOTHERS
    set NOTIFYINADVANCE = ProgrammiIntervento.NOTIFYINADVANCE
    set NOTIFYADVANCEDAYS = ProgrammiIntervento.NOTIFYADVANCEDAYS
    set NOTIFYONEXECUTION = ProgrammiIntervento.NOTIFYONEXECUTION
    set NOTIFYONCLOSE = ProgrammiIntervento.NOTIFYONCLOSE
    set IDUTENTEINS = ProgrammiIntervento.IDUTENTEINS
    set DATAINS = ProgrammiIntervento.DATAINS
    set IDUTENTEULTMOD = ProgrammiIntervento.IDUTENTEULTMOD
    set DATAULTIMAMOD = ProgrammiIntervento.DATAULTIMAMOD
    set STATOCKL = ProgrammiIntervento.STATOCKL
    set TAGAPPSYNC = ProgrammiIntervento.TAGAPPSYNC
    set ISFATHER = ProgrammiIntervento.ISFATHER
    set IDFATHER = ProgrammiIntervento.IDFATHER
    set NOTIFYDELAYS = ProgrammiIntervento.NOTIFYDELAYS
    set NOTIFYDELAYSDAYS = ProgrammiIntervento.NOTIFYDELAYSDAYS
    set NOTIFYINSUSER = ProgrammiIntervento.NOTIFYINSUSER
    set ORAINIZIO = ProgrammiIntervento.ORAINIZIO
    set ORAFINE = ProgrammiIntervento.ORAFINE
    set ISFUNCTION = ProgrammiIntervento.ISFUNCTION
    set ISADVANCE = ProgrammiIntervento.ISADVANCE
    set IDUM = ProgrammiIntervento.IDUM
    set VALORE = ProgrammiIntervento.VALORE
    set LASTVALUE = ProgrammiIntervento.LASTVALUE
    set CALCULATIONMETHOD = ProgrammiIntervento.CALCULATIONMETHOD
    set IDTIPOADVFREQUENZA = ProgrammiIntervento.IDTIPOADVFREQUENZA
    set ADVFREQUENZA = ProgrammiIntervento.ADVFREQUENZA
    set LASTDATE = ProgrammiIntervento.LASTDATE
    set MODEUM = ProgrammiIntervento.MODEUM
    set NOTIFYDELAYSCONTINUE = ProgrammiIntervento.NOTIFYDELAYSCONTINUE
    set NOTIFYDELAYSCONTINUEDAYS = ProgrammiIntervento.NOTIFYDELAYSCONTINUEDAYS
    set SHOWACTIVEMATCHKLIST = ProgrammiIntervento.SHOWACTIVEMATCHKLIST
    set ACCEPTALLMATCHKLIST = ProgrammiIntervento.ACCEPTALLMATCHKLIST
    set STATOMATCKL = ProgrammiIntervento.STATOMATCKL
    set THRESHOLD = ProgrammiIntervento.THRESHOLD
    set ISFUNCTIONRESPONSIBLE = ProgrammiIntervento.ISFUNCTIONRESPONSIBLE
  from 
    ProgrammiIntervento // master table
    AltreAnagrafiche    // joined with Programmi Intervento using key FK_MAN_PRG_OPERAZIONI01
    Personale           // manually joined, see where clauses
  where
    ProgrammiIntervento.DATASCADENZA >= StartDate && ProgrammiIntervento.DATASCADENZA <= EndDate
    Personale.IDDIPENDENTE = ProgrammiIntervento.IDESECUTORE
    Personale.IDUTENTE = Utente.IDUTENTE
    !(exists(subquery))
      select // 
         IDTESTATAOP
      from 
         Interventi // master table
      where
         Interventi.IDPROGOPERAZIONE = ProgrammiIntervento.IDPROGOPERAZIONE && Interventi.STATO != Chiuso
    AltreAnagrafiche.IDSTATOCESPITE != Dismesso
    ProgrammiIntervento.SOSPESO = No
    ProgrammiIntervento.ISFATHER = No
    ProgrammiIntervento.NOTIFICASCADENZA = Yes
    (!((ProgrammiIntervento.RIPETIZIONE = Yes && ProgrammiIntervento.RIPETIZIONECONTINUA = No && ProgrammiIntervento.DATASCADENZA > ProgrammiIntervento.RIPETIFINOA)))
    (!(ProgrammiIntervento.RIPETIZIONE = Yes && ProgrammiIntervento.RIPETIZIONECONTINUA = Yes && isNull(ProgrammiIntervento.DATASCADENZA)))
   
  return UserProgrammiInterventi
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void ProgrammaIntervento.computeNextExecutionDate()
{
  string:dateParts s = Year
  switch (IDTIPOFREQUENZA)
  {
    case Anni:
      s = Year
    break
    case Mesi:
      s = Month
    break
    case Settimane:
      s = Week
    break
    case Giorni:
      s = Day
    break
  }
   
  if (!(isNull(DATAULTIMA)))
  {
    DATASCADENZA = dateAdd(Year, FREQUENZA, DATAULTIMA)
  }
  else 
  {
    DATASCADENZA = today()
  }
   
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public static IDCollection ProgrammaIntervento.replaceExecutorProgrammiIntervento(
  Utente fromUtente // Scrivi un commento per questo parametro o premi backspace per eliminare questo commento
  Utente ToUtente   // Scrivi un commento per questo parametro o premi backspace per eliminare questo commento
)
{
  date startDate = toDate(1900, 1, 1)
  date endDate = toDate(2100, 31, 12)
   
  IDCollection involvedProgrammiIntervento of ProgrammaIntervento = this.getUserProgrammaIntervento(fromUtente, startDate, endDate)
   
   
  for each ProgrammaIntervento pi in involvedProgrammiIntervento
  {
    Personale fromPersonale = fromUtente.getLinkedPersonale()
    Personale toPersonale = ToUtente.getLinkedPersonale()
    if (pi.IDESECUTORE == fromPersonale.IDDIPENDENTE)
    {
      pi.IDESECUTORE = toPersonale.IDDIPENDENTE
      pi.DATAULTIMA = now()
      pi.IDUTENTEULTMOD = QappCore.Loggeduser.IDUTENTE
    }
    else 
    {
      QappCore.DTTLogMessage("unexpected utente", ..., DTTError)
    }
  }
   
   
  return involvedProgrammiIntervento
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static ProgrammaIntervento ProgrammaIntervento.create(
  AltreAnagrafiche cespite // 
)
{
  ProgrammaIntervento pi = new()
  pi.init()
   
  pi.IDCESPITE = cespite.ID
  pi.ChecklistTemplate = Checklist.factory(ProgrammaIntervento, pi, ...)
   
  return pi
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static ProgrammaIntervento ProgrammaIntervento.get(
  int idProgOperazione // 
)
{
  ProgrammaIntervento pi = new()
  pi.IDPROGOPERAZIONE = idProgOperazione
  try 
  {
    pi.loadFromDB(...)
  }
  catch 
  {
    pi = null
    QappCore.DTTLogMessage(formatMessage("no record found in MAN_PRG_OPERAZIONI for id: |1", idProgOperazione, ...), ...)
  }
  return pi
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event ProgrammaIntervento.OnInit()
{
  IDPROGOPERAZIONE = Sequence.getNextSequence(MANN_ID_PRG_OPERAZ, ...)
  SEQUENZA = 1
  ATTIVO = Yes
  ISADVANCE = No
  NOTIFICASCADENZA = No
  COSTOFISSO = 0,0
  GIORNIINTERVENTOPREVISTI = 0
  RIPETIZIONE = Yes
  RIPETIZIONECONTINUA = Yes
  SOSPESO = No
  ORELAVOROPREVISTE = 0,0
  NOTIFYRESPONSIBLE = No
  NOTIFYEXECUTOR = No
  NOTIFYINADVANCE = No
  NOTIFYADVANCEDAYS = 0
  NOTIFYONEXECUTION = No
  NOTIFYONCLOSE = No
  IDUTENTEINS = QappCore.Loggeduser.IDUTENTE
  DATAINS = today()
  STATOCKL = 1
  TAGAPPSYNC = Yes
  ISFATHER = No
  NOTIFYDELAYS = No
  NOTIFYDELAYSDAYS = 0
  NOTIFYINSUSER = No
  ORAINIZIO = 0,375
  ORAFINE = 0,39583
  ISFUNCTION = No
  MODEUM = 1
  NOTIFYDELAYSCONTINUE = No
  NOTIFYDELAYSCONTINUEDAYS = 0
  SHOWACTIVEMATCHKLIST = Yes
  ACCEPTALLMATCHKLIST = Yes
  STATOMATCKL = 0
}


// ──────────────────────────────────

// **************************************************************************************
// Event raised to the document during the initial steps of the document saving procedure
// **************************************************************************************
event throws exception ProgrammaIntervento.BeforeSave(
  inout boolean Skip         // A boolean output parameter. If set to True, tells the framework not to perform operations on the database, because they will be managed independently.
  inout boolean Cancel       // A boolean output parameter. If set to True, tells the framework to cancel the save procedure due to errors that occurred. Since the entire document is saved in the same transa...
  int:beforeSavePhases Phase // An integer from 0 to 3 that specifies the progress status of the save procedure as detailed at the beginning of the article.
)
{
  if (Phase == PreSave)
  {
    if (updated and !(deleted))
    {
      IDUTENTEULTMOD = QappCore.Loggeduser.IDUTENTE
      DATAULTIMAMOD = today()
    }
     
    if (inserted)
    {
      this.computeNextExecutionDate()
    }
  }
}


// ──────────────────────────────────

// ***********************************************************************
// Event raised to the document to determine the value of a named property
// ***********************************************************************
event ProgrammaIntervento.OnGetNamedPropertyValue(
  string PropertyName        // The name of the named property whose value to retrieve.
  inout string PropertyValue // An output parameter containing the value of the named property.
)
{
  if (PropertyName = "STARTDATE")
  {
    PropertyValue = convert(getDate(DATASCADENZA))
  }
   
  if (PropertyName = "STARTTIME")
  {
    int minutesfrommidnight = 0
    minutesfrommidnight = Tools.FloatTimeToMinutes(ORAINIZIO)
    PropertyValue = convert(toTime(0, minutesfrommidnight, 0))
  }
  //  
  // This class handles only "impegni to be closed" so status is forcecully OPEN
  if (PropertyName == "STATUS")
  {
    PropertyValue = Open
  }
   
  if (PropertyName == "DURATION")
  {
    if (isNull(ORAINIZIO) || isNull(ORAFINE))
    {
      PropertyValue = convert(30)
    }
    else 
    {
      PropertyValue = convert(Tools.FloatTimeToMinutes(ORAFINE - ORAINIZIO))
    }
     
  }
   
  if (PropertyName == "DESCRIPTION")
  {
    string vCODCESPITE = ""
    string vDESCRCESPITE = ""
    string sFullDescription = ""
    // 
    select into variables (found variable)
      set vCODCESPITE = Code
      set vDESCRCESPITE = Description
    from 
      AltreAnagrafiche // master table
    where
      ID = IDCESPITE
     
    // If Intervento is from programma we retrieve programma's titolo
     
    sFullDescription = Tools.Concatenate(vCODCESPITE, vDESCRCESPITE, ...)
    sFullDescription = Tools.Concatenate(sFullDescription, DESCRTITOLOOPERAZIONE, ...)
    sFullDescription = Tools.Concatenate(sFullDescription, Descrizione, ...)
     
    PropertyValue = sFullDescription
     
  }
   
  // 
  if (PropertyName == "COLOR")
  {
    int vAnaISColor = 0
    select into variables (found variable)
      set vAnaISColor = COLOREANAIS
    from 
      DISPARAMETRI // master table
     
     
    PropertyValue = convert(Tools.DxcolorToInde(vAnaISColor))
  }
   
  if (PropertyName == "ICON")
  {
    // 
    PropertyValue = convert(InterventoProgrammato)
  }
}


// ──────────────────────────────────

// ************************************************************
// Event raised by the document after loading from the database
// ************************************************************
event ProgrammaIntervento.AfterLoad(
  boolean AlreadyLoaded     // A boolean parameter that specifies whether the document was previously loaded.
  IDArray LoadedCollections // IDArray type parameter that contains the collections loaded by the framework. If the value of the Already Loaded parameter is False, the Loaded Collections parameter is NULL.
)
{
  if (ChecklistTemplate.ChecklistInstanceItemCollection.count() == 0)
  {
    ChecklistTemplate = Checklist.factory(ProgrammaIntervento, this, ...)
  }
}


// ──────────────────────────────────

// *************************************************************
// Event raised to the document at the end of the save procedure
// *************************************************************
event throws exception ProgrammaIntervento.AfterSave(
  inout boolean Cancel // Boolean output parameter that makes it possible to cancel the entire save. It is equivalent to the Cancel parameter of the BeforeSave event.
)
{
  if (ChecklistTemplate != null)
  {
    ChecklistTemplate.saveToDB(...)
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static boolean CESPERMESSI.userHasSpecificPriviledge(
  AltreAnagrafiche cespite         // 
  Utente user                      // 
  string:altreAnagraficaRoles role // 
)
{
  boolean userHasPriviledge = false
  CESPERMESSI cespermessi = CESPERMESSI.get(cespite.ID, user.IDUTENTE)
  if (cespermessi and cespermessi.loaded)
  {
    switch (role)
    {
      case Open:
         userHasPriviledge = cespermessi.O == Yes
      break
      case Visibility:
         userHasPriviledge = cespermessi.V == Yes
      break
      case Modification:
         userHasPriviledge = cespermessi.M == Yes
      break
      case Interventi:
         userHasPriviledge = cespermessi.I == Yes
      break
      case Admin:
         userHasPriviledge = cespermessi.A == Yes
      break
    }
  }
  return userHasPriviledge
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static CESPERMESSI CESPERMESSI.get(
  int idCespite // 
  int idUtente  // 
)
{
  CESPERMESSI cespermessi = new()
  cespermessi.ID = idCespite
  cespermessi.IDUTENTE = idUtente
   
  try 
  {
    cespermessi.loadFromDB(...)
  }
  catch 
  {
    QappCore.DTTLogMessage(formatMessage("Cannot retrieve the Cespermessi with ID_CESPITE |1 and ID_UTENTE |2", idCespite, idUtente, ...), ...)
  }
  return cespermessi
}


// ──────────────────────────────────

// ************************************************************
// Event raised by the document after loading from the database
// ************************************************************
event Intervento.AfterLoad(
  boolean AlreadyLoaded     // A boolean parameter that specifies whether the document was previously loaded.
  IDArray LoadedCollections // IDArray type parameter that contains the collections loaded by the framework. If the value of the Already Loaded parameter is False, the Loaded Collections parameter is NULL.
)
{
  boolean IsQuickLoadRequested = base.quickLoadRequested()
   
  if (IsQuickLoadRequested)
  {
    return 
  }
   
  // implement here code that would be skipped by QuickLoad
   
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event Intervento.OnInit()
{
  IDTESTATAOP = Sequence.getNextSequence(MANN_ID_TESTATA_OPERAZ, ...)
  DATA = now()
  STATO = "C"
  DESCROPERAZIONE = "-"
  DESCROPERAZIONETXT = "-"
  DESCRTITOLOOPERAZIONE = "-"
  COSTOFISSO = 0
  COSTOTOTALE = 0
  STATOCKL = 1
  CLOSEDBYTAG = "N"
  INTERVENTOSTATUS = "N"
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public static IDCollection Intervento.getUserInterventi(
  Utente Utente       // 
  date time StartDate // 
  date time Enddate   // 
)
{
  IDCollection UserInterventi of Intervento = new()
  select into collection (UserInterventi)
    set IDTESTATAOP = Interventi.IDTESTATAOP
    set IDCESPITE = Interventi.IDCESPITE
    set IDTIPOOPERAZIONE = Interventi.IDTIPOOPERAZIONE
    set IDPROGOPERAZIONE = Interventi.IDPROGOPERAZIONE
    set STATO = Interventi.STATO
    set DATA = Interventi.DATA
    set RICHIEDENTE = Interventi.RICHIEDENTE
    set IDRESPONSABILE = Interventi.IDRESPONSABILE
    set DESCROPERAZIONE = Interventi.DESCROPERAZIONE
    set COSTOFISSO = Interventi.COSTOFISSO
    set COSTOTOTALE = Interventi.COSTOTOTALE
    set IDCONTOFORNITORE = Interventi.IDCONTOFORNITORE
    set IDUTENTECHIUSURA = Interventi.IDUTENTECHIUSURA
    set DATACHIUSURA = Interventi.DATACHIUSURA
    set IDTIPOESITO = Interventi.IDTIPOESITO
    set IDTIPOATTUAZIONE = Interventi.IDTIPOATTUAZIONE
    set IDUTENTERESPONSABILE = Interventi.IDUTENTERESPONSABILE
    set DATAPREVISTA = Interventi.DATAPREVISTA
    set DATAEXEC = Interventi.DATAEXEC
    set DATAINIZIOEFFETTIVO = Interventi.DATAINIZIOEFFETTIVO
    set DATAFINEPREVISTA = Interventi.DATAFINEPREVISTA
    set ORELAVOROPREVISTE = Interventi.ORELAVOROPREVISTE
    set DESCRTITOLOOPERAZIONE = Interventi.DESCRTITOLOOPERAZIONE
    set IDUTENTEINS = Interventi.IDUTENTEINS
    set IDDIPRICHIEDENTE = Interventi.IDDIPRICHIEDENTE
    set NOTEDICHIUSURA = Interventi.NOTEDICHIUSURA
    set STATOCKL = Interventi.STATOCKL
    set CLOSEDBYTAG = Interventi.CLOSEDBYTAG
    set ORELAVOROEFFETTIVE = Interventi.ORELAVOROEFFETTIVE
    set ORAINIZIO = Interventi.ORAINIZIO
    set ORAFINE = Interventi.ORAFINE
    set DESCROPERAZIONETXT = Interventi.DESCROPERAZIONETXT
    set INTERVENTOSTATUS = Interventi.INTERVENTOSTATUS
    set LASTSTARTTIME = Interventi.LASTSTARTTIME
    set DESCROPERAZIONEHTML = Interventi.DESCROPERAZIONEHTML
  from 
    Interventi        // master table
    AltreAnagrafiche  // joined with Interventi using key FK_MAN_TESTATA_OPERAZIONI01
    MANTIPIOPERAZIONI // joined with Interventi using key FK_MAN_TESTATA_OPERAZIONI02
  where
    MANTIPIOPERAZIONI.NOTIFICASCADENZA = Yes
    AltreAnagrafiche.IDSTATOCESPITE != Dismesso
    Interventi.DATAPREVISTA >= StartDate && Interventi.DATAPREVISTA <= Enddate
    Interventi.IDUTENTERESPONSABILE = Utente.IDUTENTE
  return UserInterventi
}


// ──────────────────────────────────

// ******************************************************************************************************************
// create a new Link document for given document and Idrevision and add in to collection of Interventi doc collegati 
// ******************************************************************************************************************
public void Intervento.AddDocCollegati(
  Documento Document // 
  int IdRevision     // 
)
{
  MANDOCUMENTI md = new()
  md.init()
  md.IDTESTATAOP = IDTESTATAOP
  md.IDDOCUMENTO = Document.IDDOCUMENTO
  md.IDREVISIONE = null
  if (IdRevision <> 0)
  {
    md.IDREVISIONE = IdRevision
  }
  MANDOCUMENTI.add(md)
}


// ──────────────────────────────────

// **********************************************************************************************************
// Check if Document link already exists in collegati collection
// this method is require to validate/check when we create Document link to prevent Duplicate error on saving
// **********************************************************************************************************
public boolean Intervento.DocumentLinkAlreadyExists(
  Documento document // 
)
{
  boolean sameDocumentLinkAlreadyExists = false
  for each MANDOCUMENTI mandocumenti in MANDOCUMENTI
  {
    if (mandocumenti.IDDOCUMENTO == document.IDDOCUMENTO)
    {
      sameDocumentLinkAlreadyExists = true
      break 
    }
  }
  return sameDocumentLinkAlreadyExists
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Intervento.computeStandardImportMetadata()
{
  base.addImportMetadato(cespite, toPropertyIndex(IDCESPITE), true, ...)
  base.addImportMetadato(tipo, toPropertyIndex(IDTIPOOPERAZIONE), true, ...)
  base.addImportMetadato(id_prog_operazione, toPropertyIndex(IDPROGOPERAZIONE), false, ...)
  base.addImportMetadato(stato_anagrafica, toPropertyIndex(STATO), true, ...)
  base.addImportMetadato(data_intervento, toPropertyIndex(DATA), true, ...)
  base.addImportMetadato(richiedente, toPropertyIndex(RICHIEDENTE), false, ...)
  base.addImportMetadato(id_responsabile, toPropertyIndex(IDRESPONSABILE), true, ...)
  base.addImportMetadato(descrizione, toPropertyIndex(DESCROPERAZIONE), true, ...)
  base.addImportMetadato(costo_fisso, toPropertyIndex(COSTOFISSO), true, ...)
  base.addImportMetadato(costo_totale, toPropertyIndex(COSTOTOTALE), true, ...)
  base.addImportMetadato(fornitore, toPropertyIndex(IDCONTOFORNITORE), false, ...)
  base.addImportMetadato(id_utente_chiusura, toPropertyIndex(IDUTENTECHIUSURA), false, ...)
  base.addImportMetadato(data_chiusura, toPropertyIndex(DATACHIUSURA), false, ...)
  base.addImportMetadato(id_tipo_esito, toPropertyIndex(IDTIPOESITO), false, ...)
  base.addImportMetadato(id_tipo_attuazione, toPropertyIndex(IDTIPOATTUAZIONE), false, ...)
  base.addImportMetadato(responsabile, toPropertyIndex(IDUTENTERESPONSABILE), false, ...)
  base.addImportMetadato(data_inizio_prevista, toPropertyIndex(DATAPREVISTA), false, ...)
  base.addImportMetadato(data_esecuzione, toPropertyIndex(DATAEXEC), false, ...)
  base.addImportMetadato(data_inizio, toPropertyIndex(DATAINIZIOEFFETTIVO), false, ...)
  base.addImportMetadato(data_fine_prevista, toPropertyIndex(DATAFINEPREVISTA), false, ...)
  base.addImportMetadato(ore_lavoro_previste, toPropertyIndex(ORELAVOROPREVISTE), false, ...)
  base.addImportMetadato(titolo, toPropertyIndex(DESCRTITOLOOPERAZIONE), true, ...)
  base.addImportMetadato(id_utente_inserimento, toPropertyIndex(IDUTENTEINS), false, ...)
  base.addImportMetadato(richiedente, toPropertyIndex(IDDIPRICHIEDENTE), false, ...)
  base.addImportMetadato(note_chiusura, toPropertyIndex(NOTEDICHIUSURA), false, ...)
  base.addImportMetadato(stato_checklist_interventi, toPropertyIndex(STATOCKL), true, ...)
  base.addImportMetadato(closed_by_tag, toPropertyIndex(CLOSEDBYTAG), true, ...)
  base.addImportMetadato(ore_lavoro_effettive, toPropertyIndex(ORELAVOROEFFETTIVE), false, ...)
  base.addImportMetadato(ora_inizio, toPropertyIndex(ORAINIZIO), false, ...)
  base.addImportMetadato(ora_fine, toPropertyIndex(ORAFINE), false, ...)
  base.addImportMetadato(descrizione_operazione_txt, toPropertyIndex(DESCROPERAZIONETXT), false, ...)
  base.addImportMetadato(stato_intervento, toPropertyIndex(INTERVENTOSTATUS), true, ...)
  base.addImportMetadato(ultima_esecuzione, toPropertyIndex(LASTSTARTTIME), false, ...)
}


// ──────────────────────────────────

// *********************
// Method to be extended
// *********************
public int Intervento.setMainHtmlFieldPropertyIndex()
{
  return toPropertyIndex(DESCROPERAZIONEHTML)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public int Intervento.setMainTxtFieldPropertyIndex()
{
  return toPropertyIndex(DESCROPERAZIONETXT)
}


// ──────────────────────────────────

// ***************************************
// Descrivi lo scopo di questa funzione...
// ***************************************
public int Intervento.getMainID()
{
  return IDTESTATAOP
}


// ──────────────────────────────────

// ***************************************
// Descrivi lo scopo di questa funzione...
// ***************************************
public int Intervento.getTypeID()
{
  return IDTIPOOPERAZIONE
}


// ──────────────────────────────────

// ***************************************
// Descrivi lo scopo di questa funzione...
// ***************************************
public int Intervento.getKordApp()
{
  return Interventi
}


// ──────────────────────────────────

// ****************************************
// Describe the purpose of this function...
// ****************************************
public string Intervento.getDescription()
{
  string description = ""
  if (isNull(DESCRTITOLOOPERAZIONE))
  {
    ProgrammaIntervento pi = ProgrammaIntervento.get(IDPROGOPERAZIONE)
    if (pi)
      description = pi.DESCRTITOLOOPERAZIONE
  }
  return description
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static Intervento Intervento.getFromDB(
  int mainId                                                             // 
  optional string:getFromDBLoadingModes loadingMode = "normalZeroLevels" // 
)
{
  Intervento i = cast(MainModule.retrieve(Interventi, mainId, loadingMode))
  return i
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event Ubicazioni.OnInit()
{
  IDUBICAZIONE = Sequence.getNextSequence(CESN_ID_TABELLE, ...)
  SEQUENZA = 1
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public int Ubicazioni.loadFromDescription(
  string description // 
)
{
  this.init()
  DESCRUBICAZIONE = description
   
   
  boolean valid = validate(...)
  if (valid)
  {
    this.saveToDB(...)
    return IDUBICAZIONE
  }
   
   
  return 0
}


// ──────────────────────────────────

// *****************************************************
// Creates a new Tipo Cespite depending on the main type
// *****************************************************
public TipoCespite TipoCespite.create(
  int:tipiCespite tipo // 
)
{
  TipoCespite tc = new()
  tc.init()
  tc.IDTIPOCESPITE = tipo
   
  return tc
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public int TipoCespite.loadFromDescription(
  string description // 
)
{
  this.init()
  string tipoCespite = SH.leftUpToDelimiter(description, "|", ...)
  string tipoInfrastruttura = SH.rightUpToDelimiter(description, "|", ...)
   
  DESCRTIPOCESPITE = tipoInfrastruttura
   
   
  IDMap tipiCespiteMap = IDMap.fromEnum(TipiCespite)
  IDArray tipiCespiteArray = tipiCespiteMap.getKeys()
  for (int i = 0; i < tipiCespiteArray.length(); i = i + 1)
  {
    int tipoCespiteID = tipiCespiteArray.getValue(i)
    if (tipoCespite == tipiCespiteMap.getValue(tipoCespiteID))
    {
      IDTIPOCESPITE = tipoCespiteID
      break 
    }
  }
   
   
  boolean valid = validate(...)
  if (valid)
  {
    this.saveToDB(...)
    return IDTIPIINFRSTR
  }
  return 0
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static TipoCespite TipoCespite.get(
  int idTipiInfr // 
)
{
  TipoCespite tc = new()
  tc.IDTIPIINFRSTR = idTipiInfr
  try 
  {
    tc.loadFromDB(...)
  }
  catch 
  {
    tc = null
    QappCore.DTTLogMessage(formatMessage("no record found in CES_TIPI_CESPITE for id: |1", idTipiInfr, ...), ...)
  }
  return tc
}


// ──────────────────────────────────



// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event TipoCespite.OnInit()
{
  IDTIPIINFRSTR = Sequence.getNextSequence(CESN_ID_TABELLE, ...)
  SHOWITEMS = No
  SHOWMEMORANDUMS = No
  SHOWDOCUMENTS = Yes
  SHOWUSERS = No
  SHOWMAINTAINANCE = Yes
  ISMANDATEUBICAZIONE = No
  ISLOCKED = No
  SHOWREFERENCES = Yes
  SHOWCUSTOMDATA = Yes
  TAGAPPSYNC = Yes
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string MANTIPIOPERAZIONI.IDfromDescription(
  string description // 
)
{
  string tipoCespite = SH.leftUpToDelimiter(description, "|", ...)
  string descrizioneTipoIntervento = SH.rightUpToDelimiter(description, "|", ...)
   
  int vtipoID = 0
  select into variables (found variable)
    set vtipoID = IDTIPIINFRSTR
  from 
    CESTIPICESPITE // master table
  where
    DESCRTIPOCESPITE == tipoCespite
   
   
  this.DESCRTIPOOPERAZIONE = descrizioneTipoIntervento
  IDTIPOCESPITE = vtipoID
   
  try 
  {
    this.loadFromDB(0)
    int mainID = IDTIPOOPERAZIONE
    return toString(mainID)
  }
  catch 
  {
    return "0"
  }
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public IDMap MANTIPIOPERAZIONI.getValueMap()
{
  IDMap map = new()
  IDCollection documents of MANTIPIOPERAZIONI = new()
  this.loadCollectionByExample(documents, false, 0, ...)
   
  for each MANTIPIOPERAZIONI mantipioperazioni in documents
  {
    string vTipoCespite = ""
    select into variables (found variable)
      set vTipoCespite = DESCRTIPOCESPITE
    from 
      CESTIPICESPITE // master table
    where
      IDTIPIINFRSTR == mantipioperazioni.IDTIPOCESPITE
     
    string description = replace(upper(vTipoCespite + "|" + mantipioperazioni.DESCRTIPOOPERAZIONE), " ", "")
    int mainID = mantipioperazioni.IDTIPOOPERAZIONE
    map.setValue(description, mainID)
  }
  return map
}


// ──────────────────────────────────

// ************************************************************************************************************************
// actual work of the api done here in this to be extended method
// 
// the method (GET/POST/...), body (request body; it makes sense with POST only mostly) and parameters come from the webapi
// 
// WebAPIService.setResponse should be used in this method to prepare the webapi response
// ************************************************************************************************************************
public void QvmBackendManager.handleRequest(
  string method        // 
  string body          // 
  IDMap parameters     // 
  inout boolean cancel // 
)
{
  cancel = true
  method = upper(trim(method))
   
  // clear the unit test properties
  LastJson = ""
  LastCode = 0
   
  IDMap res = new()
   
  if (method == "GET")
  {
    res = this.handleGet(parameters)
  }
  else if (method == "POST" or method == "PUT")
  {
    res = this.handleWrite(body)
  }
  else 
  {
    res = this.err(405, "method not allowed")
  }
   
  string responseContent = res.getValue("json")
  int responseCode = res.getValue("code")
   
  this.respond(responseCode, responseContent)
   
  this.logRequest(parameters, method, body, responseContent)
}


// ──────────────────────────────────

// **********************************************************************************************************************
// returns the authentication type required by the API, it must be extended if the authenicaion type reuired is not basic
// 
// the method must simply return one item from the authentication types valuelist
// **********************************************************************************************************************
public string QvmBackendManager.getAuthenticationType()
{
  return basic
}


// ──────────────────────────────────

// **************************************************************************************************************************
// extend this method to implement a check of username and password passed with basic auth (e.g. try to connect to a service)
// return true if succesfully connected
// **************************************************************************************************************************
public boolean QvmBackendManager.checkBasicAuthNamePasswordCredentials(
  string username // 
  string password // 
)
{
   
  // we authorize the API only in case the passed credentials correspond to those of a user with Pers5 in AppMan (even a non active user is ok!)
  boolean validCredentials = this.authorizeApiByCustomCredentials(username, password)
   
  return validCredentials
}


// ──────────────────────────────────

// *************************************************************************************
// a token from NGT tokens is retrieved and consumed: this also determines a logged user
// *************************************************************************************
public boolean QvmBackendManager.authorizeApiByCustomCredentials(
  string username // 
  string password // 
)
{
  // we authorize the API only if credentials match the admin credentials stored in TAB_PARAMETRI_1
  boolean authorizeApiCall = false
   
  string fixedUsernameForAuthentication = this.getFixedUserNameForAuthentication()
  boolean expectedUsernameUsed = username == fixedUsernameForAuthentication
   
  string token = password
  boolean isValidToken = false
  Utente userAssociatedWithToken = LoginToken.authenticateUserWithToken(token, false, isValidToken)
   
  // store the user in a var so it can be logged later
  TokenAssociatedUser = userAssociatedWithToken
   
  boolean validCredentialsPassed = expectedUsernameUsed and isValidToken
   
  authorizeApiCall = validCredentialsPassed
   
  return authorizeApiCall
}


// ──────────────────────────────────

// *******************************************************************
// mock method used in unit tests since in .net we do not have the QV 
// bash command
// *******************************************************************
public static string QvmBackendManager.mockCommandResponse(
  string:qvmBackendManagerCommands command // 
)
{
  string mockedResponse = ""
   
  switch (command)
  {
    case proxyEnable:
      mockedResponse = "proxy enabled"
    break
    case proxyDisable:
      mockedResponse = "proxy disabled"
    break
    case proxyRead:
      mockedResponse = "proxy enabled"
    break
    case tunnelEnable:
      mockedResponse = "tunnel enabled"
    break
    case tunnelDisable:
      mockedResponse = "tunnel disabled"
    break
    case tunnelRead:
      mockedResponse = "tunnel enabled"
    break
    case updatesEnable:
      mockedResponse = "updates enabled"
    break
    case updatesDisable:
      mockedResponse = "updates disabled"
    break
    case updatesRead:
      mockedResponse = "updates enabled"
    break
    case statusRead:
       
      // status mocked value has updates disabled just to have a "strange" condition
      mockedResponse = "proxy enabled" + this.getResponseSeparator() + "tunnel enabled" + this.getResponseSeparator() + "updates disabled"
    break
    case connectionsRead:
       
      // status mocked value has updates disabled just to have a "strange" condition
      mockedResponse = this.getConnectionsReadHappyCaseResponse(true, ...)
    break
  }
  return mockedResponse
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private IDMap QvmBackendManager.handleGet(
  IDMap parameters // 
)
{
  IDMap result = new()
   
  string:qvmBackendManagerAreas area = null
  if (parameters != null)
  {
    area = parameters.getValue("area")
  }
  else 
  {
     
    // in case of null parameters we default to "status"
    area = status
  }
   
  string cmd = this.buildReadCommand(area)
  if (cmd == "")
  {
    result = this.err(400, "invalid or read-only area")
  }
  else 
  {
    string resp = this.executeCommand(cmd)
    result = this.okRead(area, resp)
  }
   
  return result
}


// ──────────────────────────────────

// *************************************************
// helper who returns the area-matching read command
// *************************************************
private string QvmBackendManager.buildReadCommand(
  string:qvmBackendManagerAreas area // 
)
{
  string:qvmBackendManagerCommands result = ""
  switch (area)
  {
    case proxy:
      result = proxyRead
    break
    case tunnel:
      result = tunnelRead
    break
    case updates:
      result = updatesRead
    break
    case status:
       
      // statusRead contains "status" only, but we renamed it not to confuse it with the status=area (the one in this case condition)
      result = statusRead
    break
    case connections:
       
      // idem: see statusRead comment above
      result = connectionsRead
    break
  }
  return result
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private IDMap QvmBackendManager.handleWrite(
  string body // 
)
{
  IDMap result = new()
   
  if (body == "")
  {
    result = this.err(400, "missing body")
  }
  else 
  {
    boolean bodyIsValidJson = SH.isValidJson(body)
     
    if (!(bodyIsValidJson))
    {
      result = this.err(400, "invalid json")
    }
    else 
    {
      IDMap req = cast(JSON.parse(body))
      string:qvmBackendManagerAreas area = req.getValue("area")
      if (area == "")
      {
         result = this.err(400, "missing area")
      }
      else 
      {
         boolean enabled = req.getValue("enabled")
          
         string cmd = this.buildToggleCommand(area, enabled)
         if (cmd == "")
         {
           result = this.err(400, "invalid area (proxy|tunnel|updates) or read-only (status)")
         }
         else 
         {
           string resp = this.executeCommand(cmd)
           result = this.okToggle(area, enabled, resp)
         }
      }
    }
  }
   
   
  return result
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private IDMap QvmBackendManager.err(
  int code       // 
  string message // 
)
{
  IDMap jsonMap = new()
  jsonMap.setBoolean("ok", false)
  jsonMap.setValue("error", message)
   
  string json = JSON.stringify(jsonMap)
   
  return this.result(code, json)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private IDMap QvmBackendManager.okRead(
  string:qvmBackendManagerAreas area // 
  string resp                        // 
)
{
  IDMap resultMap = new()
   
  IDMap jsonMap = new()
  jsonMap.setBoolean("ok", true)
  jsonMap.setValue("area", area)
  jsonMap.setValue("response", resp)
  string json = JSON.stringify(jsonMap)
   
  resultMap = this.result(200, json)
   
   
  return resultMap
}


// ──────────────────────────────────

// *************************************************
// helper who returns the area-matching read command
// *************************************************
private string QvmBackendManager.buildToggleCommand(
  string:qvmBackendManagerAreas area // 
  boolean enabled                    // 
)
{
  string:qvmBackendManagerCommands result = ""
  switch (area)
  {
    case proxy:
      result = if(enabled, proxyEnable, proxyDisable)
    break
    case tunnel:
      result = if(enabled, tunnelEnable, tunnelDisable)
    break
    case updates:
      result = if(enabled, updatesEnable, updatesDisable)
    break
    case status:
      QappCore.DTTLogMessage("UNSUPPORTED CASE", ..., DTTError)
      result = ""
    break
  }
  return result
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private IDMap QvmBackendManager.okToggle(
  string:qvmBackendManagerAreas area // 
  boolean enabled                    // 
  string response                    // 
)
{
  IDMap resultMap = new()
   
  IDMap jsonMap = new()
  jsonMap.setBoolean("ok", true)
  jsonMap.setValue("area", area)
  jsonMap.setBoolean("enabled", enabled)
  jsonMap.setValue("message", response)
   
  string json = JSON.stringify(jsonMap)
   
  resultMap = this.result(200, json)
   
  return resultMap
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private IDMap QvmBackendManager.result(
  int code    // 
  string json // 
)
{
  IDMap resultMap = new()
  resultMap.setValue("code", code)
  resultMap.setValue("json", json)
   
  return resultMap
}


// ──────────────────────────────────

// **************************************************************************
// centralized place whether to switch between unit test mode and normal mode
// 
// check is based on OS: test on windows and run commands on linux
// **************************************************************************
private static string QvmBackendManager.getCommandExecutionMode()
{
   
  string currentOperatingSystem = OSHandler.getOS()
  string:executionModes mode = if(currentOperatingSystem == "windows", unitTestMode, normalMode)
  return mode
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void QvmBackendManager.Respondold(
  int code    // 
  string json // 
)
{
  string:executionModes mode = this.getCommandExecutionMode()
   
  if (mode == normalMode)
  {
    WebAPIService.setResponse(json, code, "application/json")
  }
  else 
  {
     
    // store values to unit test them
    LastCode = code
    LastJson = json
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void QvmBackendManager.respond(
  int code    // 
  string json // 
)
{
   
  if (!(UnitTestCaptureOnlyMode))
  {
    // unless explicily setting capture only mode always respond with API (in this way even on windows we can use postman to test the API)
    WebAPIService.setResponse(json, code, "application/json")
  }
   
  string:executionModes mode = this.getCommandExecutionMode()
   
  if (mode == unitTestMode)
  {
    LastCode = code
    LastJson = json
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private static string QvmBackendManager.getFixedUserNameForAuthentication()
{
  return "token"
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void QvmBackendManager.logRequest(
  IDMap parameters       // 
  string method          // 
  string body            // 
  string responseContent // 
)
{
  // since in inde we cannot get the request we store as request data the clasName(Fullname=true) so we know which API we are talking about and the stringified parameters (in case no parameters are passed JSON
  // stringify returns "")
  string requestData = className(true) + JSON.stringify(parameters)
  int idUtente = if(TokenAssociatedUser != null, TokenAssociatedUser.IDUTENTE, null)
  ApiLog al = ApiLog.create(requestData, method, responseContent, idUtente, body)
   
  // log to db only if not explicitly unit testing
  if (!(UnitTestCaptureOnlyMode))
  {
    al.saveToDB(...)
  }
  else 
  {
    UnitTestApiLog = al
  }
}


// ──────────────────────────────────

// **********************************************************************************************************
// command is executed on the QVM, for .net we use a mock function
// 
// NOTE: X.shell could wait up to 5 seconds since the execution command in the QVM is checked every 5 seconds
// **********************************************************************************************************
public static string QvmBackendManager.executeCommand(
  string:qvmBackendManagerCommands command // 
)
{
  string:executionModes executionMode = QvmBackendManager.getCommandExecutionMode()
  string response = ""
   
  string pathToCommand = "/opt/qvm/bin/qvm_backend_manager_bus"
   
  string completeCommand = pathToCommand + " " + command
   
  if (executionMode == normalMode)
  {
    response = X.shell(completeCommand, sync, ...)
  }
  else if (executionMode == unitTestMode)
  {
    response = this.mockCommandResponse(command)
  }
   
  response = this.removeSeparatorFromTheEndOfTheString(response)
   
  return response
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static QvmBackendManager QvmBackendManager.createInstanceForUnitTests()
{
  QvmBackendManager qbm = new()
  qbm.enableUnitTestCaptureMode()
  return qbm
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void QvmBackendManager.enableUnitTestCaptureMode()
{
  UnitTestCaptureOnlyMode = true
}


// ──────────────────────────────────



// ──────────────────────────────────

// **************************************************************************************************
// single entry point to define the seprator of responses with multi information
// 
// NOTE: done in this way since new line char is not treated well by X.shell at the moment of writing
// **************************************************************************************************
public static string QvmBackendManager.getResponseSeparator()
{
  return " | "
}


// ──────────────────────────────────

// *********************************************************************
// Removes the response separator from the end of the string, if present
// *********************************************************************
public static string QvmBackendManager.removeSeparatorFromTheEndOfTheString(
  string inputString // 
)
{
  string result = ""
   
  int separatorLength = length(this.getResponseSeparator())
  boolean stringEndsWithSeparator = right(inputString, separatorLength) == this.getResponseSeparator()
   
  if (stringEndsWithSeparator)
  {
     
    // remove the separator at the end
    result = left(inputString, length(inputString) - separatorLength)
  }
  else 
  {
    result = inputString
  }
   
  return result
}


// ──────────────────────────────────



// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event ApiLog.OnInit()
{
  TIMESTAMP = now()
}


// ──────────────────────────────────

// ******************************************************************************************************************************************************************
// checks whether the passed script number is the last one, in case it is it returns true, if not it returns false and writes the errorMessage in an output parameter
// ******************************************************************************************************************************************************************
public static boolean SW9DB.isTheExpectedScriptNumber(
  int currentScriptNumber   // 
  inout string errorMessage // 
)
{
  boolean result = false
  int maxFoundNumber = 0
  boolean impossibleToReadDBVersion = false
   
  try 
  {
    select into variables (found variable)
      set maxFoundNumber = max(DBVERS)
    from 
      SW9DB // master table
  }
  catch 
  {
    impossibleToReadDBVersion = true
  }
  if (!(impossibleToReadDBVersion))
  {
    if (ignoreDBVersionCheck())
    {
      if (maxFoundNumber >= currentScriptNumber)
         result = true
      else 
      {
         result = false
         errorMessage = formatMessage("La versione del database |3 (|1) deve essere almeno maggiore di quella attesa da Qapp Core (|2) - ignoreDBVersionCheck è ON", maxFoundNumber, currentScriptNumber, 
                  QualibusDB.getDBName(), ...)
      }
    }
    else 
    {
      if ((currentScriptNumber == maxFoundNumber))
         result = true
      else 
      {
         result = false
         errorMessage = formatMessage("La versione del database |3 (|1) non è quella attesa da Qapp Core (|2)", maxFoundNumber, currentScriptNumber, QualibusDB.getDBName(), ...)
      }
    }
  }
  else 
  {
    errorMessage = "C'è stato un errore cercando di leggere la versione del database, contattare l'amministratore"
  }
   
   
  return result
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static NGTAPPLICAZIONI NGTAPPLICAZIONI.getFromDb(
  int idApplicazione // 
)
{
  NGTAPPLICAZIONI na = new()
  na.IDAPPLICAZIONE = idApplicazione
   
  try 
  {
    na.loadFromDB(...)
  }
  catch 
  {
    na = null
    QappCore.DTTLogMessage(formatMessage("NGT APPLICAZIONE not found for id |1", idApplicazione, ...), ..., DTTWarning)
  }
  return na
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static NGTAPPLICAZIONI NGTAPPLICAZIONI.getFromKordApp(
  int:kordapp kordapp // 
)
{
  NGTAPPLICAZIONI na = new()
  na.KORDAPP = kordapp
   
  try 
  {
    na.loadFromDB(...)
  }
  catch 
  {
    na = null
    QappCore.DTTLogMessage(formatMessage("NGT APPLICAZIONE not found for kordapp |1", decode(kordapp, Kordapp), ...), ..., DTTError)
  }
  return na
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static IDCollection NGTAPPLICAZIONI.getCollectionOfAllApplications()
{
  IDCollection allQualibusApplicationsOnNGTAPPLICAZIONI of NGTAPPLICAZIONI = new()
  select into collection (allQualibusApplicationsOnNGTAPPLICAZIONI)
    set IDAPPLICAZIONE = IDAPPLICAZIONE
    set DESCRAPPLICAZIONE = DESCRAPPLICAZIONE
    set PERCORSOFILE = PERCORSOFILE
    set NOMEFILE = NOMEFILE
    set SEQAPPLICAZIONE = SEQAPPLICAZIONE
    set MENUINITGRUPPO = MENUINITGRUPPO
    set AUTOSTART = AUTOSTART
    set PARAMSAVVIOAPP = PARAMSAVVIOAPP
    set KORDAPP = KORDAPP
    set Notes = Notes
    set ISQAPP = ISQAPP
    set QAPPICON = QAPPICON
    set QAPPMENUICON = QAPPMENUICON
    set QAPPMENUICONQMOBILE = QAPPMENUICONQMOBILE
    set WEBURL = WEBURL
    set ISQAPPWEB = ISQAPPWEB
    set ISROOTFOLDER = ISROOTFOLDER
    set IDFOLDER = IDFOLDER
    set QAPPNAME = QAPPNAME
    set FORCEUPDATE = FORCEUPDATE
    set SHOWINQUALIBUSMENU = SHOWINQUALIBUSMENU
    set SHOWINQMOBILEMENU = SHOWINQMOBILEMENU
    set QAPPMAINICON = QAPPMAINICON
    set ENABLEQAPPDATA = ENABLEQAPPDATA
    set DEFAULTQAPP = DEFAULTQAPP
    set ISQMOBILELINK = ISQMOBILELINK
    set PRIVILEGETOSEEQAPPINMENU = PRIVILEGETOSEEQAPPINMENU
  from 
    NGTAPPLICAZIONI // master table
   
  return allQualibusApplicationsOnNGTAPPLICAZIONI
}


// ──────────────────────────────────

// *********************************
// Creates a lock file to stop login
// *********************************
public static void LockHandler.lockApplication(
  boolean login   // 
  boolean updates // 
)
{
  string folder = LockHandler.getLockFolder()
   
  int i = QappCore.freeFile()
  if (login)
    QappCore.openFileForOutput(folder + ".lock", i, ...)
  if (updates)
    QappCore.openFileForOutput(folder + ".noupdates", i, ...)
  QappCore.closeFile(i)
}


// ──────────────────────────────────

// *****************************************************
// Deletes the lock files used to block login or updates
// *****************************************************
public static void LockHandler.unlockApplication(
  boolean login   // 
  boolean updates // 
)
{
  string folder = LockHandler.getLockFolder()
  if (login)
  {
    QappCore.deleteFile(folder + ".lock")
  }
  if (updates)
  {
    QappCore.deleteFile(folder + ".noupdates")
  }
}


// ──────────────────────────────────

// ******************************
// Checks if the lock file exists
// ******************************
public static boolean LockHandler.isLocked()
{
  string folder = this.getLockFolder()
  boolean isLocked = fileExists(folder + ".lock")
   
  return isLocked
}


// ──────────────────────────────────

// ********************************************************
// Returns the path where the lock file can be read/written
// ********************************************************
public static string LockHandler.getLockFolder()
{
  string folder = QappCore.path()
   
  // 
  C#
  {
    folder = folder + "\\WEB-INF\\"
  }
//  // 
//  Java
//  {
//    folder = folder + "/WEB-INF/"
//  }
   
  return folder
}


// ──────────────────────────────────

// ******************************
// Checks if the lock file exists
// ******************************
public static boolean LockHandler.updatesEnabled()
{
  string folder = this.getLockFolder()
  boolean noUpdates = fileExists(folder + ".noupdates")
   
  return !(noUpdates)
}


// ──────────────────────────────────

// **********************************************
// Scans Plugins folder for QPL*[.dll/.jar] file 
// **********************************************
public void PluginInterface.InitializeAllPlugins()
{
   
  string pluginExtension = ".dll"
   
//  // 
//  java
//  {
//    pluginExtension = ".jar"
//  }
  //  
  // for each plugin found
  string FoundFile = QappCore.readDirectory(this.GetPluginPath("QPL*"), ...)
  while (FoundFile <> "")
  {
    this.InitializePlugin(replace(FoundFile, pluginExtension, ""))
    // 
    FoundFile = QappCore.readDirectory(...)
  }
}


// ──────────────────────────────────

// ********************************************************************************************
// Encapsulates the logic to properly forward commands to other PluginInterfaces to avoid loops
// - do not forward to myself
// - do not forward to the original sender
// ********************************************************************************************
private void PluginInterface.BroadcastCommand(
  string message         // 
  PluginInterface caller // 
  IDArray parameters     // 
  string targetPlugin    // 
)
{
  // send to all loaded Plugins
  IDArray pluginNames = this.GetPluginsNames()
  for (int p = 0; p < pluginNames.length(); p = p + 1)
  {
    PluginInterface PluginInterface = (PluginInterface)PluginInterfaces.getObject(pluginNames.getValue(p))
    if (PluginInterface != caller)
      PluginInterface.SendCommand(message, caller, parameters, targetPlugin)
  }
  //  
  // should I send to FatherInterface?
  if (FatherInterface && FatherInterface != caller)
    FatherInterface.SendCommand(message, caller, parameters, targetPlugin)
}


// ──────────────────────────────────

// ****************************************************************************************************
// Gives complete path to a [dll/jar] in the Plugins folder
// If FileName is a search condition like "QPL*", generates a proper string to be used in ReadDirectory
// Look InitializeAllPlugins for reference
// ****************************************************************************************************
private static string PluginInterface.GetPluginPath(
  string FileName // 
)
{
  string pluginExtension = ".dll"
//  // 
//  java
//  {
//    pluginExtension = ".jar"
//  }
  // 
  string PluginPath = QappCore.path() + formatMessage("\\Plugins\\|1" + pluginExtension, FileName, ...)
  return PluginPath
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void PluginInterface.InitializePlugin(
  string PluginName // 
)
{
  // I look for the plugin in the map
  PluginInterface PluiginInterface = (PluginInterface)PluginInterfaces.getObject(PluginName)
  //  
  // if not found means I need to load it (just once)
  if (PluiginInterface == null)
    PluiginInterface = this.LoadPlugin(PluginName)
   
   
}


// ──────────────────────────────────

// ***********************************************************
// Load a plugin from file given the name and call INIT method
// Returns Plugin Handler
// ***********************************************************
private PluginInterface PluginInterface.LoadPlugin(
  string PluginName // 
)
{
  // 
  PluginInterface PluginInterface = null
  // 
  string pluginPath = this.GetPluginPath(PluginName)
  //  
  // This check on file existence is redundant
  if (fileExists(pluginPath))
  {
    PluginInterface = QappCore.createFormFromLibrary(pluginPath, "PluginInterface")
    if (PluginInterface)
    {
      PluginInterface.FatherInterface = this
      string PluginInnerName = PluginInterface.componentName()
      if (PluginInnerName != PluginName)
         throw 0, "FileName and PluginName mismatch"
      PluginInterfaces.setObject(PluginInnerName, PluginInterface)
      // 
      PluginInterface.SendCommand("INIT", this, ..., PluginName)
    }
  }
  // 
  return PluginInterface
}


// ──────────────────────────────────

// **********************************************************************************************
// Checks if the command must be forwarded because is meant for all Plugins or I'm not the target
// **********************************************************************************************
private boolean PluginInterface.CommandMustBeForwarded(
  string TargetPlugin // 
)
{
  boolean Result = false
  if (TargetPlugin = "*" or TargetPlugin != this.componentName())
    Result = true
   
  return Result
}


// ──────────────────────────────────

// *****************************************************************************
// Returns the Plugin Interface object given the plugin name
// If already loaded returns Infterface from the map, otherwise calls loadPlugin
// Currently there's no license check
// *****************************************************************************
public PluginInterface PluginInterface.GetPluginInterface(
  string pluginName // 
)
{
  PluginInterface PluiginInterface = null // 
  PluiginInterface = (PluginInterface)PluginInterfaces.getObject(pluginName)
   
  if (!(PluiginInterface))
  {
    throw 0, formatMessage("GetPluginInterface: Required Plugin '|1' not found. Likely missing a InitializeAllPlugins()", pluginName, ...)
  }
  // 
  return PluiginInterface
}


// ──────────────────────────────────

// ************************************************************
// Returns an Array of strings with names of the loaded Plugins
// ************************************************************
public IDArray PluginInterface.GetPluginsNames()
{
  IDArray ida = PluginInterfaces.getKeys()
  return ida
}


// ──────────────────────────────────

// ***********************************************************************************************************************************************
// Main public method to be called to Send Commands to other plugins or main application
// It's not meant to be extended
// Usage: 
// called on the target PluginInterface (it may be the Plugin in which you are) -> returns the result of the Handling done by that Plugin (if any)
// If TargetPlugin ='*' the command is forwaded to all other plugins (unless HandleCommand cancels it)
// ***********************************************************************************************************************************************
public IDArray PluginInterface.SendCommand(
  string message                    // 
  PluginInterface caller            // 
  optional IDArray parameters       // 
  optional string targetPlugin = "" // 
)
{
  IDArray result = null
  boolean cancel = false
   
   
  // Handle the message here
  result = this.HandleCommand(message, cancel, parameters, targetPlugin)
  //  
  // If cancel has not been set to True in HandleCommand, then Broadcast to other PluginInterfaces
  if (!(cancel) and this.CommandMustBeForwarded(targetPlugin))
    this.BroadcastCommand(message, caller, parameters, targetPlugin)
   
  return result
}


// ──────────────────────────────────

// **************************************************************************************************************
// "Protected" method that must be extended in each PluginInterface to state what should be done for each command
// For each command the returned IDArray should be in the correct form.
// E.g. "getCalendar" expects that the first Array item is a IDCollection of Impegni
// **************************************************************************************************************
public IDArray PluginInterface.HandleCommand(
  string message       // 
  inout boolean cancel // 
  IDArray parameters   // 
  string targetPlugin  // 
)
{
   
  return null
}


// ──────────────────────────────────

// ********************************************************************************
// This method get last sequence of given SEQ_NAME and return new sequence after +1
// It also update sw9_sequence with +1 value
// SeqName : use the sequence name in the value list (Sequenza)
// updateValue : next sequence computed and will be updated at the end
// ********************************************************************************
public static int Sequence.getNextSequence(
  string:sequenza SeqName           // 
  optional boolean updateValue = -1 // 
)
{
  int vLASTIDSequence = 0
  select into variables (found variable)
    set vLASTIDSequence = LASTID
  from 
    SW9SEQUENCES // master table
  where
    SEQNAME = SeqName
   
  int nextSequence = vLASTIDSequence + 1
  if (updateValue)
  {
    update SW9SEQUENCES
      set LASTID = nextSequence
    where
      SEQNAME = SeqName
  }
   
  return nextSequence
}


// ──────────────────────────────────

// ***********************************************************
// This method is used in tests and it decrements the sequence
// ***********************************************************
public static int Sequence.DecreaseLastSequence(
  string:sequenza SeqName // 
)
{
  int vLASTIDSequence = 0
  select into variables (found variable)
    set vLASTIDSequence = LASTID
  from 
    SW9SEQUENCES // master table
  where
    SEQNAME = SeqName
   
  update SW9SEQUENCES
    set LASTID = vLASTIDSequence - 1
  where
    SEQNAME = SeqName
   
  return vLASTIDSequence - 1
}


// ──────────────────────────────────

// ****************************************************************************************************
// This method is mainly meant for using in tests and it is done to set a sequence to a specific lastid
// ****************************************************************************************************
public static void Sequence.forceSequenceValue(
  string:sequenza SeqName // 
  int value               // 
)
{
  update SW9SEQUENCES
    set LASTID = value
  where
    SEQNAME = SeqName
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void EndSessionForm.ButtonOk()
{
  Tools.performExit(QappCore.logoffURL)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void EndSessionForm.showForm(
  string info                  // 
  optional string caption = "" // 
)
{
  infoPanel.Labelinfolabel.caption = info
  if (caption != "")
  {
    this.caption = caption
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean OneWayUpgrader.dataConversionNeedsToBePerfomed()
{
  boolean rtfToHtmlMustBeDone = RESULT == InitializationDone
  return rtfToHtmlMustBeDone
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static boolean OneWayUpgrader.dedicatedServerSessionShouldBeStarted()
{
   
  boolean startDedicateServerSession = false
  boolean appnameContainsQualibus = find(lower(QappCore.mainCaption), "qualibus", ...) > 0
   
  startDedicateServerSession = appnameContainsQualibus
  return startDedicateServerSession
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static OneWayUpgrader OneWayUpgrader.getRtfToHtmlInstance()
{
   
  // we load only STEPID 4 since it is the one about rtf to html conversion
   
  OneWayUpgrader owu = new()
   
  owu.STEPID = 4
  try 
  {
    owu.loadFromDB(...)
  }
  catch 
  {
    owu = null
  }
  return owu
}


// ──────────────────────────────────

// ****************************************************************************
// create the upgrader progress elements to be then used to perform conversions
// ****************************************************************************
public IDCollection OneWayUpgrader.createElementsToBeConverted()
{
  IDCollection allElementsToBeConverted of OneWayUpgraderProgress = new()
   
  if (STEPID == 6)
  {
    this.handleAspecificInfo(DiagrammiModelloEvento, allElementsToBeConverted)
  }
  else if (STEPID == 4)
  {
    this.handleAspecificInfo(CliforNotes, allElementsToBeConverted)
    this.handleAspecificInfo(PersonaleNotes, allElementsToBeConverted)
    this.handleAspecificInfo(EventiDescription, allElementsToBeConverted)
    this.handleAspecificInfo(ModelliEventoDescriptions, allElementsToBeConverted)
    this.handleAspecificInfo(ModelliEventoInstructions, allElementsToBeConverted)
     
     
//    // UNCOMMENT THESE TO TRIGGER ALSO ANAIS AND INTERVENTI CONVERSIONS
//    this.handleAspecificInfo(AltreAnagraficheNotes, allElementsToBeConverted)
//    this.handleAspecificInfo(InterventiDescriptions, allElementsToBeConverted)
     
    // ADD HERE MORE calls for converting progetti, messaggi, articoli...
     
  }
  return allElementsToBeConverted
}


// ──────────────────────────────────

// ***************************************************************************
// Progress element is created only if in DB there is no info about completion
// ***************************************************************************
public void OneWayUpgrader.handleAspecificInfo(
  string:oneWayUpgraderProgressTableFieldInfoTypes infoType // 
  IDCollection collection of OneWayUpgraderProgress         // 
)
{
   
  IDCollection completedElements of OneWayUpgraderProgress = this.fetchCompletedElements(infoType)
  boolean completedElementsFound = completedElements.count() > 0
   
  IDCollection pendingElements of OneWayUpgraderProgress = this.fetchPendingElements(infoType)
  boolean aNotStartedElementAlreadyExists = pendingElements.count() > 0
   
  boolean currentTypeNeedsToBeAddedToCollection = !(completedElementsFound) and !(aNotStartedElementAlreadyExists)
   
  if (currentTypeNeedsToBeAddedToCollection)
  {
    OneWayUpgraderProgress progressElementBasedOnInfo = OneWayUpgraderProgress.create(infoType)
    collection.add(progressElementBasedOnInfo)
  }
  else if (!(completedElementsFound) and aNotStartedElementAlreadyExists)
  {
    pendingElements.moveFirst()
    OneWayUpgraderProgress previouslyExistingNeverStartedProgressElement = (OneWayUpgraderProgress)pendingElements.getAt()
    collection.add(previouslyExistingNeverStartedProgressElement)
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static OneWayUpgrader OneWayUpgrader.getXmlToDiagramInstance()
{
   
  // we load only STEPID 6 since it is the one about xml to diagram
   
  OneWayUpgrader owu = new()
   
  owu.STEPID = 6
  try 
  {
    owu.loadFromDB(...)
  }
  catch 
  {
    owu = null
  }
  return owu
}


// ──────────────────────────────────

// *************************************************************************
// PROTECTED METHOD - Wraps completed elements select — overridable in tests
// *************************************************************************
public IDCollection OneWayUpgrader.fetchCompletedElements(
  string:oneWayUpgraderProgressTableFieldInfoTypes infoType // 
)
{
  IDCollection result of OneWayUpgraderProgress = new()
  select into collection (result)
  from 
    OneWayUpgraderProgress // master table
  where
    !(isNull(COMPLETEDON))
    TABLEFIELDINFO == infoType
  return result
}


// ──────────────────────────────────

// ***********************************************************************
// PROTECTED METHOD - Wraps pending elements select — overridable in tests
// ***********************************************************************
public IDCollection OneWayUpgrader.fetchPendingElements(
  string:oneWayUpgraderProgressTableFieldInfoTypes infoType // 
)
{
  IDCollection result of OneWayUpgraderProgress = new()
  select into collection (result)
  from 
    OneWayUpgraderProgress // master table
  where
    isNull(COMPLETEDON)
    TABLEFIELDINFO == infoType
  return result
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void OneWayUpgrader.initializeProgressElements()
{
   
  // since this method can be called several times from the timers we do not want to re-add elements for the current object if alerady added
  if (NGTONEWAYUPGRADERPROGRESS.count() > 0)
    return 
   
  IDCollection allElementsToBeConverted of OneWayUpgraderProgress = this.createElementsToBeConverted()
  NGTONEWAYUPGRADERPROGRESS.addAll(allElementsToBeConverted, true)
  NGTONEWAYUPGRADERPROGRESS.saveToDB(...)
}


// ──────────────────────────────────

// ************************************************************
// Event raised by the document after loading from the database
// ************************************************************
event OneWayUpgrader.AfterLoad(
  boolean AlreadyLoaded     // A boolean parameter that specifies whether the document was previously loaded.
  IDArray LoadedCollections // IDArray type parameter that contains the collections loaded by the framework. If the value of the Already Loaded parameter is False, the Loaded Collections parameter is NULL.
)
{
   
  // initialize to 0 (not to have NULL) the counter
  ConversionCounter = 0
  this.setOriginal()
}


// ──────────────────────────────────

// *************************************************************************
// PROTECTED METHOD - Wraps completed elements select — overridable in tests
// *************************************************************************
public IDCollection OneWayUpgraderTestable.fetchCompletedElements(
  string:oneWayUpgraderProgressTableFieldInfoTypes infoType // 
)
{
  return FakeCompletedElements
}


// ──────────────────────────────────

// ***********************************************************************
// PROTECTED METHOD - Wraps pending elements select — overridable in tests
// ***********************************************************************
public IDCollection OneWayUpgraderTestable.fetchPendingElements(
  string:oneWayUpgraderProgressTableFieldInfoTypes infoType // 
)
{
  return FakePendingElements
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event OneWayUpgraderProgress.OnInit()
{
  CREATEDON = now()
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static OneWayUpgraderProgress OneWayUpgraderProgress.create(
  string:oneWayUpgraderProgressTableFieldInfoTypes tableFieldInfo // 
)
{
  OneWayUpgraderProgress owup = new()
  owup.init()
  owup.TABLEFIELDINFO = tableFieldInfo
  owup.STATUSCODE = ProgressElementPending
   
  return owup
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void OneWayUpgraderProgress.computeSpecificNames(
  inout string inputField      // 
  inout string dbTable         // 
  inout string primaryKeyField // 
  inout string outputField     // 
)
{
  switch (TABLEFIELDINFO)
  {
    case CliforNotes:
      inputField = "NOTE"
      dbTable = "GCF_ANAGRAFICA"
      primaryKeyField = "ID_CONTO"
      outputField = "NOTE_HTML"
    break
    case PersonaleNotes:
      inputField = "NOTE"
      dbTable = "PER_ANAGRAFICA"
      primaryKeyField = "ID_DIPENDENTE"
      outputField = "NOTE_HTML"
    break
    case EventiDescription:
      inputField = "DESCR_EVENTO"
      dbTable = "EVA_TESTATA_EVENTO"
      primaryKeyField = "ID_EVENTO"
      outputField = "DESCR_EVENTO_HTML"
    break
    case ModelliEventoDescriptions:
      inputField = "DESCR_EVENTO"
      dbTable = "EVA_TEMPL_TESTATA"
      primaryKeyField = "ID_TEMPLATE_EVENTO"
      outputField = "DESCR_EVENTO_HTML"
    break
    case ModelliEventoInstructions:
      inputField = "INSTRUCTIONS"
      dbTable = "EVA_TEMPL_TESTATA"
      primaryKeyField = "ID_TEMPLATE_EVENTO"
      outputField = "INSTRUCTIONS_HTML"
    break
    case AltreAnagraficheNotes:
      inputField = "NOTE"
      dbTable = "CES_ANAGRAFICA"
      primaryKeyField = "ID_CESPITE"
      outputField = "NOTE_HTML"
    break
    case InterventiDescriptions:
      inputField = "DESCR_OPERAZIONE"
      dbTable = "MAN_TESTATA_OPERAZIONI"
      primaryKeyField = "ID_TESTATA_OP"
      outputField = "DESCR_OPERAZIONE_HTML"
    break
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string OneWayUpgraderProgress.removeNullChars(
  string inputString // 
)
{
  string sanitizedString = ""
   
  sanitizedString = replace(inputString, CHR(0), "")
   
   
   
  return sanitizedString
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string OneWayUpgraderProgress.sanitizeHtmlForDb(
  string rawHtml // 
)
{
  string result = replace(rawHtml, "'", "''")
  result = this.removeNullChars(result)
  return result
}


// ──────────────────────────────────

// **************************************************************
// Calls OnlyOffice with up to maxAttempts retries on failure.
// Returns the html result and sets conversionSuccess accordingly
// **************************************************************
public string OneWayUpgraderProgress.convertWithRetry(
  string rtfValue                 // 
  inout boolean conversionSuccess // 
  int maxAttempts                 // 
)
{
  string htmlValue = this.invokeRtfConverter(rtfValue, conversionSuccess)
  int attempts = 1
   
  while (!(conversionSuccess) and attempts <= maxAttempts)
  {
    int sleepInterval = 2 * attempts
    this.invokeSleep(sleepInterval)
    QappCore.DTTLogMessage(formatMessage("retrying OnlyOffice conversion, sleep |1, conversionAttempt |2", sleepInterval, attempts, ...), ...)
    htmlValue = this.invokeRtfConverter(rtfValue, conversionSuccess)
    attempts = attempts + 1
  }
   
  LastConversionAttempts = attempts
   
  return htmlValue
}


// ──────────────────────────────────

// ************************************************************
// PROTECTED METHOD so we can subclass to mock it in unit tests
// ************************************************************
public string OneWayUpgraderProgress.invokeRtfConverter(
  string rtfValue                 // 
  inout boolean conversionSuccess // 
)
{
  return OnlyOfficeConverter.ConvertToDocumentType(rtfValue, "rtf", "html", conversionSuccess)
}


// ──────────────────────────────────

// ************************************************************
// PROTECTED METHOD so we can subclass to mock it in unit tests
// ************************************************************
public void OneWayUpgraderProgress.invokeSleep(
  int seconds // 
)
{
  QappCore.sleep(seconds)
}


// ──────────────────────────────────

// *******************************************************
// PROTECTED METHOD - Wraps DB read — overridable in tests
// *******************************************************
public Recordset OneWayUpgraderProgress.executeQuery(
  string sql // 
)
{
  return QualibusDB.SQLQuery(sql)
}


// ──────────────────────────────────

// ********************************************************
// PROTECTED METHOD - Wraps DB write — overridable in tests
// ********************************************************
public void OneWayUpgraderProgress.executeUpdate(
  string sql            // 
  inout boolean success // 
)
{
  try 
  {
    QualibusDB.SQLExecute(sql)
    success = true
  }
  catch 
  {
    success = false
    QappCore.DTTLogMessage("executeUpdate Failure on:" + sql, ..., DTTError)
  }
}


// ──────────────────────────────────

// ********************************************************
// PROTECTED METHOD - Wraps saveToDB — overridable in tests
// ********************************************************
public void OneWayUpgraderProgress.persistProgress()
{
  this.saveToDB(...)
}


// ──────────────────────────────────

// ************************************************************
// PROTECTED METHOD so we can subclass to mock it in unit tests
// ************************************************************
public boolean OneWayUpgraderProgress.invokeXmlConverter(
  int idModelloEvento // 
)
{
  boolean result = false
   
  ModelloEvento me = ModelloEvento.getFromDB(idModelloEvento, ...)
   
  try 
  {
    result = me.migrateDiagramFromXml()
  }
  catch 
  {
     
    // if failure result is still false
    QappCore.DTTLogMessage("invokeXmlConverter failure", ..., DTTError)
  }
   
  return result
   
}


// ──────────────────────────────────

// ************************************************************
// PROTECTED METHOD so we can subclass to mock it in unit tests
// ************************************************************
public string OneWayUpgraderProgressTestable.invokeRtfConverter(
  string rtfValue                 // 
  inout boolean conversionSuccess // 
)
{
  FakeConverterCallCount = FakeConverterCallCount + 1
  conversionSuccess = FakeConverterCallCount >= FakeConverterShuoldSucceedAtAttempt
  return FakeHtmlResult
}


// ──────────────────────────────────

// ************************************************************
// PROTECTED METHOD so we can subclass to mock it in unit tests
// ************************************************************
public void OneWayUpgraderProgressTestable.invokeSleep(
  int seconds // 
)
{
  QappCore.DTTLogMessage("fake sleep", ..., DTTInfo)
}


// ──────────────────────────────────



// ──────────────────────────────────

// *******************************************************
// PROTECTED METHOD - Wraps DB read — overridable in tests
// *******************************************************
public Recordset OneWayUpgraderProgressTestable.executeQuery(
  string sql // 
)
{
  FakeQueryCallCount = FakeQueryCallCount + 1
  Recordset fakeRecordset = (Recordset)FakeQueryResults.getObject(FakeQueryCallCount - 1)
  return fakeRecordset
}


// ──────────────────────────────────

// ********************************************************
// PROTECTED METHOD - Wraps saveToDB — overridable in tests
// ********************************************************
public void OneWayUpgraderProgressTestable.persistProgress()
{
  FakeSaveToDbCallCount = FakeSaveToDbCallCount + 1
}


// ──────────────────────────────────

// ************************************************************
// PROTECTED METHOD so we can subclass to mock it in unit tests
// ************************************************************
public boolean OneWayUpgraderProgressTestable.invokeXmlConverter(
  int idModelloEvento // 
)
{
  return FakeDiagramConversionShouldSucceed
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public Recordset OneWayUpgraderProgressTestable.buildSingleColumnRecordset(
  string columnValue // 
)
{
  Recordset rs = new()
  RecordsetMetaData rmd = new()
  rmd.setColumnCount(1)
  rmd.setFieldType(1, Character)
  rmd.setFieldName(1, "VALUE")
  rs.setMetaData(rmd)
   
  // collection is the object needed for recordset rows (this is an old Inde Standard with odd naming)
  Collection c = new()
  c.addString(columnValue)
   
  rs.addRow(c)
  rs.moveFirst()
  return rs
}


// ──────────────────────────────────



// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void QappCore.OneWayUpgraderPerformConversionTimer()
{
  QappCore.DTTStarted = false
  OneWayUpgraderPerformConversionTimer.enabled = false
   
  if (QappCore.OneWayUpgrader.ConversionCounter < QappCore.OneWayUpgrader.NGTONEWAYUPGRADERPROGRESS.count())
  {
    int localCounter = 0
    for each OneWayUpgraderProgress owup in OneWayUpgrader.NGTONEWAYUPGRADERPROGRESS
    {
      if (localCounter == QappCore.OneWayUpgrader.ConversionCounter)
      {
         QappCore.DTTLogMessage(formatMessage("CONVERTING |1", owup.TABLEFIELDINFO, ...), 33333, ...)
         boolean currentConversionsSuccess = owup.PerformConversion(normalMode)
         QappCore.OneWayUpgrader.ConversionSuccess = QappCore.OneWayUpgrader.ConversionSuccess and currentConversionsSuccess
         if (!(QappCore.OneWayUpgrader.ConversionSuccess))
         {
           QappCore.DTTLogMessage("Conversion errors, review the logs", ..., DTTError)
           return 
         }
      }
      localCounter = localCounter + 1
    }
     
    QappCore.OneWayUpgrader.ConversionCounter = QappCore.OneWayUpgrader.ConversionCounter + 1
    OneWayUpgraderPerformConversionTimer.enabled = true
  }
  else 
  {
    if (QappCore.OneWayUpgrader.ConversionSuccess)
    {
      QappCore.OneWayUpgrader.RESULT = Success
      QappCore.OneWayUpgrader.saveToDB(...)
      OneWayUpgraderTimer.enabled = true
    }
    else 
    {
      QappCore.DTTLogMessage("Conversion errors, review the logs", ..., DTTError)
    }
  }
}


// ──────────────────────────────────

// **********************************************************************************************************
// Check if Document link already exists in collegati collection
// this method is require to validate/check when we create Document link to prevent Duplicate error on saving
// **********************************************************************************************************
public boolean Articolo.DocumentLinkAlreadyExists(
  Documento document // 
)
{
  boolean sameDocumentLinkAlreadyExists = false
  this.loadCollectionFromDB(ARTDOCUMENTI, 0)
  for each ARTDOCUMENTI artdocumenti in ARTDOCUMENTI
  {
    if (artdocumenti.IDDOCUMENTO == document.IDDOCUMENTO)
    {
      sameDocumentLinkAlreadyExists = true
      break 
    }
  }
  return sameDocumentLinkAlreadyExists
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string Articolo.GetFamigliaDescription()
{
  ARTFAMIGLIE famiglia = getLinkedDocument(false, ARTFAMIGLIE.className(...), ...)
  string famigliaDescr = ""
  if (famiglia)
    famigliaDescr = famiglia.DESCRIZIONE
  return famigliaDescr
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public int Articolo.IDfromDescription(
  string description // 
)
{
   
  string cleanDescription = replace(upper(description), " ", "")
   
  QualibusDB.maxRows = 1
  int matchingID = 0
  select into variables (found variable)
    set matchingID = IDARTICOLO
  from 
    Articoli // master table
  where
    (upper(replace(DESCRARTICOLO, " ", "")) = cleanDescription) or (upper(replace(CODARTICOLO, " ", "")) = cleanDescription)
   
  return matchingID
   
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Articolo.computeStandardImportMetadata()
{
  base.addImportMetadato(codice, toPropertyIndex(CODARTICOLO), true, ...)
  base.addImportMetadato(tipo, toPropertyIndex(IDTIPOARTICOLO), true, null, TipoArticolo.className(false), ...)
  base.addImportMetadato(categoria, toPropertyIndex(IDCATEGORIAMERCE), true, null, CategoriaArticolo.className(false), ...)
  base.addImportMetadato(stato_anagrafica, toPropertyIndex(STATO), true, null, "", IDMap.fromEnum(StatiArticoli))
  base.addImportMetadato(descrizione, toPropertyIndex(DESCRARTICOLO), true, ...)
  base.addImportMetadato(um_gestione, toPropertyIndex(CODUMGESTIONE), true, null, UnitaDiMisura.className(...), ...)
  base.addImportMetadato(um_acquisto, toPropertyIndex(CODUMACQUISTO), true, null, UnitaDiMisura.className(...), ...)
  base.addImportMetadato(um_vendita, toPropertyIndex(CODUMVENDITA), true, null, UnitaDiMisura.className(...), ...)
  base.addImportMetadato(peso_gestione, toPropertyIndex(PESOGESTIONE), false, ...)
  base.addImportMetadato(peso_acquisto, toPropertyIndex(PESOACQUISTO), false, ...)
  base.addImportMetadato(peso_vendita, toPropertyIndex(PESOVENDITA), false, ...)
  base.addImportMetadato(altezza, toPropertyIndex(ALTEZZAGEST), false, ...)
  base.addImportMetadato(larghezza, toPropertyIndex(LARGHEZZAGEST), false, ...)
  base.addImportMetadato(lunghezza, toPropertyIndex(LUNGHEZZAGEST), false, ...)
  base.addImportMetadato(vendita, toPropertyIndex(ARTVENDITA), false, ...)
  base.addImportMetadato(acquisto, toPropertyIndex(ARTACQUISTO), false, ...)
  base.addImportMetadato(note, toPropertyIndex(NOTE), false, ...)
  base.addImportMetadato(notetxt, toPropertyIndex(NOTETXT), false, ...)
  base.addImportMetadato(prezzo_acquisto, toPropertyIndex(PZZOACQUISTO), false, ...)
  base.addImportMetadato(prezzo_vendita, toPropertyIndex(PZZOVENDITA), false, ...)
  base.addImportMetadato(famiglia, toPropertyIndex(IDFAMIGLIE), true, null, ARTFAMIGLIE.className(...), ...)
  base.addImportMetadato(aziendale_concorrenza, toPropertyIndex(AZIENDALECONCORRENZA), false, null, "", IDMap.fromEnum(AziendaleConcorrenzaList))
  base.addImportMetadato(marca, toPropertyIndex(IDMARCHE), false, null, ARTMARCHE.className(...), ...)
  base.addImportMetadato(produttore, toPropertyIndex(IDPRODUTTORE), false, null, Clifor.className(...), ...)
   
}


// ──────────────────────────────────

// *************************************************************************************************************************************************
// to be extended in each subcass: it must return an array of property indexes names for the properties we do not want to include in the json export
// 
// NOTE: it is done with property index so we are forced to use the property itself, making it searchable better
// *************************************************************************************************************************************************
public IDArray Articolo.getPropertyIndexesOfPropertiesExcludedFromJsonExport()
{
   
  IDArray exludedPropertyIndexes = new()
  exludedPropertyIndexes.addValue(toPropertyIndex(Articolo.NOTE))
   
  return exludedPropertyIndexes
}


// ──────────────────────────────────

// *******************************************************************************************************************
// it returns the property index of the property linked to the main image field of the class (e.g. ART_ANAGRAFICA.FOTO
// *******************************************************************************************************************
public int Articolo.PROTECTEDgetMainImagePropertyIndex()
{
  return toPropertyIndex(FOTO)
}


// ──────────────────────────────────

// ****************************************************************************************************************************
// it returns the property index of the property linked to the second image field of the class (e.g. ART_ANAGRAFICA.FOTO_SECOND
// ****************************************************************************************************************************
public int Articolo.PROTECTEDgetSecondImagePropertyIndex()
{
  return toPropertyIndex(FOTOSECOND)
}


// ──────────────────────────────────

// ***************************************************************************************************************
// create a new Link document for given document and Idrevision and add in to collection of Articoli doc collegati
// ***************************************************************************************************************
public void Articolo.AddDocCollegati(
  Documento Document // 
  int IdRevision     // 
)
{
  ARTDOCUMENTI ad = new()
  ad.init()
  ad.IDARTICOLO = IDARTICOLO
  ad.IDDOCUMENTO = Document.IDDOCUMENTO
  ad.IDREVISIONE = null
  if (IdRevision <> 0)
  {
    ad.IDREVISIONE = IdRevision
  }
  ARTDOCUMENTI.add(ad)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string Articolo.getCaption(
  string additionalDescription // 
)
{
  return "Articoli " + "{{icon-fa-chevron-right}}"
   
}


// ──────────────────────────────────

// ***************************************
// Descrivi lo scopo di questa funzione...
// ***************************************
public int Articolo.getMainID()
{
  return IDARTICOLO
}


// ──────────────────────────────────

// ***************************************
// Descrivi lo scopo di questa funzione...
// ***************************************
public int Articolo.getTypeID()
{
  return IDTIPOARTICOLO
}


// ──────────────────────────────────

// ***************************************
// Descrivi lo scopo di questa funzione...
// ***************************************
public int Articolo.getKordApp()
{
  return Articoli
}


// ──────────────────────────────────

// ****************************************
// Describe the purpose of this function...
// ****************************************
public string Articolo.getDescription()
{
  string description = SH.Concat(CODARTICOLO, DESCRARTICOLO, " ")
  return description
}


// ──────────────────────────────────

// ************************************************************
// Event raised by the document after loading from the database
// ************************************************************
event Articolo.AfterLoad(
  boolean AlreadyLoaded     // A boolean parameter that specifies whether the document was previously loaded.
  IDArray LoadedCollections // IDArray type parameter that contains the collections loaded by the framework. If the value of the Already Loaded parameter is False, the Loaded Collections parameter is NULL.
)
{
  base.AfterLoad(AlreadyLoaded, LoadedCollections)
   
  boolean IsQuickLoadRequested = base.quickLoadRequested()
   
  if (IsQuickLoadRequested)
  {
    return 
  }
   
  // implement here code that would be skipped by QuickLoad
   
}


// ──────────────────────────────────



// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static Articolo Articolo.getFromDB(
  int mainId                                                             // 
  optional string:getFromDBLoadingModes loadingMode = "normalZeroLevels" // 
)
{
  Articolo c = cast(MainModule.retrieve(Articoli, mainId, loadingMode))
  return c
}


// ──────────────────────────────────



// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event ARTFAMIGLIE.OnInit()
{
  IDFAMIGLIE = Sequence.getNextSequence(TABN_ID_TABELLE_VARIE, ...)
  SEQUENZA = 1
  ATTIVO = Yes
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event CategoriaArticolo.OnInit()
{
  IDCATEGORIAMERCE = Sequence.getNextSequence(TABN_ID_TABELLE_VARIE, ...)
  CRITICA = No
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event UnitaDiMisura.OnInit()
{
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public IDMap UnitaDiMisura.getValueMap(
  optional boolean export = 0 // 
)
{
  IDMap map = new()
  for each row (readonly)
  {
    select
      CODUMARTUM = CODUM
    from 
      ARTUM // master table
    // 
    map.setValue(CODUMARTUM, CODUMARTUM)
  }
  return map
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string UnitaDiMisura.IDfromDescription(
  string description // 
)
{
  try 
  {
    CODUM = description
    this.loadFromDB(0)
    return CODUM
  }
  catch 
  {
    this.init()
    CODUM = left(description, 3)
    DESCRUM = description
    NRODECIMALI = 3
    boolean ok1 = saveToDB(...)
    if (ok1)
      return CODUM
  }
  return "0"
   
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event ARTMARCHE.OnInit()
{
  IDMARCHE = Sequence.getNextSequence(TABN_ID_TABELLE_VARIE, ...)
  SEQUENZA = 1
  ATTIVO = Yes
}


// ──────────────────────────────────

// *************************************************************************************************************
// this procedure calls "getAll" procedures extended by AttivitàCalendar..MemoCalendar, basing on the parameters
// *************************************************************************************************************
public IDCollection CalendarManager.getAllImpegni(
  optional boolean attività = -1     // 
  optional boolean disposizioni = -1 // 
  optional boolean promemoria = -1   // 
  optional boolean memo = -1         // 
)
{
  IDCollection retrievedCalendarImpegni of CalendarImpegno = new()
  if (attività)
  {
    IDCollection retrievedCalendarActivities of CalendarImpegno = AttivitaCalendar.getAll(From, To, User)
    retrievedCalendarImpegni.addAll(retrievedCalendarActivities, ...)
  }
  if (disposizioni)
  {
    // provisions
    IDCollection retrievedCalendarDisposizioni of CalendarImpegno = DisposizioneCalendar.getAll(From, To, User)
    retrievedCalendarImpegni.addAll(retrievedCalendarDisposizioni, ...)
  }
  if (promemoria)
  {
    IDCollection retrievedCalendarMemorandum of CalendarImpegno = PromemoriaCalendar.getAll(From, To, User)
    retrievedCalendarImpegni.addAll(retrievedCalendarMemorandum, ...)
  }
  if (memo)
  {
    IDCollection retrievedCalendarMemos of CalendarImpegno = MemoCalendar.getAll(From, To, User)
    retrievedCalendarImpegni.addAll(retrievedCalendarMemos, ...)
  }
  return retrievedCalendarImpegni
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static CalendarManager CalendarManager.create(
  int ResourceID     // 
  Utente user        // 
  date time dateFrom // 
  date time dateTo   // 
)
{
  CalendarManager cm = new()
  cm.ID = ResourceID
  cm.User = user
  cm.To = dateTo
  cm.From = dateFrom
  return cm
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static IDCollection AttivitaCalendar.getAll(
  date time from         // 
  date time to           // 
  optional Utente utente // 
)
{
  IDCollection resultingAttivitaCalendar of AttivitaCalendar = new()
  Personale p = utente.getLinkedPersonale()
  IDCollection retrievedActivities of Attività = new()
  select into collection (retrievedActivities)
  from 
    Attività // master table
  where
    DATAATTIVITA >= from and DATAATTIVITA <= to
    IDDIPENDENTE = p.IDDIPENDENTE
  for each Attività a in retrievedActivities
  {
    AttivitaCalendar ac = cast(AttivitaCalendar.Create(a))
    resultingAttivitaCalendar.add(ac)
  }
  return resultingAttivitaCalendar
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public date time AttivitaCalendar.getStart()
{
  time startTime = Tools.FloatToTime(Attivita.ORAINIZIO)
  date time result = dateTime(year(Attivita.DATAATTIVITA), month(Attivita.DATAATTIVITA), day(Attivita.DATAATTIVITA), hour(startTime), minute(startTime), 0)
  return result
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public date time AttivitaCalendar.getEnd()
{
  time endTime = Tools.FloatToTime(Attivita.ORAFINE)
  date time result = dateTime(year(Attivita.DATAATTIVITA), month(Attivita.DATAATTIVITA), day(Attivita.DATAATTIVITA), hour(endTime), minute(endTime), 0)
  return result
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string AttivitaCalendar.getDescription()
{
  return Attivita.DESCRATTIVITA
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string AttivitaCalendar.getDetail()
{
  return ""
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static CalendarImpegno AttivitaCalendar.Create(
  Attività attivita // 
)
{
  AttivitaCalendar ac = new()
  ac.init()
  ac.Attivita = attivita
  ac.computeProperties()
  return ac
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static IDCollection DisposizioneCalendar.getAll(
  date time from         // 
  date time to           // 
  optional Utente utente // 
)
{
  IDCollection resultingDisposizioniCalendar of DisposizioneCalendar = new()
  Personale p = utente.getLinkedPersonale()
  IDCollection retrievedActivities of Disposizione = new()
  select into collection (retrievedActivities)
  from 
    Disposizione // master table
  where
    DATAPREVISTA >= from and DATAPREVISTA <= to
    IDRESPATTIVITA = p.IDDIPENDENTE
   
   
  for each Disposizione d in retrievedActivities
  {
    DisposizioneCalendar ac = cast(DisposizioneCalendar.Create(d))
    resultingDisposizioniCalendar.add(ac)
  }
  return resultingDisposizioniCalendar
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public date time DisposizioneCalendar.getStart()
{
  time startTime = Tools.FloatToTime(Disposizione.ORAPREVISTA)
  date time result = dateTime(year(Disposizione.DATAPREVISTA), month(Disposizione.DATAPREVISTA), day(Disposizione.DATAPREVISTA), hour(startTime), minute(startTime), 0)
  return result
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public date time DisposizioneCalendar.getEnd()
{
  time endTime = Tools.FloatToTime(Disposizione.ORARIOFINE)
  date time result = dateTime(year(Disposizione.DATAPREVISTA), month(Disposizione.DATAPREVISTA), day(Disposizione.DATAPREVISTA), hour(endTime), minute(endTime), 0)
  return result
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string DisposizioneCalendar.getDescription()
{
  return Disposizione.DESCRATTIVITA
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string DisposizioneCalendar.getDetail()
{
  return ""
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static CalendarImpegno DisposizioneCalendar.Create(
  Disposizione disposizione // 
)
{
  DisposizioneCalendar ac = new()
  ac.init()
  ac.Disposizione = disposizione
  ac.computeProperties()
  return ac
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static IDCollection MemoCalendar.getAll(
  date time from         // 
  date time to           // 
  optional Utente utente // 
)
{
  IDCollection resultingMemoCalendar of MemoCalendar = new()
  IDCollection retrievedMemos of Memo = Memo.GetUserMemo(utente, from, to)
  for each Memo m in retrievedMemos
  {
    MemoCalendar ac = cast(MemoCalendar.Create(m))
    resultingMemoCalendar.add(ac)
  }
  return resultingMemoCalendar
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public date time MemoCalendar.getStart()
{
   
  time startTime = dateTime(1900, 1, 1, hour(Memo.DATAINIZIOPROMEMORIA), minute(Memo.DATAINIZIOPROMEMORIA), second(Memo.DATAINIZIOPROMEMORIA))
  date time result = dateTime(year(Memo.DATAINIZIOPROMEMORIA), month(Memo.DATAINIZIOPROMEMORIA), day(Memo.DATAINIZIOPROMEMORIA), hour(startTime), minute(startTime), 0)
  return result
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public date time MemoCalendar.getEnd()
{
  time endTime = dateTime(1900, 1, 1, hour(Memo.DATAFINEPROMEMORIA), minute(Memo.DATAFINEPROMEMORIA), second(Memo.DATAFINEPROMEMORIA))
  date time result = dateTime(year(Memo.DATAFINEPROMEMORIA), month(Memo.DATAFINEPROMEMORIA), day(Memo.DATAFINEPROMEMORIA), hour(endTime), minute(endTime), 0)
  return result
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string MemoCalendar.getDescription()
{
  return Memo.DESCRPROMEMORIA
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string MemoCalendar.getDetail()
{
  return Memo.MESSAGE
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static CalendarImpegno MemoCalendar.Create(
  Memo memo // 
)
{
  MemoCalendar memoCalendar = new()
  memoCalendar.init()
  memoCalendar.Memo = memo
  memoCalendar.computeProperties()
  return memoCalendar
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static IDCollection PromemoriaCalendar.getAll(
  date time from         // 
  date time to           // 
  optional Utente utente // 
)
{
  IDCollection resultingMemoCalendar of PromemoriaCalendar = new()
   
  IDCollection retrievedPromemoria of Promemoria = new()
  Personale linkedEmployee = utente.getLinkedPersonale()
  if (linkedEmployee)
  {
    retrievedPromemoria = Promemoria.GetEmployeePromemoria(linkedEmployee, from, to)
    for each Promemoria p in retrievedPromemoria
    {
      PromemoriaCalendar ac = cast(PromemoriaCalendar.Create(p))
      resultingMemoCalendar.add(ac)
    }
  }
  return resultingMemoCalendar
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public date time PromemoriaCalendar.getStart()
{
  time startTime = Tools.FloatToTime(Promemoria.ORARIOINIZIO)
  date time result = dateTime(year(Promemoria.DATAINIZIOPROMEMORIA), month(Promemoria.DATAINIZIOPROMEMORIA), day(Promemoria.DATAINIZIOPROMEMORIA), hour(startTime), minute(startTime), 0)
  return result
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public date time PromemoriaCalendar.getEnd()
{
  time endTime = Tools.FloatToTime(Promemoria.ORARIOFINE)
  date time result = dateTime(year(Promemoria.DATAFINEPROMEMORIA), month(Promemoria.DATAFINEPROMEMORIA), day(Promemoria.DATAFINEPROMEMORIA), hour(endTime), minute(endTime), 0)
  return result
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string PromemoriaCalendar.getDescription()
{
  return Promemoria.Description
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string PromemoriaCalendar.getDetail()
{
  return Promemoria.NOTECHIUSURA
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static CalendarImpegno PromemoriaCalendar.Create(
  Promemoria promemoria // 
)
{
  PromemoriaCalendar memoCalendar = new()
  memoCalendar.init()
  memoCalendar.Promemoria = promemoria
  memoCalendar.computeProperties()
  return memoCalendar
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static CalendarImpegno CalendarImpegno.Create(
  MainModule mainModule // 
)
{
  QappCore.DTTLogMessage("ABSTRACT ERROR", ..., DTTError)
  return null
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static IDCollection CalendarImpegno.getAll(
  date time from         // 
  date time to           // 
  optional Utente utente // 
)
{
  QappCore.DTTLogMessage("ABSTRACT ERROR", ..., DTTError)
  return null
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean CalendarImpegno.sendCommand(
  string parameters // 
)
{
  QappCore.DTTLogMessage("ABSTRACT ERROR", ..., DTTError)
  return null
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string CalendarImpegno.getDetail()
{
  QappCore.DTTLogMessage("ABSTRACT ERROR", ..., DTTError)
  return null
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string CalendarImpegno.getDescription()
{
  QappCore.DTTLogMessage("ABSTRACT ERROR", ..., DTTError)
  return null
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public date time CalendarImpegno.getEnd()
{
  QappCore.DTTLogMessage("ABSTRACT ERROR", ..., DTTError)
  return null
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public date time CalendarImpegno.getStart()
{
  QappCore.DTTLogMessage("ABSTRACT ERROR", ..., DTTError)
  return null
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void CalendarImpegno.computeProperties()
{
  Start = this.getStart()
  End = this.getEnd()
  Description = this.getDescription()
  Detail = this.getDetail()
}


// ──────────────────────────────────

// ************************************************************
// Event raised by the document after loading from the database
// ************************************************************
event DisposizioneCalendarPrivilegi.AfterLoad(
  boolean AlreadyLoaded     // A boolean parameter that specifies whether the document was previously loaded.
  IDArray LoadedCollections // IDArray type parameter that contains the collections loaded by the framework. If the value of the Already Loaded parameter is False, the Loaded Collections parameter is NULL.
)
{
  if (isNull(IDALTROUTENTE) or IDALTROUTENTE <= 0)
    return 
   
  Utente u = Utente.get(IDALTROUTENTE)
  Reparto r = Reparto.get(u.IDREPARTO)
  Reparto = r.DESCRREPARTO
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static Checklist Checklist.factory(
  int:checklistTemplateModes checkListTemplateType // 
  optional IDDocument owner                        // 
  optional int idTipoChecklist = 0                 // 
)
{
   
  Checklist ciabstract = new()
   
  switch (checkListTemplateType)
  {
    case ModelloEventoDisposizione:
      ciabstract = ModelloEventoDisposizoneChecklistTemplate.create(cast(owner))
    break
    case WorkflowStep:
      ciabstract = WorkflowStepChecklistTemplate.create(cast(owner))
    break
    case ProgrammaIntervento:
      ciabstract = ProgrammaInterventoChecklistTemplate.create(cast(owner))
    break
    case ChecklistQualibusModule:
      ciabstract = ChecklistModuleTemplate.create(idTipoChecklist)
    break
    case EventoDisposizione:
      ciabstract = EventoDisposizioneChecklist.create(cast(owner))
    break
    case Intervento:
      ciabstract = InterventoChecklist.create(cast(owner))
    break
    case ProgettoFase:
      ciabstract = ProgettoFaseChecklist.create(cast(owner))
    break
    default:
      QappCore.DTTLogMessage("Checklist factory - checklisttemplatetype not valid", ..., DTTError)
      return null
    break
  }
   
  ciabstract.ChecklistTemplateType = checkListTemplateType
   
  if (owner)
  {
    ciabstract.ChecklistOwner = (IDDocument)owner
  }
   
  if (idTipoChecklist > 0)
  {
    ciabstract.IdTipoChecklist = idTipoChecklist
  }
   
  ciabstract.load()
  return ciabstract
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Checklist.AddChecklistItem()
{
  ChecklistItem ctiabstract = ChecklistItem.Factory(ChecklistTemplateType, ChecklistInstanceItemCollection.count(), ChecklistOwner, IdTipoChecklist)
  ChecklistInstanceItemCollection.add(ctiabstract)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Checklist.load()
{
  QappCore.DTTLogMessage("ABSTRACT", ..., DTTError)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Checklist.removeChecklistItem(
  ChecklistItem item // 
)
{
  item.deleted = true
  this.FixNroRigaSequence()
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Checklist.FixNroRigaSequence()
{
  this.SortCollectionByNroRiga()
   
  int index = 1
   
  for each ChecklistItem ctiabstract in ChecklistInstanceItemCollection
  {
    int indexNroRiga = ctiabstract.IndexOfNroRiga()
    int nroRigaValue = ctiabstract.getProperty(indexNroRiga)
    if (nroRigaValue != index)
    {
      ctiabstract.setProperty(indexNroRiga, index)
    }
    index = index + 1
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Checklist.SortCollectionByNroRiga()
{
  if (ChecklistInstanceItemCollection.count() = 0)
  {
    return 
  }
   
  ChecklistInstanceItemCollection.moveFirst()
   
  ChecklistItem ctiabstract = (ChecklistItem)ChecklistInstanceItemCollection.getAt()
   
  int indexNroRiga = ctiabstract.IndexOfNroRiga()
   
  ChecklistInstanceItemCollection.addSortCriteria(indexNroRiga)
  ChecklistInstanceItemCollection.doSort()
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Checklist.moveUpItem(
  ChecklistItem item // 
)
{
  if (!(this.CanMoveUpItem(item)))
  {
    return 
  }
   
  int nroRigaIndex = item.IndexOfNroRiga()
  int nroRigaValue = item.getProperty(nroRigaIndex)
   
  int nroRigaToBeMovedUp = nroRigaValue
  int nroRigaAbove = nroRigaToBeMovedUp - 1
   
  for each ChecklistItem ctiabstract in ChecklistInstanceItemCollection
  {
    nroRigaValue = ctiabstract.getProperty(nroRigaIndex)
    if (nroRigaValue == nroRigaAbove)
    {
      int itemNroRigaValue = item.getProperty(nroRigaIndex)
      item.setProperty(nroRigaIndex, itemNroRigaValue - 1)
      ctiabstract.setProperty(nroRigaIndex, nroRigaValue + 1)
      break 
       
    }
  }
  this.SortCollectionByNroRiga()
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean Checklist.CanMoveUpItem(
  ChecklistItem item // 
)
{
  if (!(item))
  {
    return false
  }
   
  int indexOfNroRiga = item.IndexOfNroRiga()
   
  int nroRiga = item.getProperty(indexOfNroRiga)
   
  boolean canMoveUpItem = nroRiga > 1
   
  return canMoveUpItem
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean Checklist.CanMoveDownItem(
  ChecklistItem item // 
)
{
  if (!(item))
  {
    return false
  }
   
  int indexOfNroRiga = item.IndexOfNroRiga()
   
  int nroRiga = item.getProperty(indexOfNroRiga)
   
  boolean canMoveDownItem = nroRiga < ChecklistInstanceItemCollection.count()
   
  return canMoveDownItem
}


// ──────────────────────────────────



// ──────────────────────────────────

// ************************************************************************************************************************************************************************
// When applicable it return the status of completion of the checklist
// Example: in Disposizione Evento a checlist is complete if every item has a result type set, in Modello Evento Disposizione you can't set a result so it's not applicable
// ************************************************************************************************************************************************************************
public string Checklist.getChecklistCompletionStatus()
{
  switch (ChecklistTemplateType)
  {
    case EventoDisposizione:
      int closedCount = 0
       
      if (ChecklistInstanceItemCollection.count() == 0)
         return NotExist
       
      for each ChecklistItem ci in ChecklistInstanceItemCollection
      {
         EventoDisposizioneChecklistItem edci = cast(ci)
         if (edci.IDEVARESULTSTYPE > 0)
           closedCount = closedCount + 1
      }
       
      if (closedCount == ChecklistInstanceItemCollection.count())
         return Complete
       
      if (closedCount == 0)
         return Open
       
      if (closedCount > 0)
         return Half
       
    break
    default:
      return NotApplicable
    break
  }
   
  return NotApplicable
}


// ──────────────────────────────────

// *************************************************************
// Event raised to the document at the end of the save procedure
// *************************************************************
event throws exception Checklist.AfterSave(
  inout boolean Cancel // Boolean output parameter that makes it possible to cancel the entire save. It is equivalent to the Cancel parameter of the BeforeSave event.
)
{
  ChecklistInstanceItemCollection.saveToDB(...)
}


// ──────────────────────────────────

// ************************************************************
// Event raised to the document during the validation procedure
// ************************************************************
event Checklist.OnValidate(
  int:validateReasons Reason // An integer specifying the reason the document is being validated. The most common values are 0 to indicate complete validation before saving, or 1 to indicate a quick validati...
  inout boolean Error        // A boolean output parameter. If set to True, specifies an error condition and stops the validation immediately.
  inout boolean Skip         // A boolean output parameter. If set to True, does not check the rules for automatic validation of the document (see notes).
)
{
   
  boolean collectionValid = this.isCollectionValid()
   
  if (!(collectionValid))
  {
    this.setPropertyError("non va", Details)
    Error = true
  }
  else 
  {
    Error = false
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public int ChecklistItem.IndexOfNroRiga()
{
  QappCore.DTTLogMessage("ABSTRACT", ..., DTTError)
  return null
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static ChecklistItem ChecklistItem.Factory(
  int:checklistTemplateModes checkListTemplateType // 
  int itemCollectionCount                          // 
  optional IDDocument owner                        // 
  optional int idTipoChecklist = 0                 // 
)
{
  switch (checkListTemplateType)
  {
    case ModelloEventoDisposizione:
      ModelloEventoDisposizioneChecklistTemplateItem medc = new()
      return medc.createInstance(cast(owner), itemCollectionCount)
    break
    case WorkflowStep:
      WorkflowStepChecklstTemplateItem wscti = new()
      return wscti.createInstance(cast(owner), itemCollectionCount)
    break
    case ProgettoFase:
      ProgettoFaseChecklistItem pfci = new()
      return pfci.createInstance(cast(owner), itemCollectionCount)
    break
    case ProgrammaIntervento:
      ProgrammaInterventoChecklistTemplateItem picti = new()
      return picti.createInstance(cast(owner), itemCollectionCount)
    break
    case Intervento:
      InterventoChecklistItem ici = new()
      return ici.createInstance(cast(owner), itemCollectionCount)
    break
    case EventoDisposizione:
      EventoDisposizioneChecklistItem edci = new()
      return edci.createInstance(cast(owner), itemCollectionCount)
    break
    case ChecklistQualibusModule:
      ChecklistModuleTemplateItem ccti = new()
      return ccti.createInstance(idTipoChecklist, itemCollectionCount)
    break
  }
  return null
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static WorkflowStepChecklistTemplate WorkflowStepChecklistTemplate.create(
  WkfStep step // 
)
{
  WorkflowStepChecklistTemplate wsct = new()
  wsct.init()
  wsct.WorkflowStep = step
   
  return wsct
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void WorkflowStepChecklistTemplate.load()
{
  if (WorkflowStep.IDSTEP <= 0)
  {
    QappCore.DTTLogMessage("invalid workflow step passed", ..., DTTError)
    return 
  }
   
  IDCollection workflowStepChecklistItems of WorkflowStepChecklstTemplateItem = new()
  select into collection (workflowStepChecklistItems)
  from 
    WorkflowStepChecklstTemplateItem // master table
  where
    IDSTEP == WorkflowStep.IDSTEP
   
  ChecklistInstanceItemCollection.addAll(workflowStepChecklistItems, true)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static ModelloEventoDisposizoneChecklistTemplate ModelloEventoDisposizoneChecklistTemplate.create(
  ModelloEventoDisposizione modelloDisposizione // 
)
{
  ModelloEventoDisposizoneChecklistTemplate medct = new()
  medct.init()
  medct.ModelloEventoDisposizione = modelloDisposizione
   
  return medct
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void ModelloEventoDisposizoneChecklistTemplate.load()
{
  if (ModelloEventoDisposizione.IDTEMPLATTIVITA <= 0)
  {
    QappCore.DTTLogMessage("Invalid modello disposizione passed", ..., DTTError)
    return 
  }
   
  ChecklistInstanceItemCollection.clear()
   
  IDCollection modelloDisposizioniChecklistItems of ModelloEventoDisposizioneChecklistTemplateItem = new()
  select into collection (modelloDisposizioniChecklistItems)
  from 
    ModelloEventoDisposizioneChecklistTemplateItem // master table
  where
    IDTEMPLATEEVENTO == ModelloEventoDisposizione.IDTEMPLATEEVENTO
    IDTEMPLATTIVITA == ModelloEventoDisposizione.IDTEMPLATTIVITA
   
  ChecklistInstanceItemCollection.addAll(modelloDisposizioniChecklistItems, true)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static Checklist ProgrammaInterventoChecklistTemplate.create(
  ProgrammaIntervento programmaIntervento // 
)
{
  ProgrammaInterventoChecklistTemplate pict = new()
  pict.init()
  pict.ProgrammaIntervento = programmaIntervento
   
  return pict
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void ProgrammaInterventoChecklistTemplate.load()
{
  if (ProgrammaIntervento.IDPROGOPERAZIONE <= 0)
  {
    QappCore.DTTLogMessage("Invalid programma intervento passed", ..., DTTError)
    return 
  }
   
  IDCollection programmaInterventoChecklistItems of ProgrammaInterventoChecklistTemplateItem = new()
  select into collection (programmaInterventoChecklistItems)
  from 
    ProgrammaInterventoChecklistTemplateItem // master table
  where
    IDCESPITE == ProgrammaIntervento.IDCESPITE
    IDPROGOPERAZIONE == ProgrammaIntervento.IDPROGOPERAZIONE
   
  ChecklistInstanceItemCollection.addAll(programmaInterventoChecklistItems, true)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static Checklist ChecklistModuleTemplate.create(
  int idChecklist // 
)
{
  ChecklistModuleTemplate cct = new()
  cct.init()
  cct.IDTIPOCKLIST = idChecklist
   
  return cct
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void ChecklistModuleTemplate.load()
{
  IDCollection commonChecklistItems of ChecklistModuleTemplateItem = new()
  select into collection (commonChecklistItems)
  from 
    ChecklistModuleTemplateItemItem // master table
  where
    IDTIPOCKLIST == IDTIPOCKLIST
   
   
  ChecklistInstanceItemCollection.addAll(commonChecklistItems, true)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static InterventoChecklist InterventoChecklist.create(
  Intervento intervento // 
)
{
  InterventoChecklist ic = new()
  ic.init()
  ic.Intervento = intervento
   
  return ic
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void InterventoChecklist.load()
{
  if (Intervento.IDTESTATAOP <= 0)
  {
    QappCore.DTTLogMessage("Invalid intervento passed", ..., DTTError)
  }
   
  IDCollection interventoChecklistItems of InterventoChecklistItem = new()
  select into collection (interventoChecklistItems)
  from 
    InterventoChecklistItem // master table
  where
    IDTESTATAOP = Intervento.IDTESTATAOP
   
  ChecklistInstanceItemCollection.addAll(interventoChecklistItems, true)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static EventoDisposizioneChecklist EventoDisposizioneChecklist.create(
  Disposizione eventoDisposizione // 
)
{
  EventoDisposizioneChecklist edc = new()
  edc.init()
  edc.DisposizioneEvento = eventoDisposizione
   
  return edc
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void EventoDisposizioneChecklist.load()
{
  if (DisposizioneEvento.IDTIPOATTIVITA <= 0)
  {
    QappCore.DTTLogMessage("Invalid disposizione Evento passed", ..., DTTWarning)
  }
   
  IDCollection eventoDisposizioneChecklistItems of EventoDisposizioneChecklistItem = new()
  select into collection (eventoDisposizioneChecklistItems)
  from 
    EventoDisposizioneChecklistItem // master table
  where
    IDATTIVITA = DisposizioneEvento.IDATTIVITA
    IDEVENTO = DisposizioneEvento.IDEVENTO
   
  ChecklistInstanceItemCollection.addAll(eventoDisposizioneChecklistItems, true)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static ProgettoFaseChecklist ProgettoFaseChecklist.create(
  PRGFASI progettoFase // 
)
{
  ProgettoFaseChecklist pfc = new()
  pfc.init()
  pfc.PRGFASI = progettoFase
   
  return pfc
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void ProgettoFaseChecklist.load()
{
  if (PRGFASI.IDFASE <= 0)
  {
    QappCore.DTTLogMessage("Invalid progetto fase passed", ..., DTTError)
  }
   
  IDCollection progettoFaseChecklistItems of ProgettoFaseChecklistItem = new()
  select into collection (progettoFaseChecklistItems)
  from 
    ProgettoFaseChecklistItem // master table
  where
    IDPROGETTO = PRGFASI.IDPROGETTO
    IDFASE = PRGFASI.IDFASE
   
  ChecklistInstanceItemCollection.addAll(progettoFaseChecklistItems, true)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static WorkflowStepChecklstTemplateItem WorkflowStepChecklstTemplateItem.createInstance(
  WkfStep owner           // 
  int itemCollectionCount // 
)
{
  WorkflowStepChecklstTemplateItem wscti = new()
  wscti.init()
  wscti.IDSTEP = owner.IDSTEP
  wscti.IDTEMPLATEEVENTO = owner.IDTEMPLATEEVENTO
  wscti.NRORIGA = itemCollectionCount + 1
   
  return wscti
}


// ──────────────────────────────────

// **************************************************************************************
// Event raised to the document during the initial steps of the document saving procedure
// **************************************************************************************
event throws exception WorkflowStepChecklstTemplateItem.BeforeSave(
  inout boolean Skip         // A boolean output parameter. If set to True, tells the framework not to perform operations on the database, because they will be managed independently.
  inout boolean Cancel       // A boolean output parameter. If set to True, tells the framework to cancel the save procedure due to errors that occurred. Since the entire document is saved in the same transa...
  int:beforeSavePhases Phase // An integer from 0 to 3 that specifies the progress status of the save procedure as detailed at the beginning of the article.
)
{
  if (inserted)
  {
    if (IDDOMANDA = -1)
    {
      IDDOMANDA = Sequence.getNextSequence(EVAN_ID_DOMANDA_EVENTO_TEMP, ...)
    }
  }
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event WorkflowStepChecklstTemplateItem.OnInit()
{
  IDDOMANDA = -1
   
  MANDATORY = No
}


// ──────────────────────────────────

// ************************************************************
// Event raised to the document during the validation procedure
// ************************************************************
event WorkflowStepChecklstTemplateItem.OnValidate(
  int:validateReasons Reason // An integer specifying the reason the document is being validated. The most common values are 0 to indicate complete validation before saving, or 1 to indicate a quick validati...
  inout boolean Error        // A boolean output parameter. If set to True, specifies an error condition and stops the validation immediately.
  inout boolean Skip         // A boolean output parameter. If set to True, does not check the rules for automatic validation of the document (see notes).
)
{
  if (!(deleted))
  {
    if (DESCRDOMANDA == "")
    {
      this.setPropertyError("La Descrizione non può essere vuota", DESCRDOMANDA)
      this.refreshUserInterface()
    }
  }
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event ModelloEventoDisposizioneChecklistTemplateItem.OnInit()
{
  IDDOMANDA = -1
   
  MANDATORY = No
}


// ──────────────────────────────────

// ************************************************************
// Event raised to the document during the validation procedure
// ************************************************************
event ModelloEventoDisposizioneChecklistTemplateItem.OnValidate(
  int:validateReasons Reason // An integer specifying the reason the document is being validated. The most common values are 0 to indicate complete validation before saving, or 1 to indicate a quick validati...
  inout boolean Error        // A boolean output parameter. If set to True, specifies an error condition and stops the validation immediately.
  inout boolean Skip         // A boolean output parameter. If set to True, does not check the rules for automatic validation of the document (see notes).
)
{
  if (!(deleted))
  {
    if (DESCRDOMANDA == "")
    {
      this.setPropertyError("La Descrizione non può essere vuota", DESCRDOMANDA)
      this.refreshUserInterface()
    }
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public int ModelloEventoDisposizioneChecklistTemplateItem.IndexOfNroRiga()
{
  int indexOfNroRiga = toPropertyIndex(NRORIGA)
   
  return indexOfNroRiga
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static ModelloEventoDisposizioneChecklistTemplateItem ModelloEventoDisposizioneChecklistTemplateItem.createInstance(
  ModelloEventoDisposizione owner // 
  int itemCollectionCount         // 
)
{
  ModelloEventoDisposizioneChecklistTemplateItem medcti = new()
  medcti.init()
  medcti.IDTEMPLATEEVENTO = owner.IDTEMPLATEEVENTO
  medcti.IDTEMPLATTIVITA = owner.IDTEMPLATTIVITA
  medcti.NRORIGA = itemCollectionCount + 1
   
  return medcti
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static ProgrammaInterventoChecklistTemplateItem ProgrammaInterventoChecklistTemplateItem.createInstance(
  ProgrammaIntervento owner // 
  int itemCollectionCount   // 
)
{
  ProgrammaInterventoChecklistTemplateItem picti = new()
  picti.init()
  picti.IDCESPITE = owner.IDCESPITE
  picti.IDPROGOPERAZIONE = owner.IDPROGOPERAZIONE
  picti.NRORIGA = itemCollectionCount + 1
   
  return picti
}


// ──────────────────────────────────

// **************************************************************************************
// Event raised to the document during the initial steps of the document saving procedure
// **************************************************************************************
event throws exception ProgrammaInterventoChecklistTemplateItem.BeforeSave(
  inout boolean Skip         // A boolean output parameter. If set to True, tells the framework not to perform operations on the database, because they will be managed independently.
  inout boolean Cancel       // A boolean output parameter. If set to True, tells the framework to cancel the save procedure due to errors that occurred. Since the entire document is saved in the same transa...
  int:beforeSavePhases Phase // An integer from 0 to 3 that specifies the progress status of the save procedure as detailed at the beginning of the article.
)
{
  if (inserted)
  {
    if (IDDOMANDA = -1)
    {
      IDDOMANDA = Sequence.getNextSequence(EVAN_ID_DOMANDA_EVENTO_TEMP, ...)
    }
  }
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event ProgrammaInterventoChecklistTemplateItem.OnInit()
{
  IDDOMANDA = -1
   
  MANDATORY = No
}


// ──────────────────────────────────

// ************************************************************
// Event raised to the document during the validation procedure
// ************************************************************
event ProgrammaInterventoChecklistTemplateItem.OnValidate(
  int:validateReasons Reason // An integer specifying the reason the document is being validated. The most common values are 0 to indicate complete validation before saving, or 1 to indicate a quick validati...
  inout boolean Error        // A boolean output parameter. If set to True, specifies an error condition and stops the validation immediately.
  inout boolean Skip         // A boolean output parameter. If set to True, does not check the rules for automatic validation of the document (see notes).
)
{
  if (!(deleted))
  {
    if (DESCRDOMANDA == "")
    {
      this.setPropertyError("La Descrizione non può essere vuota", DESCRDOMANDA)
      this.refreshUserInterface()
    }
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static ChecklistModuleTemplateItem ChecklistModuleTemplateItem.createInstance(
  int idTipoChecklist     // 
  int itemCollectionCount // 
)
{
  ChecklistModuleTemplateItem ccti = new()
  ccti.init()
  ccti.IDTIPOCKLIST = idTipoChecklist
  ccti.SEQUENZA = itemCollectionCount + 1
   
  return ccti
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static InterventoChecklistItem InterventoChecklistItem.createInstance(
  Intervento owner        // 
  int itemCollectionCount // 
)
{
  InterventoChecklistItem ici = new()
  ici.init()
  ici.IDTESTATAOP = owner.IDTESTATAOP
  ici.NRORIGA = itemCollectionCount + 1
   
  return ici
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public int EventoDisposizioneChecklistItem.IndexOfNroRiga()
{
  int indexOfNroRiga = toPropertyIndex(NRORIGA)
  return indexOfNroRiga
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event EventoDisposizioneChecklistItem.OnInit()
{
  IDDOMANDA = -1
  MANDATORY = No
}


// ──────────────────────────────────

// **************************************************************************************
// Event raised to the document during the initial steps of the document saving procedure
// **************************************************************************************
event throws exception EventoDisposizioneChecklistItem.BeforeSave(
  inout boolean Skip         // A boolean output parameter. If set to True, tells the framework not to perform operations on the database, because they will be managed independently.
  inout boolean Cancel       // A boolean output parameter. If set to True, tells the framework to cancel the save procedure due to errors that occurred. Since the entire document is saved in the same transa...
  int:beforeSavePhases Phase // An integer from 0 to 3 that specifies the progress status of the save procedure as detailed at the beginning of the article.
)
{
  if (inserted)
  {
    if (IDDOMANDA == -1)
    {
      IDDOMANDA = Sequence.getNextSequence(EVAN_ID_DOMANDA_EVENTO, ...)
    }
  }
}


// ──────────────────────────────────

// ************************************************************
// Event raised to the document during the validation procedure
// ************************************************************
event EventoDisposizioneChecklistItem.OnValidate(
  int:validateReasons Reason // An integer specifying the reason the document is being validated. The most common values are 0 to indicate complete validation before saving, or 1 to indicate a quick validati...
  inout boolean Error        // A boolean output parameter. If set to True, specifies an error condition and stops the validation immediately.
  inout boolean Skip         // A boolean output parameter. If set to True, does not check the rules for automatic validation of the document (see notes).
)
{
  if (!(deleted))
  {
    if (DESCRDOMANDA == "")
    {
      this.setPropertyError("La Descrizione non può essere vuota", DESCRDOMANDA)
      this.refreshUserInterface()
    }
     
    if (IDEVARESULTSTYPE > 0)
    {
      EVARESULTSTYPE evaresultstype = EVARESULTSTYPE.get(IDEVARESULTSTYPE)
      if (evaresultstype)
      {
         if (evaresultstype.hasMandatoryNotes())
         {
           if (NOTE == "")
           {
             this.setPropertyError("note obbligatorie per l'esito impostato", NOTE)
             Error = true
           }
         }
      }
    }
  }
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static ProgettoFaseChecklistItem ProgettoFaseChecklistItem.createInstance(
  PRGFASI owner           // 
  int itemCollectionCount // 
)
{
  ProgettoFaseChecklistItem pfci = new()
  pfci.init()
  pfci.IDFASE = owner.IDFASE
  pfci.IDPROGETTO = owner.IDPROGETTO
  pfci.NRORIGA = itemCollectionCount + 1
   
  return pfci
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event ProgettoFaseChecklistItem.OnInit()
{
  IDRIGARIESAME = -1
}


// ──────────────────────────────────

// **************************************************************************************
// Event raised to the document during the initial steps of the document saving procedure
// **************************************************************************************
event throws exception ProgettoFaseChecklistItem.BeforeSave(
  inout boolean Skip         // A boolean output parameter. If set to True, tells the framework not to perform operations on the database, because they will be managed independently.
  inout boolean Cancel       // A boolean output parameter. If set to True, tells the framework to cancel the save procedure due to errors that occurred. Since the entire document is saved in the same transa...
  int:beforeSavePhases Phase // An integer from 0 to 3 that specifies the progress status of the save procedure as detailed at the beginning of the article.
)
{
  if (inserted)
  {
    if (IDRIGARIESAME == -1)
    {
      IDRIGARIESAME = Sequence.getNextSequence(PRGN_ID_RIGA_RIESAME, ...)
    }
  }
}


// ──────────────────────────────────

// **************************************************************************************************************
// create a new Link document for given document and Idrevision and add in to collection of Clifor doc collegati 
// **************************************************************************************************************
public void Clifor.AddDocCollegati(
  Documento Document // 
  int IdRevision     // 
)
{
  GCFDOCUMENTI gd = new()
  gd.init()
  gd.IDCONTO = IDCONTO
  gd.IDDOCUMENTO = Document.IDDOCUMENTO
  gd.IDREVISIONE = null
  if (IdRevision <> 0)
  {
    gd.IDREVISIONE = IdRevision
  }
  GCFDOCUMENTI.add(gd)
}


// ──────────────────────────────────

// **********************************************************************************************************
// Check if Document link already exists in collegati collection
// this method is require to validate/check when we create Document link to prevent Duplicate error on saving
// **********************************************************************************************************
public boolean Clifor.DocumentLinkAlreadyExists(
  Documento document // 
)
{
  boolean sameDocumentLinkAlreadyExists = false
   
  this.loadCliforDocumentiCollegati(false, 0)
  for each GCFDOCUMENTI gcfdocumenti in GCFDOCUMENTI
  {
    if (gcfdocumenti.IDDOCUMENTO == document.IDDOCUMENTO)
    {
      sameDocumentLinkAlreadyExists = true
      break 
    }
  }
  return sameDocumentLinkAlreadyExists
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static Clifor Clifor.create(
  optional TipoClifor tipoCliFor               // 
  optional string:cliforStati statoCliFor = "" // 
  optional string ragioneSociale = ""          // 
)
{
  Clifor c = new()
  c.init()
  if (statoCliFor != "")
    c.STATOCONTO = statoCliFor
  c.PROFID = 0
   
  if (tipoCliFor)
    c.IDTIPOCONTO = tipoCliFor.IDTIPOCONTO
   
  c.RAGIONESOCIALE = ragioneSociale
   
  return c
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public int Clifor.IDfromDescription(
  string description // 
)
{
   
  string cleanDescription = replace(upper(description), " ", "")
   
  QualibusDB.maxRows = 1
  int matchingID = 0
  select into variables (found variable)
    set matchingID = IDCONTO
  from 
    ClientiFornitori // master table
  where
    (upper(RAGIONESOCIALE) = cleanDescription) or (upper(PARTITAIVA) = cleanDescription) or upper(CODCONTO) == cleanDescription
   
  return matchingID
   
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Clifor.computeStandardImportMetadata()
{
  base.addImportMetadato(tipo, toPropertyIndex(IDTIPOCONTO), true, null, TipoClifor.className(false), ...)
  base.addImportMetadato(codice, toPropertyIndex(CODCONTO), true, ...)
  base.addImportMetadato(stato_anagrafica, toPropertyIndex(STATOCONTO), true, null, "", IDMap.fromEnum(CliforStati))
  base.addImportMetadato(data_chiusura, toPropertyIndex(DATACHIUSURA), false, ...)
  base.addImportMetadato(ragione_sociale, toPropertyIndex(RAGIONESOCIALE), true, ...)
  base.addImportMetadato(codice_fiscale, toPropertyIndex(CODICEFISCALE), false, ...)
  base.addImportMetadato(partita_iva, toPropertyIndex(PARTITAIVA), true, ...)
  base.addImportMetadato(indirizzo, toPropertyIndex(INDIRIZZO), false, ...)
  base.addImportMetadato(cap, toPropertyIndex(CAP), false, ...)
  base.addImportMetadato(comune, toPropertyIndex(COMUNE), false, ...)
  base.addImportMetadato(cod_provincia, toPropertyIndex(CODPROVINCIA), false, ...)
  base.addImportMetadato(cod_nazione, toPropertyIndex(CODSTATO), false, ...)
  base.addImportMetadato(telefono, toPropertyIndex(TELEFONO), false, ...)
  base.addImportMetadato(fax, toPropertyIndex(FAX), false, ...)
  base.addImportMetadato(email, toPropertyIndex(EMAIL), false, ...)
  base.addImportMetadato(cellulare, toPropertyIndex(CELLULARE), false, ...)
  base.addImportMetadato(sito_internet, toPropertyIndex(INTERNET), false, ...)
  base.addImportMetadato(note, toPropertyIndex(NOTE), false, ...)
  base.addImportMetadato(contatto_principale, toPropertyIndex(CONTATTOPRINCIPALE), false, ...)
  base.addImportMetadato(tipo_persona, toPropertyIndex(TIPOPERSONA), true, null, "", IDMap.fromEnum(TipiPersonaClifor))
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Clifor.loadImageNOTUSED()
{
  string fotvFOTOGCFANAGRAFICA = null
  select into variables (found variable)
    set FOTO = FOTO
  from 
    ClientiFornitori // master table
  where
    IDCONTO == IDCONTO
   
//  // how to???
//  FOTO = loadBlobFile([filename])
  FOTO = fotvFOTOGCFANAGRAFICA
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public IDCollection Clifor.GetModuleSpecificTextReplacements()
{
  IDCollection coll of MapElement = new()
   
  coll.add(MapElement.create("ID_CONTO", toString(IDCONTO)))
  coll.add(MapElement.create("COD_CONTO", CODCONTO))
  coll.add(MapElement.create("ID_TIPO_CONTO", toString(IDTIPOCONTO)))
  coll.add(MapElement.create("STATO_CONTO", STATOCONTO))
  coll.add(MapElement.create("DATA_CHIUSURA", toString(DATACHIUSURA)))
  coll.add(MapElement.create("RAGIONE_SOCIALE", RAGIONESOCIALE))
  coll.add(MapElement.create("INDIRIZZO", INDIRIZZO))
  coll.add(MapElement.create("CAP", CAP))
  coll.add(MapElement.create("COMUNE", COMUNE))
  coll.add(MapElement.create("TELEFONO", TELEFONO))
  coll.add(MapElement.create("FAX", FAX))
  coll.add(MapElement.create("TELEX", TELEX))
  coll.add(MapElement.create("CELLULARE", CELLULARE))
  coll.add(MapElement.create("E_MAIL", EMAIL))
  coll.add(MapElement.create("INTERNET", INTERNET))
  coll.add(MapElement.create("NOTE", NOTE))
  coll.add(MapElement.create("PARTITA_IVA", PARTITAIVA))
  coll.add(MapElement.create("CODICE_FISCALE", CODICEFISCALE))
  coll.add(MapElement.create("COD_PROVINCIA", CODPROVINCIA))
  coll.add(MapElement.create("COD_STATO", CODSTATO))
  coll.add(MapElement.create("CONTATTO_PRINCIPALE", CONTATTOPRINCIPALE))
  coll.add(MapElement.create("NOTE_TXT", NOTETXT))
  return coll
}


// ──────────────────────────────────

// *******************************************************************************************************************
// it returns the property index of the property linked to the main image field of the class (e.g. ART_ANAGRAFICA.FOTO
// *******************************************************************************************************************
public int Clifor.PROTECTEDgetMainImagePropertyIndex()
{
  return toPropertyIndex(FOTO)
}


// ──────────────────────────────────

// ****************************************************************************************************************************
// it returns the property index of the property linked to the second image field of the class (e.g. ART_ANAGRAFICA.FOTO_SECOND
// ****************************************************************************************************************************
public int Clifor.PROTECTEDgetSecondImagePropertyIndex()
{
   
  // no secondary image in clifor!
   
  return null
}


// ──────────────────────────────────

// **********************************************
// this procedure loads collegamenti for Contatti
// **********************************************
public void Clifor.handleSubModuleCollegamentiCustomData()
{
  IDCollection collContatti of Contatto = new()
  select into collection (collContatti)
  from 
    Contatto // master table
  where
    IDCONTO == IDCONTO
   
  for each Contatto c in collContatti
  {
    int idContatto = c.IDCONTATTO
    int:cdataType lookupCDType = ContattoCDataType
    for each row (readonly)
    {
      select
         moduleID = CDATAMODULEVALUES.IDMODULERECID
         kord = CDATASECTIONS.KordApp
      from 
         CDATAMODULEVALUES // master table
         CDATAFIELDSINFO   // joined with CDATA MODULE VALUES using key FK_CDATA_MODULE_VALUES_ID_CDATA_FLD
         CDATASECTIONS     // joined with CDATA FIELDS INFO using key FK_CDATA_FIELD_ID_CDATA_SEC
      where
         CDATAMODULEVALUES.VALUE == idContatto
         CDATAFIELDSINFO.Type = lookupCDType
         CDATAFIELDSINFO.Active == Yes
         CDATASECTIONS.Active == Yes
       
      base.addCollegamentoMainModule(kord, moduleID, kord, CustomData, true)
    }
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean Clifor.isLocked()
{
  return false
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean Clifor.isClosed()
{
  return STATOCONTO == Chiuso
}


// ──────────────────────────────────



// ──────────────────────────────────

// **********************************************
// this method validate the clifor emails on save
// **********************************************
public boolean Clifor.isValidEmail()
{
  boolean isValidEmail = EmailTools.CheckEmailIsValid(trim(EMAIL))
   
  return isValidEmail
}


// ──────────────────────────────────

// ***************************************************
// this procedure loads Contacts linked to this Clifor
// ***************************************************
public void Clifor.loadCliforContacts()
{
  IDCollection contacts of Contatto = new()
  select into collection (contacts)
  from 
    Contatto // master table
  where
    IDCONTO == IDCONTO
  Contacts = contacts
   
  DevTools.ToBeReviewed("this method is strange, in principle the code above is equivalent to the commented line below, as working on this code again try and replace")
//  this.loadCollectionFromDB(Contacts, ...)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Clifor.loadCliforAddresses(
  optional boolean forceReloadFromDb = 1 // 
  optional int childrenLevel = 9999      // 
)
{
  if (forceReloadFromDb)
  {
    Addresses.loaded = false
  }
  this.loadCollectionFromDB(Addresses, childrenLevel)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Clifor.loadCliforDocumentiCollegati(
  optional boolean forceReloadFromDb = 1 // 
  optional int childrenLevel = 9999      // 
)
{
  if (forceReloadFromDb)
  {
    GCFDOCUMENTI.loaded = false
  }
  this.loadCollectionFromDB(GCFDOCUMENTI, childrenLevel)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Clifor.loadCliforCaratterizzazioni(
  optional boolean forceReloadFromDB = 1 // 
  optional int childrenLevel = 9999      // 
)
{
  if (forceReloadFromDB)
  {
    GCFCARATTERIZZAZIONI.loaded = false
  }
  this.loadCollectionFromDB(GCFCARATTERIZZAZIONI, childrenLevel)
}


// ──────────────────────────────────

// ****************************************************************************************************************************
// this method allows to return a collection without loading BLOB from db, if no search condition passed we limit to 20 records
// ****************************************************************************************************************************
public static IDCollection Clifor.getCollectionWithoutBlobs(
  optional string searchCondition = "" // 
)
{
  DevTools.DEPRECATE("clifor.getCollectionWithoutBlobs has been written and unit tested but no one uses it, may be some qapp so we deprecate", "", "delete the method in qappcore and delete its tests", "26.0", 
        toDate(2026, 6, 1))
   
  IDCollection loadedClifors of Clifor = new()
   
  string searchConditionInWhereClause = "%" + nullValue(searchCondition, "") + "%"
   
  // limit collection to 30 records if no filtering is applied
  if (searchCondition == "")
  {
    QualibusDB.maxRows = 20
  }
   
  // this query explicitly loads all but the BLOB, this is the idea!
  for each row (readonly)
  {
    select
      IDCONTOGCFANAGRAFICA = IDCONTO
      RAGIONESOCIALEGCFANAGRAFICA = RAGIONESOCIALE
      CODCONTOGCFANAGRAFICA = CODCONTO
      STATOCONTOGCFANAGRAFICA = STATOCONTO
      IDTIPOCONTOGCFANAGRAFICA = IDTIPOCONTO
      INDIRIZZOGCFANAGRAFICA = INDIRIZZO
      CAPGCFANAGRAFICA = CAP
      COMUNEGCFANAGRAFICA = COMUNE
      TELEFONOGCFANAGRAFICA = TELEFONO
      CELLULAREGCFANAGRAFICA = CELLULARE
      EMAILGCFANAGRAFICA = EMAIL
      INTERNETGCFANAGRAFICA = INTERNET
      PARTITAIVAGCFANAGRAFICA = PARTITAIVA
      CODICEFISCALEGCFANAGRAFICA = CODICEFISCALE
      CODPROVINCIAGCFANAGRAFICA = CODPROVINCIA
      CODSTATOGCFANAGRAFICA = CODSTATO
      CONTATTOPRINCIPALEGCFANAGRAFICA = CONTATTOPRINCIPALE
    from 
      ClientiFornitori // master table
    where
      searchCondition == "" or (searchCondition == "" or COMUNE like searchConditionInWhereClause or RAGIONESOCIALE like searchConditionInWhereClause)
    order by
      RAGIONESOCIALE
     
     
    Clifor c = new()
    c.IDCONTO = IDCONTOGCFANAGRAFICA
    c.RAGIONESOCIALE = RAGIONESOCIALEGCFANAGRAFICA
    c.CODCONTO = CODCONTOGCFANAGRAFICA
    c.STATOCONTO = STATOCONTOGCFANAGRAFICA
    c.IDTIPOCONTO = IDTIPOCONTOGCFANAGRAFICA
    c.INDIRIZZO = INDIRIZZOGCFANAGRAFICA
    c.CAP = CAPGCFANAGRAFICA
    c.COMUNE = COMUNEGCFANAGRAFICA
    c.TELEFONO = TELEFONOGCFANAGRAFICA
    c.CELLULARE = CELLULAREGCFANAGRAFICA
    c.EMAIL = EMAILGCFANAGRAFICA
    c.INTERNET = INTERNETGCFANAGRAFICA
    c.PARTITAIVA = PARTITAIVAGCFANAGRAFICA
    c.CODICEFISCALE = CODICEFISCALEGCFANAGRAFICA
    c.CODPROVINCIA = CODPROVINCIAGCFANAGRAFICA
    c.CODSTATO = CODSTATOGCFANAGRAFICA
    c.CONTATTOPRINCIPALE = CONTATTOPRINCIPALEGCFANAGRAFICA
    c.setOriginal()
    loadedClifors.add(c)
  }
  return loadedClifors
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string Clifor.getCaption(
  string additionalDescription // 
)
{
  return "Clienti e Fornitori " + "{{icon-fa-chevron-right}}"
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public int Clifor.setMainTxtFieldPropertyIndex()
{
  return toPropertyIndex(NOTETXT)
}


// ──────────────────────────────────

// *********************
// Method to be extended
// *********************
public int Clifor.setMainHtmlFieldPropertyIndex()
{
  return toPropertyIndex(NOTEHTML)
}


// ──────────────────────────────────

// ***************************************
// Descrivi lo scopo di questa funzione...
// ***************************************
public int Clifor.getMainID()
{
  return IDCONTO
}


// ──────────────────────────────────

// ***************************************
// Descrivi lo scopo di questa funzione...
// ***************************************
public int Clifor.getTypeID()
{
  return IDTIPOCONTO
}


// ──────────────────────────────────

// ***************************************
// Descrivi lo scopo di questa funzione...
// ***************************************
public int Clifor.getKordApp()
{
  return ClientiFornitori
}


// ──────────────────────────────────

// ****************************************
// Describe the purpose of this function...
// ****************************************
public string Clifor.getDescription()
{
  return RAGIONESOCIALE
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event Clifor.OnInit()
{
  int nextID = Sequence.getNextSequence(GCFN_ID_CONTO, ...)
  IDCONTO = nextID
  PROFID = 0
  PARENTPROFID = 0
  ISMAINCOMPANY = No
  TIPOPERSONA = Giuridica
  STATOCONTO = Attivo
}


// ──────────────────────────────────

// ************************************************************
// Event raised by the document after loading from the database
// ************************************************************
event Clifor.AfterLoad(
  boolean AlreadyLoaded     // A boolean parameter that specifies whether the document was previously loaded.
  IDArray LoadedCollections // IDArray type parameter that contains the collections loaded by the framework. If the value of the Already Loaded parameter is False, the Loaded Collections parameter is NULL.
)
{
  base.AfterLoad(AlreadyLoaded, LoadedCollections)
   
  boolean IsQuickLoadRequested = base.quickLoadRequested()
  if (IsQuickLoadRequested)
  {
    return 
  }
   
  // implement here code that would be skipped by QuickLoad
   
}


// ──────────────────────────────────

// ************************************************************
// Event raised to the document during the validation procedure
// ************************************************************
event Clifor.OnValidate(
  int:validateReasons Reason // An integer specifying the reason the document is being validated. The most common values are 0 to indicate complete validation before saving, or 1 to indicate a quick validati...
  inout boolean Error        // A boolean output parameter. If set to True, specifies an error condition and stops the validation immediately.
  inout boolean Skip         // A boolean output parameter. If set to True, does not check the rules for automatic validation of the document (see notes).
)
{
  base.OnValidate(Reason, Error, Skip)
   
  if (RAGIONESOCIALE == "")
  {
    Error = true
    this.setPropertyError("Il valore non può essere vuoto", RAGIONESOCIALE)
  }
   
  if (EMAIL != null)
  {
    boolean isValidEmail = this.isValidEmail()
    if (!(isValidEmail))
    {
      Error = true
      this.setPropertyError(formatMessage("Email non valida <b>|1</b>", EMAIL, ...), EMAIL)
    }
  }
}


// ──────────────────────────────────

// **************************************************************************************
// Event raised to the document during the initial steps of the document saving procedure
// **************************************************************************************
event throws exception Clifor.BeforeSave(
  inout boolean Skip         // A boolean output parameter. If set to True, tells the framework not to perform operations on the database, because they will be managed independently.
  inout boolean Cancel       // A boolean output parameter. If set to True, tells the framework to cancel the save procedure due to errors that occurred. Since the entire document is saved in the same transa...
  int:beforeSavePhases Phase // An integer from 0 to 3 that specifies the progress status of the save procedure as detailed at the beginning of the article.
)
{
  base.BeforeSave(Skip, Cancel, Phase)
   
  if (Phase == PreSave)
  {
    if (deleted)
      IdCollectionTools.deleteCollectionFromDb(this, Contacts)
  }
}


// ──────────────────────────────────

// *************************************************************
// Event raised to the document at the end of the save procedure
// *************************************************************
event throws exception Clifor.AfterSave(
  inout boolean Cancel // Boolean output parameter that makes it possible to cancel the entire save. It is equivalent to the Cancel parameter of the BeforeSave event.
//)
//{
//  boolean noteHtmlHasSomeValue = isNull(NOTEHTML) == false
//  if (noteHtmlHasSomeValue)
//  {
//    // we do not anymore convert html to RTF and save to note field
//    NOTETXT = Richcontentmanager.HtmlToText(NOTEHTML)
//  }
//}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static Clifor Clifor.getFromDB(
  int mainId                                                             // 
  optional string:getFromDBLoadingModes loadingMode = "normalZeroLevels" // 
)
{
  Clifor c = cast(MainModule.retrieve(ClientiFornitori, mainId, loadingMode))
  return c
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static GCFCARATTERIZZAZIONI GCFCARATTERIZZAZIONI.create(
  int idConto // 
)
{
  GCFCARATTERIZZAZIONI tag = new()
  tag.IDCONTO = idConto
  tag.init()
  return tag
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event GCFCARATTERIZZAZIONI.OnInit()
{
  IDCARATTERIZZAZIONE = Sequence.getNextSequence(GCFN_ID_CARATT, ...)
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event GCFTIPICARATT.OnInit()
{
  IDTIPOCARATT = Sequence.getNextSequence(GCFN_ID_TABELLE, ...)
  DESCRCARATT = "Nuovo tag"
  GRUPPO = "Gruppo"
}


// ──────────────────────────────────

// **********************************
// get TipoClifor object for given ID
// **********************************
public static TipoClifor TipoClifor.get(
  int idTipoClifor // 
)
{
  TipoClifor tc = new()
  tc.IDTIPICLIFOR = idTipoClifor
  try 
  {
    tc.loadFromDB(0)
  }
  catch 
  {
    QappCore.DTTLogMessage(formatMessage("Unable to load TipoClifor for ID: |1", idTipoClifor, ...), ..., DTTError)
  }
  return tc
}


// ──────────────────────────────────

// ****************************************************************************************************************
// given a tipo Conto (Clienti, Fornitori or Clienti e Fornitori) the most used TipoCliFor of that type is returned
// ****************************************************************************************************************
public static TipoClifor TipoClifor.getMostUsedTipoCliFor(
  int:tipiConto tipoConto // 
)
{
  Recordset tipiContoRs = new()
  select into recordset (tipiContoRs)
    GCFTIPICONTO.IDTIPICLIFOR as ID_TIPI_CLIFOR
    count(ClientiFornitori.IDCONTO) as COUIDCONGCAN
  from 
    ClientiFornitori // master table
    GCFTIPICONTO     // joined with Clienti Fornitori using key FK_GCF_ANAGRAFICA_ID_TIPO_CONTO
  where
    GCFTIPICONTO.IDTIPOCONTO == tipoConto
  group by
    GCFTIPICONTO.IDTIPICLIFOR
   
  // sort for most used (-2 means the second field - count - in descending order)
  tipiContoRs.addSortCriteria(-2)
  tipiContoRs.doSort()
   
  // retrieve the most used
  tipiContoRs.moveFirst()
   
  int idTipoCliForMostUsed = toInteger(tipiContoRs.getFieldValueIdx(1))
   
  TipoClifor tc = TipoClifor.get(idTipoCliForMostUsed)
  return tc
}


// ──────────────────────────────────



// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean TipoClifor.shouldShowReferencesTab()
{
  return SHOWREFERENCES == Yes
}


// ──────────────────────────────────

// ****************************************************************************************
// unused method at the moment of writing since all visibility is in user visibillity class
// anyway it is tested and working
// ****************************************************************************************
public static IDCollection TipoClifor.getIdsOfTipiCliForUserCanSee(
  Utente utente // 
)
{
  IDCollection tipiCliforIds of IntDataType = new()
//   
//  // alternative approach with DB for discussion
//  boolean utenteIsPers1 = utente.hasSpecificPrivilegeForKordApp(ClientiFornitori, Pers1)
//  for each row (readonly)
//  {
//    select
//      idTipoClifor = IDTIPICLIFOR
//    from 
//      GCFTIPICONTO // master table
//    where
//      (utenteIsPers1 == true) or exists(subquery)
//         select // 
//           IDTIPICLIFOR
//         from 
//           TipoCliforRoles // master table
//         where
//           TipoCliforRoles.VISUALIZZA == Yes
//           GCFTIPICONTO.IDTIPICLIFOR == TipoCliforRoles.IDTIPICLIFOR
//           TipoCliforRoles.IDUTENTE == utente.IDUTENTE
//    // 
//    IntDataType idt = IntDataType.createInteger(idTipoClifor)
//    tipiCliforIds.add(idt)
//  }
   
   
  IDCollection tipiCliForUserCanSee of TipoClifor = new()
  if (utente.hasSpecificPrivilegeForKordApp(ClientiFornitori, Pers1))
  {
    IDCollection allTipiCliFor of TipoClifor = new()
    //  
    // return all existing tipi
    select into collection (allTipiCliFor)
    from 
      TipoClifor // master table
     
    tipiCliForUserCanSee.addAll(allTipiCliFor, true)
  }
  else 
  {
    IDCollection tipoCliforRolesForUser of TipoCliforRole = TipoCliforRole.getTipiCliForUserCanSee(utente)
    for each TipoCliforRole tcr in tipoCliforRolesForUser
    {
      TipoClifor tc = this.get(tcr.IDTIPICLIFOR)
      tipiCliForUserCanSee.add(tc)
    }
  }
   
  for each TipoClifor tc in tipiCliForUserCanSee
  {
    tipiCliforIds.add(IntDataType.createInteger(tc.IDTIPICLIFOR))
  }
   
   
  return tipiCliforIds
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event TipoClifor.OnInit()
{
  IDTIPICLIFOR = Sequence.getNextSequence(GCFN_TIPI_CONTO, ...)
  IDTIPOCONTO = Clienti
  SHOWCUSTOMDATA = No
  SHOWREFERENCES = No
  SHOWCONTATTI = No
  SHOWINDIRIZZI = No
  SHOWPROMEMORIA = No
  SHOWDOCUMENTS = No
}


// ──────────────────────────────────



// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event IndirizzoClifor.OnInit()
{
  this.computeNextId()
  this.populateTipoIndirizzo()
}


// ──────────────────────────────────

// ***********************************************************
// Event raised to the document when the transaction is closed
// ***********************************************************
event IndirizzoClifor.OnEndTransaction()
{
  // this strange old arrangement to fill the ragione Sociale field which is NOT NULL, in priciple even this field not required because it is associated with 
  // IDConto FK
  this.copyParentData()
}


// ──────────────────────────────────

// ****************************************************
// Raised to the document during duplication operations
// ****************************************************
event IndirizzoClifor.OnDuplicate(
  IDDocument SourceDocument // This parameter contains the original document when duplicating the document header. Otherwise, its value is Null Object. This way, it is possible to distinguish the two cases, ...
)
{
  this.computeNextId()
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void IndirizzoClifor.computeStandardImportMetadata()
{
  base.addImportMetadato(indirizzo, toPropertyIndex(INDIRIZZO), true, null, ...)
  base.addImportMetadato(cap, toPropertyIndex(CAP), false, null, ...)
  base.addImportMetadato(comune, toPropertyIndex(COMUNE), false, null, ...)
  base.addImportMetadato(cod_provincia, toPropertyIndex(CODPROVINCIA), false, null, ...)
  base.addImportMetadato(cod_nazione, toPropertyIndex(CODSTATO), false, null, ...)
  base.addImportMetadato(ragione_sociale, toPropertyIndex(RAGIONESOCIALE), true, null, ...)
  base.addImportMetadato(descrizione, toPropertyIndex(DESCRINDIRIZZO), true, null, ...)
  base.addImportMetadato(conto, toPropertyIndex(IDCONTO), true, null, Clifor.className(false), ...)
  base.addImportMetadato(tipo, toPropertyIndex(IDTIPOINDIRIZZO), true, null, TipoIndirizzo.className(false), ...)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void IndirizzoClifor.copyParentData()
{
  if (parent)
  {
    if (Clifor.isMyInstance(parent))
    {
      Clifor parentClifor = parent
      IDCONTO = parentClifor.IDCONTO
       
      // write RagioneSociale only the first time (when still not modified)
      if (RAGIONESOCIALE == "")
      {
         RAGIONESOCIALE = parentClifor.RAGIONESOCIALE
      }
    }
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void IndirizzoClifor.computeNextId()
{
  IDINDIRIZZO = Sequence.getNextSequence(GCFN_ID_INDIRIZZO, ...)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void IndirizzoClifor.populateTipoIndirizzo()
{
  boolean tipoWithAltriFound = false
  int vIDTIPOINDIRIZZO = 0
  select into variables (tipoWithAltriFound)
    set vIDTIPOINDIRIZZO = IDTIPOINDIRIZZO
  from 
    GCFTIPIINDIRIZZO // master table
  where
    lower(DESCRTIPOINDIRIZZO) == "altri"
   
  if (!(tipoWithAltriFound))
  {
    QualibusDB.maxRows = 1
    select into variables (found variable)
      set vIDTIPOINDIRIZZO = IDTIPOINDIRIZZO
    from 
      GCFTIPIINDIRIZZO // master table
  }
  IDTIPOINDIRIZZO = vIDTIPOINDIRIZZO
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static IndirizzoClifor IndirizzoClifor.create(
  Clifor clifor // 
)
{
  IndirizzoClifor ic = new()
  ic.init()
  ic.IDCONTO = clifor.IDCONTO
  ic.RAGIONESOCIALE = clifor.RAGIONESOCIALE
  return ic
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static Contatto Contatto.create(
  Clifor clifor                // 
  optional string name = ""    // 
  optional string surname = "" // 
)
{
  Contatto contact = new()
  contact.init()
  contact.NOME = name
  contact.COGNOME = surname
  contact.NOME = name
  contact.IDCONTO = clifor.IDCONTO
   
  return contact
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Contatto.computeStandardImportMetadata()
{
  base.addImportMetadato(nome, toPropertyIndex(NOME), true, null, ...)
  base.addImportMetadato(cognome, toPropertyIndex(COGNOME), false, null, ...)
  base.addImportMetadato(titolo, toPropertyIndex(TITOLO), false, null, ...)
  base.addImportMetadato(ruolo, toPropertyIndex(RUOLO), false, null, ...)
  base.addImportMetadato(cellulare, toPropertyIndex(CELLULARE), false, null, ...)
  base.addImportMetadato(telefono, toPropertyIndex(TELEFONO), false, null, ...)
  base.addImportMetadato(email, toPropertyIndex(EMAIL), false, null, ...)
  base.addImportMetadato(fax, toPropertyIndex(FAX), false, null, ...)
  base.addImportMetadato(codice, toPropertyIndex(SKYPEID), false, null, ...)
  base.addImportMetadato(conto, toPropertyIndex(IDCONTO), true, null, Clifor.className(false), ...)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static Contatto Contatto.get(
  int idContatto // 
)
{
  Contatto contatto = new()
  contatto.IDCONTATTO = idContatto
  try 
  {
    contatto.loadFromDB(...)
  }
  catch 
  {
    contatto = null
    QappCore.DTTLogMessage(formatMessage("Unable to load contatto for ID: |1", idContatto, ...), ...)
  }
  return contatto
}


// ──────────────────────────────────

// ***********************************************************************
// text description of Contatto is returned, company name is also appended
// ***********************************************************************
public string Contatto.getFullDescription()
{
  Clifor c = getLinkedDocument(false, Clifor.className(...), ...)
  string contattoDescription = formatMessage("|1 |2 - |3", COGNOME, NOME, c.RAGIONESOCIALE, ...)
   
  return contattoDescription
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Contatto.evalIsAssistant()
{
  IsAssitant = ContactType == Assistant
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Contatto.computeIsAssistant()
{
  ContactType = if(IsAssitant, Assistant, Default)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean Contatto.canBeDeleted(
  inout string errorMessages // 
)
{
  boolean canBeDeleted = true
   
  int foundPromemoria = 0
  select into variables (found variable)
    set foundPromemoria = count(...)
  from 
    Promemoria // master table
  where
    CLIFORIDCONTATTO == IDCONTATTO
  if (foundPromemoria > 0)
  {
    canBeDeleted = false
    errorMessages = errorMessages + "<br>- controlla <b>Promemoria</b>"
  }
   
  int foundCdataContatto = 0
  select into variables (found variable)
    set foundCdataContatto = count(...)
  from 
    CDATAMODULEVALUES // master table
    CDATAFIELDSINFO   // joined with CDATA MODULE VALUES using key FK_CDATA_MODULE_VALUES_ID_CDATA_FLD
  where
    CDATAFIELDSINFO.Type == ContattoCDataType
    CDATAMODULEVALUES.VALUE == IDCONTATTO
  if (foundCdataContatto > 0)
  {
    canBeDeleted = false
    errorMessages = errorMessages + "<br>- controlla <b>Dati Personalizzati</b>"
  }
   
   
  int foundDocListeDistribuzione = 0
  select into variables (found variable)
    set foundDocListeDistribuzione = count(...)
  from 
    DistribuzioneRecipients // master table
  where
    IDCONTATTO == IDCONTATTO
  if (foundDocListeDistribuzione > 0)
  {
    canBeDeleted = false
    errorMessages = errorMessages + "<br>- controlla <b>Liste distribuzione</b>"
  }
   
  int foundEventi = 0
  select into variables (found variable)
    set foundEventi = count(...)
  from 
    Eventi // master table
  where
    DEPRECATEDIDCONTATTOCLIENTE == IDCONTATTO or DEPRECATEDIDCONTOFORNITORE == IDCONTATTO
  if (foundEventi > 0)
  {
    canBeDeleted = false
    errorMessages = errorMessages + "<br>- controlla <b>Eventi</b> (contatto cliente o fornitore)"
  }
   
  int foundInterfacce = 0
  select into variables (found variable)
    set foundInterfacce = count(...)
  from 
    PRGINTERFACCE // master table
  where
    IDCONTATTO == IDCONTATTO
  if (foundInterfacce > 0)
  {
    canBeDeleted = false
    errorMessages = errorMessages + "<br>- controlla <b>Interfacce</b>"
  }
   
  return canBeDeleted
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event Contatto.OnInit()
{
  IDCONTATTO = Sequence.getNextSequence(GCFN_ID_CONTATTO, ...)
  ATTIVO = Yes
}


// ──────────────────────────────────

// **************************************************************************
// Event called to customize the Smart Lookup procedure at the document level
// **************************************************************************
event Contatto.OnGetSmartLookup(
  Recordset RecordSet       // The recordset to be filled if the queries run during the Smart Lookup event are customized.
  inout int Level           // The Smart Lookup procedure performs multiple attempts, increasingly widening the search field. This integer parameter specifies the number of attempts made.
  inout boolean NullValue   // A boolean output parameter. If set to True, then NULL (no results found) will be returned to the panel.
  inout boolean Skip        // A boolean output parameter. If set to True, then the standard queries for the current level will not be executed.
  inout boolean Cancel      // A boolean output parameter. If set to True, the Smart Lookup procedure will be stopped.
  IDDocument CallerDocument // The document for which the smart lookup procedure is launched. It can be null.
)
{
  if (CallerDocument)
  {
    if (Promemoria.isMyInstance(CallerDocument))
    {
      Promemoria promemoria = (Promemoria)CallerDocument
      if (promemoria)
      {
         Recordset computedContattoRecordSet = promemoria.ComputeContattoLookupRecordset()
         RecordSet.copyFrom(computedContattoRecordSet)
          
         DOEventsImplementationHelper.onGetSmartLookupHandler(RecordSet, this, true, ...)
      }
    }
  }
}


// ──────────────────────────────────

// ************************************************************
// Event raised by the document after loading from the database
// ************************************************************
event Contatto.AfterLoad(
  boolean AlreadyLoaded     // A boolean parameter that specifies whether the document was previously loaded.
  IDArray LoadedCollections // IDArray type parameter that contains the collections loaded by the framework. If the value of the Already Loaded parameter is False, the Loaded Collections parameter is NULL.
)
{
  FullName = COGNOME + " " + NOME
  this.evalIsAssistant()
  this.setOriginal()
}


// ──────────────────────────────────

// **************************************************************************************
// Event raised to the document during the initial steps of the document saving procedure
// **************************************************************************************
event throws exception Contatto.BeforeSave(
  inout boolean Skip         // A boolean output parameter. If set to True, tells the framework not to perform operations on the database, because they will be managed independently.
  inout boolean Cancel       // A boolean output parameter. If set to True, tells the framework to cancel the save procedure due to errors that occurred. Since the entire document is saved in the same transa...
  int:beforeSavePhases Phase // An integer from 0 to 3 that specifies the progress status of the save procedure as detailed at the beginning of the article.
)
{
  ContactType = if(IsAssitant, Assistant, Default)
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event TipoIndirizzo.OnInit()
{
  IDTIPOINDIRIZZO = Sequence.getNextSequence(GCFN_ID_TIPO_IND, ...)
  DESCRTIPOINDIRIZZO = "Nuovo tipo indirizzo"
  AUTOMATICO = No
  SEQUENZA = 1
  ATTIVO = Yes
  CONTATTO = Yes
}


// ──────────────────────────────────

// ************************************************************
// Event raised to the document during the validation procedure
// ************************************************************
event TipoIndirizzo.OnValidate(
  int:validateReasons Reason // An integer specifying the reason the document is being validated. The most common values are 0 to indicate complete validation before saving, or 1 to indicate a quick validati...
  inout boolean Error        // A boolean output parameter. If set to True, specifies an error condition and stops the validation immediately.
  inout boolean Skip         // A boolean output parameter. If set to True, does not check the rules for automatic validation of the document (see notes).
)
{
  if (!(deleted))
  {
    if (isNull(DESCRTIPOINDIRIZZO) or DESCRTIPOINDIRIZZO == "")
    {
      this.setPropertyError("Il campo non può essere vuoto", DESCRTIPOINDIRIZZO)
      Error = true
    }
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static IDCollection TipoCliforRole.getTipiCliForUserCanSee(
  Utente utente // 
)
{
  IDCollection tipoCliforUserCanSee of TipoCliforRole = new()
  select into collection (tipoCliforUserCanSee)
  from 
    TipoCliforRole // master table
  where
    VISUALIZZA == Yes
    IDUTENTE == utente.IDUTENTE
   
  return tipoCliforUserCanSee
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Collegamento.computeBackgroundColor()
{
  BackgroundColor = RGBColor(255, 255, 255, ...)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static CollegamentoMainModule CollegamentoEvento.create(
  int mainID          // 
  int idReferenceType // 
)
{
  CollegamentoEvento collegamentoEvento = new()
  collegamentoEvento.IDEVENTO = mainID
  try 
  {
    collegamentoEvento.loadFromDB(0)
    collegamentoEvento.TypeID = idReferenceType
     
     
     
  }
  catch 
  {
    QappCore.DTTLogMessage(formatMessage("Cannot load CollegamentoRiferimentoEvento: |1", mainID, ...), ...)
    collegamentoEvento = null
  }
  return collegamentoEvento
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static CollegamentoMainModule CollegamentoPersonale.create(
  int mainID          // 
  int idReferenceType // 
)
{
  CollegamentoPersonale collegamento = new()
  collegamento.IDDIPENDENTE = mainID
  try 
  {
    collegamento.loadFromDB(0)
    collegamento.TypeID = idReferenceType
  }
  catch 
  {
    QappCore.DTTLogMessage(formatMessage("Cannot load CollegamentoRiferimentoPersonale: |1", mainID, ...), ...)
    collegamento = null
  }
  return collegamento
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static CollegamentoMainModule CollegamentoArticolo.create(
  int mainID          // 
  int idReferenceType // 
)
{
  CollegamentoArticolo collegamento = new()
  collegamento.IDARTICOLO = mainID
  try 
  {
    collegamento.loadFromDB(0)
    collegamento.TypeID = idReferenceType
  }
  catch 
  {
    QappCore.DTTLogMessage(formatMessage("Cannot load CollegamentoRiferimentoArticolo: |1", mainID, ...), ...)
    collegamento = null
  }
  return collegamento
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static CollegamentoMainModule CollegamentoAltreAnagrafiche.create(
  int mainID          // 
  int idReferenceType // 
)
{
  CollegamentoAltreAnagrafiche collegamento = new()
  collegamento.ID = mainID
  try 
  {
    collegamento.loadFromDB(0)
    collegamento.TypeID = idReferenceType
  }
  catch 
  {
    QappCore.DTTLogMessage(formatMessage("Cannot load CollegamentoRiferimentoAltreAnagrafiche: |1", mainID, ...), ...)
    collegamento = null
  }
  return collegamento
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static CollegamentoMainModule CollegamentoPrivati.create(
  int mainID          // 
  int idReferenceType // 
)
{
  CollegamentoPrivati collegamento = new()
  collegamento.IDCITTADINI = mainID
  try 
  {
    collegamento.loadFromDB(0)
    collegamento.TypeID = idReferenceType
  }
  catch 
  {
    QappCore.DTTLogMessage(formatMessage("Cannot load CollegamentoRiferimentoPrivati: |1", mainID, ...), ...)
    collegamento = null
  }
  return collegamento
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static CollegamentoMainModule CollegamentoFunzione.create(
  int mainID          // 
  int idReferenceType // 
)
{
  CollegamentoFunzione collegamento = new()
  collegamento.IDFUNZIONE = mainID
  try 
  {
    collegamento.loadFromDB(0)
    collegamento.TypeID = idReferenceType
  }
  catch 
  {
    QappCore.DTTLogMessage(formatMessage("Cannot load CollegamentoRiferimentoFunzione: |1", mainID, ...), ...)
    collegamento = null
  }
  return collegamento
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static CollegamentoMainModule CollegamentoProgetto.create(
  int mainID          // 
  int idReferenceType // 
)
{
  CollegamentoProgetto collegamento = new()
  collegamento.IDPROGETTO = mainID
  try 
  {
    collegamento.loadFromDB(0)
    collegamento.TypeID = idReferenceType
  }
  catch 
  {
    QappCore.DTTLogMessage(formatMessage("Cannot load CollegamentoRiferimentoProgetto: |1", mainID, ...), ...)
    collegamento = null
  }
  return collegamento
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static CollegamentoMainModule CollegamentoClifor.create(
  int mainID          // 
  int idReferenceType // 
)
{
  CollegamentoClifor collegamento = new()
  collegamento.IDCONTO = mainID
  try 
  {
    collegamento.loadFromDB(0)
    collegamento.TypeID = idReferenceType
  }
  catch 
  {
    QappCore.DTTLogMessage(formatMessage("Cannot load CollegamentoRiferimentoClifor: |1", mainID, ...), ...)
    collegamento = null
  }
  return collegamento
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void CollegamentoIntervento.setAdditionalProperties()
{
  string vCodeCespite = ""
  string vDescriptionCespite = ""
  select into variables (found variable)
    set vCodeCespite = Code
    set vDescriptionCespite = Description
  from 
    AltreAnagrafiche // master table
  where
    ID == IDCESPITE
   
  CodeCespite = vCodeCespite
  DescriptionCespite = vDescriptionCespite
  this.setOriginal()
}


// ──────────────────────────────────

// ************************************************************
// Event raised by the document after loading from the database
// ************************************************************
event CollegamentoIntervento.AfterLoad(
  boolean AlreadyLoaded     // A boolean parameter that specifies whether the document was previously loaded.
  IDArray LoadedCollections // IDArray type parameter that contains the collections loaded by the framework. If the value of the Already Loaded parameter is False, the Loaded Collections parameter is NULL.
)
{
  this.setAdditionalProperties()
  this.setOriginal()
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void CollegamentoQappdata.computeBackgroundColor()
{
  int qappDataColor = 0
  select into variables (found variable)
    set qappDataColor = COLOR
  from 
    NGTQAPPSSTYLES // master table
  where
    STYLEID == STYLEID
   
  BackgroundColor = qappDataColor
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event Cruscotto.OnInit()
{
  IDQUERY = Sequence.getNextSequence(QRYN_ID_QUERY, ...)
  HIDDEN = No
  MAILSENDERNOTIFICATION = No
  RECURRENCETYPE = Giorno
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static Cruscotto Cruscotto.create(
  string title                            // 
  string description                      // 
  string groupDescription                 // 
  int:cruscottoTypes cruscottoType        // 
  optional boolean isCompanyDashboard = 0 // 
)
{
  Cruscotto c = new()
  c.init()
  c.TITOLOQUERY = title
  c.DESCRQUERY = description
  c.DESCRGRUPPO = groupDescription
  c.TIPOTYPE = cruscottoType
  QappCore.DTTLogMessage(formatMessage("setting company dashboard to |1", if(isCompanyDashboard, Yes, No), ...), 6666, ...)
  c.ISCOMPDASHBOARD = if(isCompanyDashboard, Yes, No)
   
  return c
}


// ──────────────────────────────────

// ******************************************************************************************************************************
// given an idQappCOmmand the corresponding CRU QUERY record is unlinked from it (clearing the FK and making notifications False)
// ******************************************************************************************************************************
public static void Cruscotto.clearForeignKeyAndMailsenderNotifications(
  int idQappCommand // 
)
{
   
  // method implemented with a query because it is quick and dirty and it is meant at least to hide the query from outside
  DevTools.RefactoringOpportunity("this query cries for refactoring but it works well")
   
  update CRUQUERY
    set IDQAPPCOMMAND = null
    set MAILSENDERNOTIFICATION = No
  where
    IDQAPPCOMMAND == idQappCommand
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static CMBValueParser CMBValueParser.create(
  string CMBVALUE // 
  int type        // 
)
{
  CMBValueParser cvp = new()
  cvp.CMBVALUE = CMBVALUE
  cvp.Type = type
  return cvp
}


// ──────────────────────────────────

// **************************************************************************************************************************************
// This method computer combo values or name value pairs from CMB_VALUE string
// some examples of CMB_VALUE are as follows
// case 1 
//   Uno;Due 
//   [type=combo;columns=3]Uno;Due 
//   [type=Radiogroup;columns=3]Uno;Due
//  
//   in this case all three lines above,it store two element in "Elements" Map without any   value since combo is defined as "Names" only
// 
// case 2
//  
// [type=combo;columns=3](Uno,1);(Due,2)
// 
// In the above case Values in "Elements" are stored agaist "Uno and Due" are 1 and 2 respectively
// 
// 
// **************************************************************************************************************************************
private void CMBValueParser.parseContentPartOfCMBValue()
{
  // (One,1);(Two,2) - Radio Group
  // (Option1@DependsOnInedx);(Option2@DependsOnIndex)
  Elements = new()
  string elementPartInCMBValue = SH.rightUpToDelimiter(CMBVALUE, "]", ...)
  IDArray stringElements = SH.tokenizeToArray(elementPartInCMBValue, ";", ...)
  this.computeComboElements(stringElements)
}


// ──────────────────────────────────

private boolean CMBValueParser.computeNumberOfColumns(
  string inputString // 
)
{
  // columns=3
  // 
  boolean expectedBehaviour = false
  if (find(inputString, "columns=", ...) > 0)
  {
    string foundNumberOfColumnsAsString = SH.rightUpToDelimiter(inputString, "=", ...)
    if (isNumber(foundNumberOfColumnsAsString))
    {
      NumberOfColumns = toInteger(foundNumberOfColumnsAsString)
    }
    else 
    {
       
      // if value not found (like in case of [Type=Radiogroup,columns=]Contrattualizzato;Extra - real case found in Salus DB!!!
      NumberOfColumns = 2
      QappCore.DTTLogMessage("Unexpected value for columns, setting it to 2", ..., DTTWarning)
    }
    expectedBehaviour = true
  }
  else 
  {
    QappCore.DTTLogMessage("Unexpected inputString without columns info", ..., DTTError)
    expectedBehaviour = false
  }
  return expectedBehaviour
}


// ──────────────────────────────────

// *******************************************************
// checks if in the passed string there is info on Column 
// *******************************************************
private boolean CMBValueParser.stringContainsColumnsInfo(
  string inputString // 
)
{
  boolean columnInfoFound = find(inputString, "columns=", ...) > 0
  return columnInfoFound
}


// ──────────────────────────────────

// *************************************************************************************
// checks if in the passed string there is info on combo type, if found it returns true 
// *************************************************************************************
private boolean CMBValueParser.computeComboType(
  string inputString // 
)
{
  // Type=RadioGroup
  // 
  boolean expectedBehaviour = false
  if (find(inputString, "type=", ...) > 0)
  {
    string typeValue = SH.rightUpToDelimiter(inputString, "=", ...)
    ComboStyle = typeValue
    expectedBehaviour = true
  }
  else 
  {
    QappCore.DTTLogMessage("Unexpected inputString without type info", ..., DTTError)
    expectedBehaviour = false
  }
  return expectedBehaviour
}


// ──────────────────────────────────

// ******************************************************
// checks if in the passed string there is info on  Type 
// ******************************************************
private boolean CMBValueParser.stringContainsTypeInfo(
  string inputString // 
)
{
  boolean typeInfoFound = find(inputString, "type=", ...) > 0
  return typeInfoFound
}


// ──────────────────────────────────

// ***********************************************************************************
// this procedure computes the checkgroup elements considering these 4 possibilities: 
// - (value@0)
// - value@0
// - value
// - (value,9)
// ***********************************************************************************
private void CMBValueParser.computeComboElements(
  IDArray stringElements // 
)
{
  for (int i = 0; i < stringElements.length(); i = i + 1)
  {
    IDMap map = new()
    string element = trim(stringElements.getValue(i))
    string name = ""
    int value = 0
    this.parseCurrentElement(element, name, value)
    map.setValue(name, value)
    Elements.addObject(map)
    ElementsIndex.setValue(i, name)
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void CMBValueParser.identifyCmbInfo(
  string bracketPart         // 
  inout string typeInfo      // 
  inout string dependsOnInfo // 
  inout string ColumnsInfo   // 
)
{
  StringTokenizer st = new()
  st.setString(bracketPart, ",", ...)
  while (st.hasNextToken())
  {
    string token = st.nextToken()
    if (find(token, "type", ...) > 0)
    {
      typeInfo = token
    }
    if (find(token, "columns", ...) > 0)
    {
      ColumnsInfo = token
    }
    if (find(token, "dependson", ...) > 0)
    {
      dependsOnInfo = token
    }
  }
}


// ──────────────────────────────────

// *******************************************
// compute value of dependsON from inputstring
// *******************************************
private boolean CMBValueParser.computeDependsOnFieldId(
  string inputString // 
)
{
  // columns=3
  // 
  boolean expectedBehaviour = false
  if (find(inputString, "dependson=", ...) > 0)
  {
    string foundNumberOfColumnsAsString = SH.rightUpToDelimiter(inputString, "=", ...)
    if (isNumber(foundNumberOfColumnsAsString))
    {
      DependsOnFieldId = toInteger(foundNumberOfColumnsAsString)
    }
    else 
    {
      QappCore.DTTLogMessage("Unexpected value for dependsOn", ..., DTTError)
    }
    expectedBehaviour = true
  }
  else 
  {
    QappCore.DTTLogMessage("Unexpected inputString without columns info", ..., DTTError)
    expectedBehaviour = false
  }
  return expectedBehaviour
}


// ──────────────────────────────────

// ******************************************************************************************************
// this procedure takes input CurrentElements and valorize the currentElementName and CurrentElementValue
// 
// 
// -- CheckGroup examples
// [Type=Checkgroup,columns=3]Asia;AFrica;Europe
// "Asia" -> CurrentElementName = "Asia", CurrentElementValue = 0
// 
// [Dependson=7109,Type=CheckGroup,columns=4]India@0;China@0;Ghana@1;Sudan@1;Italy@2;Spain@2
// "Ghana@1" -> CurrentElementName = "Ghana", CurrentElementValue = 1
// 
// -- Radio group examples
// [Dependson=7109,Type=RadioGroup,columns=4]India@0;China@0;Ghana@1;Sudan@1;Italy@2;Spain@2
// 
// [Type=Radiogroup,columns=3]Asia;AFrica;Europe
// 
// --radio group defined to be used for calc field
// [Type=Radiogroup,columns=3](Asia,4);(AFrica,5);(Europe,6)
// "(AFrica,5)" -> CurrentElementName = "AFrica", CurrentElementValue = 5
// 
// -- handling of complex cases (string startng or ending with parenthesis handled as normal string:
// "Europa (Italia,Francia,Spagna)" and "(ONe,Two,Three) Uno,Due,Tre"
// ******************************************************************************************************
public static void CMBValueParser.parseCurrentElement(
  string currentElement           // 
  inout string currentElementName // 
  inout int currentElementValue   // 
)
{
   
  // handle of case of string ending or starting with parenthesis
  boolean elementsStartWithParenthesisOnly = left(currentElement, 1) == "(" and right(currentElement, 1) != ")"
  boolean elementsEndsWithParenthesisOnly = left(currentElement, 1) != "(" and right(currentElement, 1) == ")"
   
  if (elementsStartWithParenthesisOnly or elementsEndsWithParenthesisOnly)
  {
    currentElementName = currentElement
    currentElementValue = null
     
    return 
  }
   
  // in case of "(value@0)" we obtain "value@0"
  if (left(currentElement, 1) == "(" and right(currentElement, 1) == ")")
  {
    currentElement = mid(currentElement, 2, length(currentElement) - 2)
  }
   
  // we can have value@0 or value
  if (find(currentElement, "@", ...) > 0)
  {
    StringTokenizer st = new()
    st.setString(currentElement, "@", ...)
     
    // get left part of "@" which is a string
    currentElementName = st.nextToken()
     
    // get right part of "@" which is value
    currentElementValue = toInteger(st.nextToken())
  }
  else if (find(currentElement, ",", ...) > 0)
  {
    currentElementName = SH.leftUpToDelimiter(currentElement, ",", ...)
    currentElementValue = toInteger(SH.rightUpToDelimiter(currentElement, ",", ...))
     
  }
  else 
  {
    currentElementName = currentElement
    currentElementValue = null
  }
}


// ──────────────────────────────────

// *******************************************************
// parse combobox information (type,dependson and columns)
// *******************************************************
private boolean CMBValueParser.parseInfoPartCMBValue()
{
  StringTokenizer st = new()
  st.setString(CMBVALUE, "]", ...)
  string bracketPart = ""
  if (st.hasNextToken())
  {
    bracketPart = st.nextToken()
  }
   
  boolean bracketPartFound = find(bracketPart, "[", ...) > 0
  if (bracketPartFound == false)
  {
    ComboStyle = ComboBox
    NumberOfColumns = 2
    return false
  }
   
  // remove opening bracket
   
  bracketPart = replace(bracketPart, "[", "")
  bracketPart = lower(bracketPart)
   
  string typePart = ""
  string dependsOnPart = ""
  string columnsPart = ""
//   
  this.identifyCmbInfo(bracketPart, typePart, dependsOnPart, columnsPart)
  if (typePart != "")
  {
    this.computeComboType(typePart)
  }
  else 
  {
    // default case: if not specified we set the ComboStyle as Combo
    ComboStyle = ComboBox
  }
   
  if (columnsPart != "")
  {
    this.computeNumberOfColumns(columnsPart)
  }
  else 
  {
    // default case: if not specified we set the numberOfCloumns to 2
    NumberOfColumns = 2
  }
   
  if (dependsOnPart != "")
  {
    this.computeDependsOnFieldId(dependsOnPart)
  }
  else 
  {
    DependsOnFieldId = null
  }
  return true
}


// ──────────────────────────────────



// ──────────────────────────────────

// ***********************************************************************************************************************************************************************************
// the maskedit mask can be in three parts
// part1;part2;part3
// part1: the acutal mask (like LL000LL for italian car license plate)
// part2: can be 0 or anyother char - 0 means do not save literals (so we save only what use types) while ANY OTHER CHAR (strange C/S choice. -1 but also 1 or even X!) saves literals
// prat3: placeholder char (defaults to "_") - it is what the component displays for chars to be typed
// ***********************************************************************************************************************************************************************************
public void CMBValueParser.parseTextFieldMask()
{
  if (length(CMBVALUE) == 0)
  {
    return 
  }
   
  IDArray maskStringParts = SH.tokenizeToArray(CMBVALUE, ";", ...)
   
  // in case mask has only one part CMBVALUE is the mask...
  TextFieldMask = maskStringParts.getValue(0)
   
  // while if more ";" separated parts exist those are parsed here
  if (maskStringParts.length() >= 2)
  {
    MaskeditSaveLiterals = maskStringParts.getValue(1) != "0"
  }
  else 
  {
    MaskeditSaveLiterals = false
  }
  if (maskStringParts.length() >= 3)
  {
    MaskeditPlaceholderChar = maskStringParts.getValue(2)
  }
  else 
  {
     
    // assign the default value
    MaskeditPlaceholderChar = "_"
  }
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void CMBValueParser.parseNumberOfRowsOfMemo()
{
  // set default value
  NumberOfVisibleLinesInMemo = 3
  if (CMBVALUE != "")
  {
    if (isNumber(CMBVALUE))
    {
      NumberOfVisibleLinesInMemo = toInteger(CMBVALUE)
    }
    else 
    {
      QappCore.DTTLogMessage(formatMessage("Invalid value(number) defined in CMBVALUE", ...), ..., DTTError)
    }
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void CMBValueParser.parseDecimalPrecision()
{
  // set default value
  DecimalPrecision = 2
  if (CMBVALUE != "")
  {
    if (find(CMBVALUE, ";", ...) > 0)
    {
      string decimalPrecisionStringValue = SH.rightUpToDelimiter(CMBVALUE, ";", 1)
       
      if (decimalPrecisionStringValue != "")
      {
          
         int precision = toInteger(decimalPrecisionStringValue)
          
         if (precision >= 0 and precision < 4)
         {
           DecimalPrecision = precision
         }
         else 
         {
           DecimalPrecision = 2
         }
          
      }
    }
  }
  this.computeFloatEditMask()
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void CMBValueParser.computeFloatEditMask()
{
  string hashCharBeforeDecimal = "############."
  string hashCharAfterDecimal = SH.padLeft("", DecimalPrecision, "#")
   
  FloatFieldMask = hashCharBeforeDecimal + hashCharAfterDecimal
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void CMBValueParser.parseNullAsZero()
{
  string nullAsZeroTrueSearchKey = lower("[NullAsZero=true]")
  string nullAsZeroFalseSearchKey = lower("[NullAsZero=false]")
   
  int posTrue = find(lower(CMBVALUE), nullAsZeroTrueSearchKey, 0)
  int posFalse = find(lower(CMBVALUE), nullAsZeroFalseSearchKey, 0)
   
  if (posTrue > 0)
  {
    CMBVALUE = replace(lower(CMBVALUE), nullAsZeroTrueSearchKey, "")
    NullAsZero = true
  }
  else if (posFalse > 0)
  {
    CMBVALUE = replace(lower(CMBVALUE), nullAsZeroFalseSearchKey, "")
    NullAsZero = false
  }
  else 
  {
    NullAsZero = false
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string CustomDataValue.getValue()
{
  int valuePropertyIndex = getPropertyIndex("VALUE", true, ...)
   
  string value = null
  if (valuePropertyIndex > 0)
  {
    value = toString(getProperty(valuePropertyIndex))
  }
  return value
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void CustomDataValue.setValue(
  string value // 
)
{
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public void CustomDataValue.setIsModello(
  boolean isModello // 
)
{
  int isModelloPropertyIndex = getPropertyIndex("ISMODELLO", true, ...)
  if (isModelloPropertyIndex > 0)
  {
    string:flagYN value = if(isModello, Yes, No)
    this.setProperty(isModelloPropertyIndex, value)
  }
  else 
  {
    QappCore.DTTLogMessage("ISMODELLO property not found and it won't be set", ..., DTTInfo)
  }
}


// ──────────────────────────────────

// ***********************************************************
// method useful for testing that IS MODELLO was set correctly
// ***********************************************************
public boolean CustomDataValue.getIsModello()
{
  int valuePropertyIndex = getPropertyIndex("ISMODELLO", true, ...)
   
  boolean value = null
  if (valuePropertyIndex > 0)
  {
    string:flagYN isModelloValue = getProperty(valuePropertyIndex)
    value = isModelloValue == Yes
  }
  return value
}


// ──────────────────────────────────

// ***********************************************************************************
// Returns a description for the value of CData. Meaningful for Module Type Cdata only
// ***********************************************************************************
public string CustomDataValue.getDescription()
{
  QappCore.DTTLogMessage("ABSTRACT", ..., DTTError)
  return ""
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public static MainModuleCDValue MainModuleCDValue.getInstance(
  int mainID                                      // 
  MainModuleDatoPersonalizzatoInfo cdataFieldInfo // 
)
{
  MainModuleCDValue cv = null
   
  switch (cdataFieldInfo.Type)
  {
    case IntegerCDataType:
      cv = MainModuleCDIntegerValue.GetConcreteInstance(mainID, cdataFieldInfo)
    break
    case FloatCDataType:
      cv = MainModuleCDFloatValue.GetConcreteInstance(mainID, cdataFieldInfo)
    break
    case TextCDataType:
      cv = MainModuleCDTextValue.GetConcreteInstance(mainID, cdataFieldInfo)
    break
    case MemoCDataType:
      cv = MainModuleCDMemoValue.GetConcreteInstance(mainID, cdataFieldInfo)
    break
    case ComboCDataType:
      cv = MainModuleCDComboValue.GetConcreteInstance(mainID, cdataFieldInfo)
    break
    case DateCDataType:
      cv = MainModuleCDDateValue.GetConcreteInstance(mainID, cdataFieldInfo)
    break
    case BooleanCDataType:
      cv = MainModuleCDBooleanValue.GetConcreteInstance(mainID, cdataFieldInfo)
    break
    case CalcFloatCDataType:
      cv = MainModuleCDFloatValue.GetConcreteInstance(mainID, cdataFieldInfo)
    break
    case AltraAnagraficaCDataType:
      cv = MainModuleCDModuleValue.GetConcreteInstance(mainID, cdataFieldInfo)
    break
    case PersonaleCDataType:
      cv = MainModuleCDModuleValue.GetConcreteInstance(mainID, cdataFieldInfo)
    break
    case ProgettoCDataType:
      cv = MainModuleCDModuleValue.GetConcreteInstance(mainID, cdataFieldInfo)
    break
    case PrivatoCDataType:
      cv = MainModuleCDModuleValue.GetConcreteInstance(mainID, cdataFieldInfo)
    break
    case EventoCDataType:
      cv = MainModuleCDModuleValue.GetConcreteInstance(mainID, cdataFieldInfo)
    break
    case FunzioneCDataType:
      cv = MainModuleCDModuleValue.GetConcreteInstance(mainID, cdataFieldInfo)
    break
    case DocumentoCDataType:
      cv = MainModuleCDModuleValue.GetConcreteInstance(mainID, cdataFieldInfo)
    break
    case ArticoloCDataType:
      cv = MainModuleCDModuleValue.GetConcreteInstance(mainID, cdataFieldInfo)
    break
    case ContattoCDataType:
      cv = MainModuleCDModuleValue.GetConcreteInstance(mainID, cdataFieldInfo)
    break
    case CliForCDataType:
      cv = MainModuleCDModuleValue.GetConcreteInstance(mainID, cdataFieldInfo)
    break
    default:
      QappCore.DTTLogMessage("Main module Cdata type not supported", ..., DTTError)
    break
  }
   
  return cv
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public static MainModuleCDValue MainModuleCDValue.GetConcreteInstance(
  int mainID                                      // 
  MainModuleDatoPersonalizzatoInfo NuovoParametro // cdataFieldInfo
)
{
  return null
}


// ──────────────────────────────────

// *************************************************************************************************************************************
// centralized code to try to retrieve th cdatavalue instance and in case of issues act.
// 
// cases are:
// no instance foudn -> init is done to retrieve a new instance ready to be inserted
// one instance found whose value is null -> that instance is deleted (since NULL is a non supported case) and init done ready to insert
// one instance foudn and value is not null -> instance is returned
// 
// IN FUTURE we could use this method to handle special cases (like more instances found)
// *************************************************************************************************************************************
public MainModuleCDValue MainModuleCDValue.tryRetrievingInstance()
{
  try 
  {
    this.loadFromDB(...)
    string s = base.getValue()
    if (s = "")
    {
      QappCore.DTTLogMessage("a NULL value found in DB so we delete this non legit value", ..., DTTWarning)
      this.deleted = true
      this.saveToDB(...)
      this.init()
    }
  }
  catch 
  {
    DevTools.ToBeReviewed("in this catch it could be possible to clean DB (eg: for a cdata more values are found, while only one should be there")
    this.init()
  }
   
  return this
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public void MainModuleCDValue.setIsModelloDEPRECATED(
  boolean isModello // 
)
{
  int isModelloPropertyIndex = getPropertyIndex("ISMODELLO", true, ...)
  if (isModelloPropertyIndex > 0)
  {
    string:flagYN value = if(isModello, Yes, No)
    this.setProperty(isModelloPropertyIndex, value)
  }
  else 
  {
    QappCore.DTTLogMessage("ISMODELLO property not found and it won't be set", ..., DTTInfo)
  }
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event MainModuleCDBooleanValue.OnInit()
{
  ISMODELLO = "N"
}


// ──────────────────────────────────

// **********
//  
// **********
public string MainModuleCDBooleanValue.getValue()
{
   
   
//  DevTools.ToBeReviewed("the values returned by Qualibus DB are Y and N, not -1 and 0 (the true and false for InDe")
   
  string formatReturnValue = ""
   
  // (Yes  = "Y") and (No = "N"). So ("Y"=> -1) and ("N" => 0)
  if (VALUE == Yes)
  {
     
    formatReturnValue = toString(true)
     
  }
  else 
  {
     
    formatReturnValue = toString(false)
     
  }
   
   
  return formatReturnValue
   
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public static MainModuleCDValue MainModuleCDBooleanValue.GetConcreteInstance(
  int mainID                                      // Scrivi un commento per questo parametro o premi backspace per eliminare questo commento
  MainModuleDatoPersonalizzatoInfo cDataFieldInfo // cdataFieldInfo
)
{
  MainModuleCDBooleanValue cdbv = new()
   
  cdbv.IDCDATAFLD = cDataFieldInfo.IDCDATAFLD
  cdbv.IDMODULERECID = mainID
   
  return cdbv.tryRetrievingInstance()
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public void MainModuleCDBooleanValue.setValue(
  string value // 
)
{
  // if value = "-1" then we set Y, else N
   
  if (value == toString(true))
  {
    VALUE = Yes
  }
  else 
  {
    VALUE = No
  }
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event MainModuleCDComboValue.OnInit()
{
  ISMODELLO = "N"
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public static MainModuleCDValue MainModuleCDComboValue.GetConcreteInstance(
  int mainID                                      // 
  MainModuleDatoPersonalizzatoInfo cDataFieldInfo // cdataFieldInfo
)
{
  MainModuleCDComboValue cdcv = new()
   
  cdcv.IDCDATAFLD = cDataFieldInfo.IDCDATAFLD
  cdcv.IDMODULERECID = mainID
   
  return cdcv.tryRetrievingInstance()
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public void MainModuleCDComboValue.setValue(
  string value // 
)
{
   
  VALUE = value
   
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event MainModuleCDFloatValue.OnInit()
{
  ISMODELLO = "N"
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public static MainModuleCDValue MainModuleCDFloatValue.GetConcreteInstance(
  int mainID                                      // 
  MainModuleDatoPersonalizzatoInfo cDataFieldInfo // cdataFieldInfo
)
{
  MainModuleCDFloatValue cdfv = new()
   
  cdfv.IDCDATAFLD = cDataFieldInfo.IDCDATAFLD
  cdfv.IDMODULERECID = mainID
   
  return cdfv.tryRetrievingInstance()
}


// ──────────────────────────────────

// **********
//  
// **********
public string MainModuleCDFloatValue.getValue()
{
  string formattedValue = toString(VALUE)
  return formattedValue
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public void MainModuleCDFloatValue.setValue(
  string value // 
)
{
  if (trim(value) == "")
    VALUE = null
  else 
    VALUE = toFloat(value)
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event MainModuleCDIntegerValue.OnInit()
{
  ISMODELLO = "N"
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public static MainModuleCDValue MainModuleCDIntegerValue.GetConcreteInstance(
  int mainID                                      // 
  MainModuleDatoPersonalizzatoInfo cDataFieldInfo // cdataFieldInfo
)
{
  MainModuleCDIntegerValue cdiv = new()
   
   
  cdiv.IDCDATAFLD = cDataFieldInfo.IDCDATAFLD
  cdiv.IDMODULERECID = mainID
   
  return cdiv.tryRetrievingInstance()
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public void MainModuleCDIntegerValue.setValue(
  string value // 
)
{
  if (trim(value) == "")
    VALUE = null
  else 
    VALUE = toInteger(value)
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event MainModuleCDMemoValue.OnInit()
{
  ISMODELLO = "N"
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public static MainModuleCDValue MainModuleCDMemoValue.GetConcreteInstance(
  int mainID                                      // 
  MainModuleDatoPersonalizzatoInfo cDataFieldInfo // cdataFieldInfo
)
{
  MainModuleCDMemoValue cdmv = new()
   
  cdmv.IDCDATAFLD = cDataFieldInfo.IDCDATAFLD
  cdmv.IDMODULERECID = mainID
   
  return cdmv.tryRetrievingInstance()
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public void MainModuleCDMemoValue.setValue(
  string value // 
)
{
  VALUE = value
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event MainModuleCDTextValue.OnInit()
{
  ISMODELLO = "N"
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public static MainModuleCDValue MainModuleCDTextValue.GetConcreteInstance(
  int mainID                                      // 
  MainModuleDatoPersonalizzatoInfo cDataFieldInfo // cdataFieldInfo
)
{
  MainModuleCDTextValue cdtv = new()
   
  cdtv.IDCDATAFLD = cDataFieldInfo.IDCDATAFLD
  cdtv.IDMODULERECID = mainID
   
  return cdtv.tryRetrievingInstance()
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public void MainModuleCDTextValue.setValue(
  string value // 
)
{
  VALUE = value
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event MainModuleCDDateValue.OnInit()
{
  ISMODELLO = "N"
}


// ──────────────────────────────────

// **********
//  
// **********
public string MainModuleCDDateValue.getValue()
{
  string dateFormatString = UIParameters.getDateFormat()
  string formattedDate = format(VALUE, dateFormatString, ...)
  return formattedDate
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public static MainModuleCDValue MainModuleCDDateValue.GetConcreteInstance(
  int mainID                                      // 
  MainModuleDatoPersonalizzatoInfo cDataFieldInfo // cdataFieldInfo
)
{
  MainModuleCDDateValue cddv = new()
   
  cddv.IDCDATAFLD = cDataFieldInfo.IDCDATAFLD
  cddv.IDMODULERECID = mainID
   
  return cddv.tryRetrievingInstance()
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public void MainModuleCDDateValue.setValue(
  string value // 
)
{
  if (value == "" or value == null)
  {
    VALUE = null
  }
  else 
  {
     
     
    // we may need a regex validation before this code:
    // needs a conversion from dd/mm/yyyy to mm/dd/yyyy for DB Qualibus
    string dateFormatString = UIParameters.getDateFormat()
    string formatted = format(value, dateFormatString, ...)
    int dd = toInteger(left(formatted, 2))
    int mm = toInteger(left(right(formatted, 7), 2))
    int yyyy = toInteger(right(formatted, 4))
    VALUE = toDate(yyyy, mm, dd)
  }
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event MainModuleCDModuleValue.OnInit()
{
  ISMODELLO = "N"
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public static MainModuleCDValue MainModuleCDModuleValue.GetConcreteInstance(
  int mainID                                      // 
  MainModuleDatoPersonalizzatoInfo cDataFieldInfo // cdataFieldInfo
)
{
  MainModuleCDModuleValue cddv = new()
   
  cddv.IDCDATAFLD = cDataFieldInfo.IDCDATAFLD
  cddv.IDMODULERECID = mainID
   
  return cddv.tryRetrievingInstance()
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public void MainModuleCDModuleValue.setValue(
  string value // 
)
{
  DevTools.ToBeReviewed("CDModuleValue should be inherited by one class per module type...")
  VALUE = toInteger(value)
}


// ──────────────────────────────────

// ***********************************************************************************
// Returns a description for the value of CData. Meaningful for Module Type Cdata only
// ***********************************************************************************
public string MainModuleCDModuleValue.getDescription()
{
  // for module type a main module is retrieved and getDescription() is returned, the only exception is contatto cdata because in that case Main Module is not in the picture
   
   
  int mainID = VALUE
  string description = toString(VALUE)
  MainModuleDatoPersonalizzatoInfo cfi = new()
  MainModule mm = null
  cfi.IDCDATAFLD = IDCDATAFLD
  cfi.loadFromDB(0)
  if (cfi)
  {
    switch (cfi.Type)
    {
      case ArticoloCDataType:
         mm = MainModule.retrieve(Articoli, mainID, normalZeroLevels)
      break
      case CliForCDataType:
         mm = MainModule.retrieve(ClientiFornitori, mainID, normalZeroLevels)
      break
      case PersonaleCDataType:
         mm = MainModule.retrieve(Personale, mainID, normalZeroLevels)
      break
      case EventoCDataType:
         mm = MainModule.retrieve(Eventi, mainID, normalZeroLevels)
      break
      case AltraAnagraficaCDataType:
         mm = MainModule.retrieve(AltreAnagrafiche, mainID, normalZeroLevels)
      break
      case ProgettoCDataType:
         mm = MainModule.retrieve(Progetti, mainID, normalZeroLevels)
      break
      case PrivatoCDataType:
         mm = MainModule.retrieve(Privati, mainID, normalZeroLevels)
      break
      case FunzioneCDataType:
         mm = MainModule.retrieve(Funzioni, mainID, normalZeroLevels)
      break
      case DocumentoCDataType:
         mm = MainModule.retrieve(Documenti, mainID, normalZeroLevels)
      break
      case ContattoCDataType:
         Contatto c = Contatto.get(mainID)
         description = c.getFullDescription()
      break
    }
  }
  if (mm)
  {
    description = mm.getDescription()
  }
  else 
  {
    QappCore.DTTLogMessage(formatMessage("Contatto description (|1) has been requested.", description, ...), ..., DTTInfo)
  }
   
  return description
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public static RefdataValue RefdataValue.getInstance(
  Riferimento riferimento               // 
  RiferimentoDatoPersonalizzatoInfo rdi // 
)
{
  RefdataValue rdv = null
   
  switch (rdi.Datatype)
  {
    case IntegerCDataType:
      rdv = RefdataIntegerValue.GetConcreteInstance(riferimento, rdi)
    break
    case FloatCDataType:
      rdv = RefdataFloatValue.GetConcreteInstance(riferimento, rdi)
    break
    case TextCDataType:
      rdv = RefdataTextValue.GetConcreteInstance(riferimento, rdi)
    break
    case MemoCDataType:
      rdv = RefdataMemoValue.GetConcreteInstance(riferimento, rdi)
    break
    case ComboCDataType:
      rdv = RefdataComboValue.GetConcreteInstance(riferimento, rdi)
    break
    case DateCDataType:
      rdv = RefdataDateValue.GetConcreteInstance(riferimento, rdi)
    break
    case BooleanCDataType:
      rdv = RefdataBooleanValue.GetConcreteInstance(riferimento, rdi)
    break
    case CalcFloatCDataType:
      rdv = RefdataFloatValue.GetConcreteInstance(riferimento, rdi)
    break
    default:
      QappCore.DTTLogMessage("not supported", ..., DTTError)
    break
  }
   
   
  return rdv
}


// ──────────────────────────────────



// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public static RefdataValue RefdataBooleanValue.GetConcreteInstance(
  Riferimento riferimento                     // 
  RiferimentoDatoPersonalizzatoInfo rDataInfo // 
)
{
  RefdataBooleanValue rcv = new()
   
  rcv.IDRiferimento = riferimento.IDRiferimento
  rcv.IDDatopersRiferimento = rDataInfo.ID
  try 
  {
    rcv.loadFromDB(...)
  }
  catch 
  {
    rcv.init()
    rcv.IDRiferimento = riferimento.IDRiferimento
  }
   
  return rcv
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public void RefdataBooleanValue.setValue(
  string value // 
)
{
  // se -1 allora Y, se 0 allora N
  if (value == toString(true))
  {
    VALUE = Yes
  }
  else 
  {
    VALUE = No
  }
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public static RefdataValue RefdataComboValue.GetConcreteInstance(
  Riferimento riferimento                     // 
  RiferimentoDatoPersonalizzatoInfo rDataInfo // 
)
{
  RefdataComboValue rcv = new()
   
  rcv.IDRiferimento = riferimento.IDRiferimento
  rcv.IDDatopersRiferimento = rDataInfo.ID
   
  try 
  {
    rcv.loadFromDB(...)
  }
  catch 
  {
    rcv.init()
    rcv.IDRiferimento = riferimento.IDRiferimento
  }
   
  return rcv
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public void RefdataComboValue.setValue(
  string value // 
)
{
  // passaggio utile per il debug
  string inputValue = value
  VALUE = inputValue
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public static RefdataValue RefdataDateValue.GetConcreteInstance(
  Riferimento riferimento                     // 
  RiferimentoDatoPersonalizzatoInfo rDataInfo // 
)
{
  RefdataDateValue rfv = new()
   
  rfv.IDRiferimento = riferimento.IDRiferimento
  rfv.IDDatopersRiferimento = rDataInfo.ID
  try 
  {
    rfv.loadFromDB(...)
  }
  catch 
  {
    rfv.init()
    rfv.IDRiferimento = riferimento.IDRiferimento
  }
   
  return rfv
}


// ──────────────────────────────────

// **********
//  
// **********
public string RefdataDateValue.getValue()
{
  string dateFormatString = UIParameters.getDateFormat()
  string formattedDate = format(VALUE, dateFormatString, ...)
  return formattedDate
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public void RefdataDateValue.setValue(
  string value // 
)
{
  string dateFormatString = UIParameters.getDateFormat()
  string format = format(value, dateFormatString, ...)
  int dd = toInteger(left(format, 2))
  int mm = toInteger(left(right(format, 7), 2))
  int yyyy = toInteger(right(format, 4))
  date dateInputvalue = toDate(yyyy, mm, dd)
   
  VALUE = dateInputvalue
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public static RefdataValue RefdataFloatValue.GetConcreteInstance(
  Riferimento riferimento                     // 
  RiferimentoDatoPersonalizzatoInfo rDataInfo // 
)
{
  RefdataFloatValue rfv = new()
   
  rfv.IDRiferimento = riferimento.IDRiferimento
  rfv.IDDatopersRiferimento = rDataInfo.ID
   
  try 
  {
    rfv.loadFromDB(...)
  }
  catch 
  {
    rfv.init()
    rfv.IDRiferimento = riferimento.IDRiferimento
  }
   
  return rfv
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public string RefdataFloatValue.getValue()
{
  string formattedFloatValue = format(VALUE, "0.00", ".", ",", ...)
  return formattedFloatValue
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public void RefdataFloatValue.setValue(
  string value // 
)
{
  if (trim(value) == "")
  {
    VALUE = null
  }
  else 
  {
    float floatInputValue = toFloat(value)
    VALUE = floatInputValue
  }
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public static RefdataValue RefdataIntegerValue.GetConcreteInstance(
  Riferimento riferimento                     // 
  RiferimentoDatoPersonalizzatoInfo rDataInfo // 
)
{
  RefdataIntegerValue riv = new()
   
  riv.IDRiferimento = riferimento.IDRiferimento
  riv.IDDatopersRiferimento = rDataInfo.ID
  try 
  {
    riv.loadFromDB(...)
  }
  catch 
  {
    riv.init()
    riv.IDRiferimento = riferimento.IDRiferimento
  }
  return riv
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public void RefdataIntegerValue.setValue(
  string value // 
)
{
  if (trim(value) == "")
  {
    VALUE = null
  }
  else 
  {
    int intInputValue = toInteger(value)
    VALUE = intInputValue
  }
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public static RefdataValue RefdataMemoValue.GetConcreteInstance(
  Riferimento riferimento                     // 
  RiferimentoDatoPersonalizzatoInfo rDataInfo // 
)
{
  RefdataMemoValue rmv = new()
   
  rmv.IDRiferimento = riferimento.IDRiferimento
  rmv.IDDatopersRiferimento = rDataInfo.ID
  try 
  {
    rmv.loadFromDB(...)
  }
  catch 
  {
    rmv.init()
    rmv.IDRiferimento = riferimento.IDRiferimento
  }
  return rmv
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public void RefdataMemoValue.setValue(
  string value // 
)
{
  // passaggio utile per il debug
  string inputValue = value
  VALUE = inputValue
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public static RefdataValue RefdataTextValue.GetConcreteInstance(
  Riferimento riferimento                     // 
  RiferimentoDatoPersonalizzatoInfo rDataInfo // 
)
{
  RefdataTextValue rmv = new()
   
  rmv.IDRiferimento = riferimento.IDRiferimento
  rmv.IDDatopersRiferimento = rDataInfo.ID
   
  try 
  {
    rmv.loadFromDB(...)
  }
  catch 
  {
    rmv.init()
    rmv.IDRiferimento = riferimento.IDRiferimento
  }
  return rmv
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public void RefdataTextValue.setValue(
  string value // 
)
{
  // passaggio utile per il debug
  string inputValue = value
  VALUE = inputValue
}


// ──────────────────────────────────

// ************************************************************************************************************************************************************************************
// in case after parsing a calc(float) formua some valeus are not replaced (it might happen in modelli evento if a section is not included in modello) the values are replace with zero
// 
// e.g: 
// initial formuka  ([123]+[124]+[125])/3
// replaced formula (10+[124]+[125])/3
// note: NES.MATH will raise exception in pasing the replace formula, so this method makes sure that the non replace ones are replaced with zero
// 
// replaced formula by this metod: (10+0+0)/3
// 
// 
// NOTE: this method is public because it is unit tested, but theoretically it should be private
// ************************************************************************************************************************************************************************************
public string DatoPersonalizzato.handleNotReplacedCustomData(
  string calcValueToBeCalculated // 
)
{
  string computedString = calcValueToBeCalculated
  //  
  // TEMP SOLUTION TO PASS THE UNIT TEST
  boolean stringContainsOpeningSquareBracket = false
  stringContainsOpeningSquareBracket = find(computedString, "[", ...) > 0
  while (stringContainsOpeningSquareBracket)
  {
    int positionOfFirstOpeningBracket = find(computedString, "[", ...)
    int positionOfFirstClosingBracket = find(computedString, "]", ...)
    string subStringThatContainsUnhandledCustomData = mid(computedString, positionOfFirstOpeningBracket, positionOfFirstClosingBracket - positionOfFirstOpeningBracket + 1)
    computedString = replace(computedString, subStringThatContainsUnhandledCustomData, "0")
     
    // search Again for [ to avoid infinte loop
    stringContainsOpeningSquareBracket = find(computedString, "[", ...) > 0
  }
  return computedString
}


// ──────────────────────────────────

// ************************************************************************************************************
// This method calculate the value of calculated field eg.  
// field 128 = 2 
// field 129 = 3 
// and calculated field.CMB_VALUE = [128]+[129]  
// it replaces the [128] with 2 and [129] with 3 and finally 2+3 is evaluated and set as calculated field value
// ************************************************************************************************************
public void DatoPersonalizzato.computeCalcFloat()
{
  // popuplate the collection of subscribers witht he actual objects
  DatoPersonalizzatoInfo currentCdataInfo = this.getCustomDataInfo()
   
  string calcValueToBeCalculated = this.extractOnlyFormulaPart()
   
   
  boolean atLeastOneNullFormulaMemberFound = false
   
  for each DatoPersonalizzato dpb in ObservedElementsDatoPresonalizzato
  {
    string fieldValue = ""
    DatoPersonalizzatoInfo parentCdataFieldInfo = dpb.getCustomDataInfo()
     
    if (parentCdataFieldInfo.isComboField())
    {
      string selectedStringInCombo = dpb.getValueAsString()
      int comboElementValue = parentCdataFieldInfo.getComboElementValue(selectedStringInCombo)
      fieldValue = toString(comboElementValue)
    }
    else 
    {
      fieldValue = dpb.getValueAsString()
    }
    string fieldValueToBeReplaced = fieldValue
    fieldValueToBeReplaced = if(fieldValue == "", "0", fieldValue)
    if (fieldValue == "")
    {
      atLeastOneNullFormulaMemberFound = true
    }
     
    calcValueToBeCalculated = replace(calcValueToBeCalculated, "[" + toString(parentCdataFieldInfo.getFieldID()) + "]", fieldValueToBeReplaced)
  }
   
   
  // handle not replaced Customdata
   
  calcValueToBeCalculated = this.handleNotReplacedCustomData(calcValueToBeCalculated)
   
  // evaluate with the the value using nesMath component
   
  float calculatedValue = NesMath.evaluateExpression(calcValueToBeCalculated)
   
   
  if (atLeastOneNullFormulaMemberFound and !(currentCdataInfo.NullAsZero))
  {
    calculatedValue = null
  }
   
  this.setValueAsString(toString(calculatedValue))
   
}


// ──────────────────────────────────

// *****************************************************************************************************
// This method parse/compute the elements based on DependsON Field
// [21] = Asia;Africa;Europe  -- Child CD
// [22] = [dependson=21,type=Radiogroup]India@0;China@0;Ghana@1;Nizeria@1;Italy@2;France@2  -- Parent CD
// 
// in this case if Europe is selected in Field [21], the field 22 should have Italy and France elements
// 
// *****************************************************************************************************
public void DatoPersonalizzato.computeDependsOnElements()
{
  // code with referring example above
   
  // we are focusing on the field where dependsOn is defined, in this case it is  [22] and we are looping in the collection of [21]
  // in theory in case of Combo DependsOn there is only a child, [21]
   
   
  DatoPersonalizzatoInfo currentcustomDataFieldInfo = cast(this.getCustomDataInfo())
   
  // the current implementation of depends on relies on the fact that one depends on observes only one "parent", so count ==1 in the next if, but this is very specific to the curent implementation
  if (ObservedElementsDatoPresonalizzato and ObservedElementsDatoPresonalizzato.count() == 1)
  {
    QappCore.DTTLogMessage(formatMessage("Observed Cdata Id: |1, Caption: |2", currentcustomDataFieldInfo.getFieldID(), currentcustomDataFieldInfo.getCaption(), ...), ...)
    ObservedElementsDatoPresonalizzato.moveFirst()
     
    // extract 21
    DatoPersonalizzato parent = (DatoPersonalizzato)ObservedElementsDatoPresonalizzato.getAt()
    if (parent)
    {
      DatoPersonalizzatoInfo parentCdataFieldInfo = cast(parent.getCustomDataInfo())
       
      string actualValueOfParent = parent.getValueAsString()
      if (actualValueOfParent == "" or actualValueOfParent == null)
      {
//          
         // clear the elements of the current Element (that is the child of the parent)
         currentcustomDataFieldInfo.Elements.clear()
         currentcustomDataFieldInfo.Elements = new()
          
      }
      else 
      {
          
         // read the index of the actualSubValue [21]
         int indexOfSelectedSubscriberValue = -1
         IDArray ida = parentCdataFieldInfo.ElementsIndex
         for (int i = 0; i < ida.length(); i = i + 1)
         {
           string value = ida.getValue(i)
           if (actualValueOfParent == value)
           {
             indexOfSelectedSubscriberValue = i
             break 
           }
         }
          
          
         // clean the situation of element in [22]
         currentcustomDataFieldInfo.parseCmbValue()
          
         // actually in 22 we have all the possibile elements (India@0);(Ghana@1)
          
          
         IDArray elements = currentcustomDataFieldInfo.getElements()
         IDArray elementsToKeep = new()
         for (int j = 0; j < elements.length(); j = j + 1)
         {
           // (India@0);(Ghana@1)
           IDMap elementMap = (IDMap)elements.getObject(j)
           IDArray elementKeys = elementMap.getKeys()
            
           // extract "Ghana"
           string theOnlyElementName = elementKeys.getValue(0)
            
           // extract 1 of (Ghana@1)
           int currentElementDependency = elementMap.getValue(theOnlyElementName)
            
           // if index matches we keep it
           if (currentElementDependency == indexOfSelectedSubscriberValue)
           {
             elementsToKeep.addObject(elementMap)
           }
         }
          
         currentcustomDataFieldInfo.Elements = elementsToKeep
          
          
         // at this point if we selected "Europe" in [21], we will have elements in [22] that contains only italy and france
          
      }
       
       
    }
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static DatoPersonalizzato DatoPersonalizzato.factory(
  IDDocument owner                          // mainModule and Riferimenti only supported
  DatoPersonalizzatoInfo customDataInfoBase // 
)
{
  DatoPersonalizzato createdObject = null
   
  boolean ownerIsMainModule = MainModule.isMyInstance(owner)
  boolean ownerIsRiferimento = Riferimento.isMyInstance(owner)
   
  boolean correctCustomDataInfoType = false
   
  if (ownerIsMainModule)
  {
    MainModule mm = (MainModule)owner
    correctCustomDataInfoType = customDataInfoBase.typeName() == MainModuleDatoPersonalizzatoInfo.className(...)
    MainModuleDatoPersonalizzatoInfo cfi = cast(customDataInfoBase)
    boolean isModello = owner.typeName() == ModelloEvento.className(...)
    createdObject = MainModuleDatoPersonalizzato.create(cfi, mm.getMainID(), isModello)
  }
  else if (ownerIsRiferimento)
  {
    Riferimento r = (Riferimento)owner
    correctCustomDataInfoType = customDataInfoBase.typeName() == RiferimentoDatoPersonalizzatoInfo.className(...)
    RiferimentoDatoPersonalizzatoInfo rdi = cast(customDataInfoBase)
    createdObject = RdatoPersonalizzato.create(r, rdi)
  }
  else 
  {
    QappCore.DTTLogMessage("Only main module and riferimenti supported", ..., DTTError)
  }
  if (!(correctCustomDataInfoType))
  {
    QappCore.DTTLogMessage("WRONG CLASS PASSED", ..., DTTError)
  }
   
  if (createdObject != null)
  {
    string currentlyStoredValue = createdObject.CustomDataValue.getValue()
    createdObject.storeOriginalStringValue(currentlyStoredValue)
     
    createdObject.initializeUniqueId()
    createdObject.setOwner(cast(owner))
  }
   
  return createdObject
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void DatoPersonalizzato.setCustomDataInfo(
  DatoPersonalizzatoInfo customDataInfo // Write a comment for this parameter or press backspace to delete this comment
)
{
  DatoPersonalizzatoInfo = customDataInfo
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public DatoPersonalizzatoInfo DatoPersonalizzato.getCustomDataInfo()
{
  return DatoPersonalizzatoInfo
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public CustomDataValue DatoPersonalizzato.getCustomDataValueObject()
{
  return CustomDataValue
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void DatoPersonalizzato.setCustomDataValueObject(
  CustomDataValue customDataValue // Write a comment for this parameter or press backspace to delete this comment
)
{
  CustomDataValue = customDataValue
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string DatoPersonalizzato.getValueAsString()
{
  CustomDataValue cdvb = this.getCustomDataValueObject()
  return cdvb.getValue()
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void DatoPersonalizzato.setValueAsString(
  string value // Write a comment for this parameter or press backspace to delete this comment
)
{
  CustomDataValue cdvb = this.getCustomDataValueObject()
   
  // it a non empty value is passed...
  if (nullValue(value, "") == "")
  {
    this.updated = false
     
    string currentValue = cdvb.getValue()
     
    if ((nullValue(OriginalStringValue, "") != "") or currentValue != "")
    {
      DevTools.ToBeReviewed("this entire method has been created by duplicating the old setValue and the comment below was written with that in mind.")
      //  
      // it is essential to clear the field because setvalue is called using MainModule.SetDatipersonlazaati on endtransaction
      // and if any mandatory custom data is represented by it, it will be false case to validate since it will have old value in Field
      boolean originalStringValueIsNotEmpty = OriginalStringValue != ""
      cdvb.setValue("")
      cdvb.inserted = false
      cdvb.updated = originalStringValueIsNotEmpty
      cdvb.deleted = originalStringValueIsNotEmpty
       
      // we mark this as updated to inform the DO Framework that on this.SaveToDb the before save events must be triggered (since this is not a DB object so update is handled manually)
      this.updated = true
    }
    else 
    {
       
      // if this else executes it means we are setting an empty value and originally it was already empty so it means "nothing to be done"
      cdvb.inserted = false
      cdvb.updated = false
      cdvb.deleted = false
    }
  }
  else 
  {
     
    // ...if a empty value is passed
    cdvb.deleted = false
    if (nullValue(OriginalStringValue, "") = "")
    {
      cdvb.inserted = true
    }
    else 
    {
      cdvb.updated = true
    }
     
     
    cdvb.setIsModello(cdvb.getIsModello())
    cdvb.setValue(value)
    //  
    // used in the event before Save to store in the db the CDValue updated
    this.updated = cdvb.updated
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static void DatoPersonalizzato.initializeCollection(
  IDatoPersonalizzatoOwner owner // Write a comment for this parameter or press backspace to delete this comment
)
{
   
  IDCollection DatoPersonalizzatoBaseCollection of DatoPersonalizzato = owner.getNonInitializedDatoPersonalizzatoCollection()
   
  // loop on all the collection of "siblings" dato personalizzato (e.g. all main module datiP)
  for each DatoPersonalizzato dpb1 in DatoPersonalizzatoBaseCollection
  {
    DatoPersonalizzatoInfo outerCdataFieldInfo = cast(dpb1.getCustomDataInfo())
    boolean isCalcField = outerCdataFieldInfo.getCustomDataType() == CalcFloatCDataType
    boolean isComboField = outerCdataFieldInfo.getCustomDataType() == ComboCDataType
    boolean observerNeedsToBeSet = isCalcField or isComboField
    if (observerNeedsToBeSet)
    {
      string formula = lower(outerCdataFieldInfo.getFormulaAsString())
      formula = dpb1.normalizeFormulaSpaces(formula)
      for each DatoPersonalizzato dpb in DatoPersonalizzatoBaseCollection
      {
          
         DatoPersonalizzatoInfo innerCdataFieldInfo = cast(dpb.getCustomDataInfo())
         int cdataId = innerCdataFieldInfo.getFieldID()
         boolean innerFoundInFormula = find(formula, formatMessage("[|1]", cdataId, ...), ...) > 0
         boolean dependsOnFoundInFormula = find(formula, formatMessage("dependson=|1", cdataId, ...), ...) > 0
         boolean datoPersonalizzatoInnerNeedsToBeObserved = innerFoundInFormula or dependsOnFoundInFormula
          
         if (datoPersonalizzatoInnerNeedsToBeObserved)
         {
           dpb1.ObservedElementsDatoPresonalizzato.addRef(dpb)
            
         }
      }
    }
  }
   
   
  for each DatoPersonalizzato dpb in DatoPersonalizzatoBaseCollection
  {
     
    // in case any radio/combo/checkgroup we is depends on any other parent field , so we set dependent items only
    dpb.computeDependsOnElements()
  }
   
  owner.setDatoPersonalizzatoCollection(DatoPersonalizzatoBaseCollection)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string DatoPersonalizzato.getDescription()
{
  CustomDataValue cdvb = this.getCustomDataValueObject()
  return cdvb.getDescription()
}


// ──────────────────────────────────

// ***********************************************************
// returnes a computedOnTheFly unique ID useful in comparisons
// ***********************************************************
public string DatoPersonalizzato.getUniqueId()
{
  return UniqueId
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void DatoPersonalizzato.initializeUniqueId()
{
  DatoPersonalizzatoInfo cdib = this.getCustomDataInfo()
  string uniqueCodeOfCustomDataInfoBase = cdib.getUniqueCode()
   
  UniqueId = uniqueCodeOfCustomDataInfoBase
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public IDCollection DatoPersonalizzato.getObservers()
{
  IDCollection collectionGeneratedFromArray of DatoPersonalizzato = new()
  IDCollection ownerCollection of DatoPersonalizzato = Owner.getDatoPersonalizzatoCollection()
  string uniqueIDOfThis = this.getUniqueId()
  for each DatoPersonalizzato dpb in ownerCollection
  {
    for each DatoPersonalizzato dpb1 in dpb.ObservedElementsDatoPresonalizzato
    {
      string observedElementUniqueId = dpb1.getUniqueId()
      boolean thisIsBeingObserved = uniqueIDOfThis == observedElementUniqueId
      if (thisIsBeingObserved)
      {
         collectionGeneratedFromArray.addRef(dpb)
      }
    }
  }
   
   
  return collectionGeneratedFromArray
   
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void DatoPersonalizzato.setOwner(
  IDatoPersonalizzatoOwner owner // 
)
{
  Owner = owner
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public IDDocument DatoPersonalizzato.getOwner()
{
  return cast(Owner)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void DatoPersonalizzato.notifyUiResponsibleAboutUpdatedObsevers()
{
  IDCollection observers of DatoPersonalizzato = this.getObservers()
  for each DatoPersonalizzato observer in observers
  {
    DatoPersonalizzatoInfo observerCdataFieldInfo = cast(observer.getCustomDataInfo())
     
    // boolean made to call observer.refreshUiValue once only
    boolean notifyRenderingHelper = false
    if (observerCdataFieldInfo.isCalcField())
    {
      QappCore.DTTLogMessage(formatMessage("calc field found in observer list |1", observerCdataFieldInfo.getFieldID(), ...), ..., DTTInfo)
      notifyRenderingHelper = true
    }
    else if (observerCdataFieldInfo.DependsOnFieldId > 0)
    {
      // clear existing value of observer
      observer.setValueAsString("")
      observer.computeDependsOnElements()
      notifyRenderingHelper = true
    }
     
    if (notifyRenderingHelper)
      observer.refreshUiValue()
  }
}


// ──────────────────────────────────

// ****************************************************************************************************************************************************************************************************************
// // ******************************************************************************************************************************************************************************************************************­
// **************// Refreshes the UI component by pushing the current value to the RenderingHelper.// Called when observers (CalcFloat, DependsOn) need to update their display after a parent changes// *********************­
// *******************************************************************************************************************************************************************************************************// ***********­
// *****************************************************************************************************************************************************************************************************
public void DatoPersonalizzato.refreshUiValue()
{
  string value = this.getValueAsString()
   
  RenderingHelper rh = this.getRenderingHelper()
   
  DatoPersonalizzatoInfo dpi = cast(this.getCustomDataInfo())
  QappCore.DTTLogMessage(formatMessage("RdatoPers.updateUIValueFromASpecificObserver 3, cdfieldInfo ID:|1,Name: |2", dpi.getFieldID(), dpi.getCaption(), ...), ..., DTTInfo)
  int propertyIndex = rh.getIndexOfField(dpi)
   
  rh.setPropertyClientComponentReady(value, propertyIndex, dpi)
}


// ──────────────────────────────────

// ***************************************************************************************
// This method remove NullAsZero and decimal precision from the formula value of CalcField
// ***************************************************************************************
public string DatoPersonalizzato.extractOnlyFormulaPart()
{
  string formulaPartOnly = ""
  DatoPersonalizzatoInfo cdib = this.getCustomDataInfo()
  string formulaString = cdib.getFormulaAsString()
   
  // removed NullAsZero=true/false from formula
  string nullAsZeroTrueSearchKey = lower("[NullAsZero=true]")
  string nullAsZeroFalseSearchKey = lower("[NullAsZero=false]")
   
  int posTrue = find(lower(formulaString), nullAsZeroTrueSearchKey, 0)
  int posFalse = find(lower(formulaString), nullAsZeroFalseSearchKey, 0)
   
  if (posTrue > 0)
  {
    formulaPartOnly = replace(lower(formulaString), nullAsZeroTrueSearchKey, "")
  }
  else if (posFalse > 0)
  {
    formulaPartOnly = replace(lower(formulaString), nullAsZeroFalseSearchKey, "")
  }
  else 
  {
    formulaPartOnly = formulaString
  }
   
  // remove precision part from formula
  formulaPartOnly = SH.leftUpToDelimiter(formulaPartOnly, ";", 1)
   
  formulaPartOnly = this.normalizeFormulaSpaces(formulaPartOnly)
  return formulaPartOnly
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void DatoPersonalizzato.performSpecificValidation(
  int:validateReasons Reason // Write a comment for this parameter or press backspace to delete this comment
  inout boolean Error        // 
)
{
  QappCore.DTTLogMessage("ABSTRACT", ..., DTTError)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void DatoPersonalizzato.performCommonValidation(
  IDDocument renderingHelper // Write a comment for this parameter or press backspace to delete this comment
  int fieldIndex             // 
  int:validateReasons Reason // 
  inout boolean Error        // 
)
{
  // common validation for 
  // 1. Reference Custom data or nornal custom data Field value is out of range wrt to Min/Max
  // 2. Reference Custom data or nornal custom data Field Mandatoryness is validated
   
  if (QappCore.InhibitCdataRenderingHelperQuickValidation)
  {
    if (Reason == Quick)
    {
      return 
    }
  }
  DatoPersonalizzatoInfo cdataFieldInfo = this.getCustomDataInfo()
  string value = ""
  if (CustomDataValue != null)
  {
    value = this.getValueAsString()
  }
  if (nullValue(value, "") == "" and this.isValueMandatory())
  {
    Error = true
    if (renderingHelper)
    {
      renderingHelper.setPropertyError("Dati personalizzati obbligatori non definiti", fieldIndex)
      renderingHelper.refreshUserInterface()
    }
     
    return 
  }
  if (nullValue(value, "") == "")
  {
    return 
  }
  if (cdataFieldInfo.getCustomDataType() == IntegerCDataType or cdataFieldInfo.getCustomDataType() == FloatCDataType)
  {
    float val = toFloat(value)
    float min = nullValue(cdataFieldInfo.getMinValue(), 0)
    float max = nullValue(cdataFieldInfo.getMaxValue(), 0)
     
    boolean hasLimits = (min != 0 or max != 0)
    boolean outOfRange = not(val >= min and val <= max)
    if (hasLimits and outOfRange)
    {
      Error = true
      if (renderingHelper)
      {
         QappCore.DTTLogMessage(renderingHelper.typeName(), 4343434343, ...)
         renderingHelper.setPropertyError(formatMessage("Valore fuori dai limiti consentiti. Range (|1 - |2)", min, max, ...), fieldIndex)
         renderingHelper.refreshUserInterface()
      }
    }
  }
   
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private RenderingHelper DatoPersonalizzato.getRenderingHelper()
{
  IDatoPersonalizzatoOwner owner = cast(this.getOwner())
  return cast(owner.getRenderingHelper())
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private int DatoPersonalizzato.getValueFieldIndex()
{
  int index = 0
  DatoPersonalizzatoInfo cdFieldInfo = this.getCustomDataInfo()
  RenderingHelper rh = this.getRenderingHelper()
  if (rh)
  {
    index = rh.getIndexOfField(cdFieldInfo)
  }
   
  return index
}


// ──────────────────────────────────

// *********************************************************************
// returns true if dato personalizzato must have the value set
// it must be extended in a subclass if a specialized behavior is needed
// otherwise the superclass one is executed
// *********************************************************************
public boolean DatoPersonalizzato.isValueMandatory()
{
   
  // extend only to override this default behavior (THIS IS NOT "ABSTRACT"!!!)
  DatoPersonalizzatoInfo cdataFieldInfo = this.getCustomDataInfo()
  return cdataFieldInfo.isMandatory()
}


// ──────────────────────────────────

// *****************************
// setter of OriginalStringValue
// 
// *****************************
public void DatoPersonalizzato.storeOriginalStringValue(
  string value // 
)
{
  OriginalStringValue = value
}


// ──────────────────────────────────

// *************************************************************
// Removes whitespace inside bracket field references so that
// [ 5481 ] or  [5481 ] or [ 5481], all are normalized to [5481]
// *************************************************************
public string DatoPersonalizzato.normalizeFormulaSpaces(
  string formula // 
)
{
  string formulaWithoutSpaces = ""
  boolean insideBracket = false
   
  for (int i = 1; i <= length(formula); i = i + 1)
  {
    string character = mid(formula, i, 1)
    if (character == "[")
    {
      insideBracket = true
      formulaWithoutSpaces = formulaWithoutSpaces + character
    }
    else if (character == "]")
    {
      insideBracket = false
      formulaWithoutSpaces = formulaWithoutSpaces + character
    }
    else if (insideBracket and character == " ")
    {
      // drop spaces inside brackets
       
    }
    else 
    {
      formulaWithoutSpaces = formulaWithoutSpaces + character
    }
  }
  return formulaWithoutSpaces
}


// ──────────────────────────────────

// **************************************************************************************
// Event raised to the document during the initial steps of the document saving procedure
// **************************************************************************************
event throws exception DatoPersonalizzato.BeforeSave(
  inout boolean Skip         // A boolean output parameter. If set to True, tells the framework not to perform operations on the database, because they will be managed independently.
  inout boolean Cancel       // A boolean output parameter. If set to True, tells the framework to cancel the save procedure due to errors that occurred. Since the entire document is saved in the same transa...
  int:beforeSavePhases Phase // An integer from 0 to 3 that specifies the progress status of the save procedure as detailed at the beginning of the article.
)
{
  if (Phase == PreSave)
  {
    if (CustomDataValue.updated)
    {
      string currentValueAsString = CustomDataValue.getValue()
      CustomDataValue.saveToDB(...)
      string s = CustomDataValue.saveToXML(false, "", ...)
       
      // update the stored originalStringValue
      this.storeOriginalStringValue(currentValueAsString)
    }
    if (deleted)
    {
      // while deleting the customDataValue,if customDataValue object is just inserted(which is on init event) and still does not exists in DB , we should not fire SaveToDB to it because it will raise an
      // execption
      if (!(CustomDataValue.inserted))
      {
         DatoPersonalizzatoInfo dpi = this.getCustomDataInfo()
         CustomDataValue.deleted = true
         try 
         {
           CustomDataValue.saveToDB(...)
         }
         catch 
         {
           QappCore.DTTLogMessage(formatMessage("unable to save deletion of Custom data: |1, Id: |2, DataType: |3", dpi.getCaption(), dpi.getFieldID(), decode(dpi.getCustomDataType(), CdataType), ...), ...)
         }
      }
    }
  }
   
}


// ──────────────────────────────────

// ************************************************************
// Event raised to the document during the validation procedure
// ************************************************************
event DatoPersonalizzato.OnValidate(
  int:validateReasons Reason // An integer specifying the reason the document is being validated. The most common values are 0 to indicate complete validation before saving, or 1 to indicate a quick validati...
  inout boolean Error        // A boolean output parameter. If set to True, specifies an error condition and stops the validation immediately.
  inout boolean Skip         // A boolean output parameter. If set to True, does not check the rules for automatic validation of the document (see notes).
)
{
  RenderingHelper rh = this.getRenderingHelper()
  int fieldIndex = this.getValueFieldIndex()
  if (fieldIndex >= 0)
  {
    // write some common validation here
    this.performCommonValidation(rh, fieldIndex, Reason, Error)
     
    // specific validation done here
    this.performSpecificValidation(Reason, Error)
  }
  if (Error)
  {
    // inform to rendering helper
     
  }
   
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public void MainModuleDatoPersonalizzato.setValueDEPRECATED(
  string value // 
)
{
  if (nullValue(value, "") == "")
  {
    this.updated = false
    if (nullValue(MainModuleCDValueOLD.getValue(), "") != "")
    {
      // it is essential to clear the field because setvalue is called using MainModule.SetDatipersonlazaati on endtransaction
      // and if any mandatory custom data is represented by it, it will be false case to validate since it will have old value in Field
      MainModuleCDValueOLD.setValue("")
      MainModuleCDValueOLD.deleted = true
      this.updated = true
    }
  }
  else 
  {
    if (nullValue(MainModuleCDValueOLD.getValue(), "") = "")
      MainModuleCDValueOLD.inserted = true
     
    boolean modelloEventoIsMyParent = ModelloEvento.isMyInstance(parent)
    MainModuleCDValueOLD.setIsModelloDEPRECATED(modelloEventoIsMyParent)
    MainModuleCDValueOLD.setValue(value)
    //  
    // used in the event before Save to store in the db the CDValue updated
    this.updated = MainModuleCDValueOLD.updated
  }
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public string MainModuleDatoPersonalizzato.getValueDEPRECATED()
{
  return MainModuleCDValueOLD.getValue()
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public static DatoPersonalizzato MainModuleDatoPersonalizzato.create(
  MainModuleDatoPersonalizzatoInfo cDataFieldInfo // 
  int mainID                                      // 
  optional boolean isModello = 0                  // 
)
{
  MainModuleDatoPersonalizzato dp = new()
  dp.setCustomDataInfo(cDataFieldInfo)
  CustomDataValue cdv = MainModuleCDValue.getInstance(mainID, cDataFieldInfo)
  cdv.setIsModello(isModello)
  dp.setCustomDataValueObject(cdv)
  return dp
}


// ──────────────────────────────────

// **************************
// Returns CData Descripition
// **************************
public string MainModuleDatoPersonalizzato.getDescriptionDEPRECATED()
{
  return base.getValueAsString()
}


// ──────────────────────────────────

// ************************************************************************************************************************************************************************************
// in case after parsing a calc(float) formua some valeus are not replaced (it might happen in modelli evento if a section is not included in modello) the values are replace with zero
// 
// e.g: 
// initial formuka  ([123]+[124]+[125])/3
// replaced formula (10+[124]+[125])/3
// note: NES.MATH will raise exception in pasing the replace formula, so this method makes sure that the non replace ones are replaced with zero
// 
// replaced formula by this metod: (10+0+0)/3
// 
// 
// NOTE: this method is public because it is unit tested, but theoretically it should be private
// ************************************************************************************************************************************************************************************
public string MainModuleDatoPersonalizzato.handleNotReplacedCustomData(
  string calcValueToBeCalculated // 
)
{
  string computedString = calcValueToBeCalculated
  //  
  // TEMP SOLUTION TO PASS THE UNIT TEST
  boolean stringContainsOpeningSquareBracket = false
  stringContainsOpeningSquareBracket = find(computedString, "[", ...) > 0
  while (stringContainsOpeningSquareBracket)
  {
    int positionOfFirstOpeningBracket = find(computedString, "[", ...)
    int positionOfFirstClosingBracket = find(computedString, "]", ...)
    string subStringThatContainsUnhandledCustomData = mid(computedString, positionOfFirstOpeningBracket, positionOfFirstClosingBracket - positionOfFirstOpeningBracket + 1)
    computedString = replace(computedString, subStringThatContainsUnhandledCustomData, "0")
     
    // search Again for [ to avoid infinte loop
    stringContainsOpeningSquareBracket = find(computedString, "[", ...) > 0
  }
  return computedString
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void MainModuleDatoPersonalizzato.performSpecificValidation(
  int:validateReasons Reason // Write a comment for this parameter or press backspace to delete this comment
  inout boolean Error        // 
)
{
  // kept for future use
   
}


// ──────────────────────────────────

// *********************************************************************
// returns true if dato personalizzato must have the value set
// it must be extended in a subclass if a specialized behavior is needed
// otherwise the superclass one is executed
// *********************************************************************
public boolean MainModuleDatoPersonalizzato.isValueMandatory()
{
   
  boolean isValueMandatory = null
   
  // extended because in MMDatoPersonalizzato we have a special behavior for modelli eventi
  DatoPersonalizzatoInfo cdataFieldInfo = base.getCustomDataInfo()
   
  if (MainModule.isMyInstance(base.getOwner()))
  {
    MainModule mm = cast(base.getOwner())
    int:kordapp ka = mm.getKordApp()
     
    if (ka == ModelliEventi)
    {
      isValueMandatory = false
    }
    else 
    {
      isValueMandatory = cdataFieldInfo.isMandatory()
    }
  }
   
  return isValueMandatory
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public string RdatoPersonalizzato.getValueDEPRECATED()
{
  return RefdataValueOLD.getValue()
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public void RdatoPersonalizzato.setValueDEPRECATED(
  string value // 
)
{
  if (value == "")
  {
    // we allow to set null("") value too
    RefdataValueOLD.setValue(value)
    RefdataValueOLD.deleted = true
    this.updated = true
     
  }
  else 
  {
    RefdataValueOLD.setValue(value)
    this.updated = RefdataValueOLD.updated
  }
}


// ──────────────────────────────────



// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void RdatoPersonalizzato.performSpecificValidation(
  int:validateReasons Reason // Write a comment for this parameter or press backspace to delete this comment
  inout boolean Error        // 
)
{
  // kept for future use
   
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public int DatoPersonalizzatoInfo.getFieldID()
{
  QappCore.DTTLogMessage("ABSTRACT", ..., DTTError)
  return null
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean DatoPersonalizzatoInfo.isComboField()
{
  QappCore.DTTLogMessage("ABSTRACT", ..., DTTError)
  return null
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string DatoPersonalizzatoInfo.getFormulaAsString()
{
  QappCore.DTTLogMessage("ABSTRACT", ..., DTTError)
  return ""
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean DatoPersonalizzatoInfo.isCalcField()
{
  QappCore.DTTLogMessage("ABSTRACT", ..., DTTError)
  return null
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string DatoPersonalizzatoInfo.getUniqueCode()
{
  return null
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public int DatoPersonalizzatoInfo.getCustomDataType()
{
  QappCore.DTTLogMessage("ABSTRACT", ..., DTTError)
  return null
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static DatoPersonalizzatoInfo DatoPersonalizzatoInfo.factory(
  IDDocument owner                // 
  string name                     // 
  int:cdataType type              // 
  optional string mandatory = "N" // 
)
{
  DatoPersonalizzatoInfo createdObject = null
   
  boolean ownerIsReferenceType = ReferenceType.isMyInstance(owner)
  boolean ownerIsCustomDataSection = CdataSection.isMyInstance(owner)
   
  if (ownerIsCustomDataSection)
  {
    CdataSection cs = (CdataSection)owner
    createdObject = MainModuleDatoPersonalizzatoInfo.create(cs, type, name, mandatory)
  }
  else if (ownerIsReferenceType)
  {
    ReferenceType rt = (ReferenceType)owner
    createdObject = RiferimentoDatoPersonalizzatoInfo.create(rt, name, type, mandatory)
  }
  else 
  {
    QappCore.DTTLogMessage("CustomDataInfoBase.Factory support only CustomDataSection and ReferenceType", ..., DTTError)
  }
   
  return createdObject
}


// ──────────────────────────────────

// ******************************************************************************************************************************************************************************
// this reads the CMB_VALUE field of the Custom Data definition to parse the [type=X,columns=Y] and get data from it and the Content part like Italia@0;Spain@1;India@2;Turkia@99
// returned value is true if no problems are found
// ******************************************************************************************************************************************************************************
public void DatoPersonalizzatoInfo.parseCmbValue()
{
  CMBValueParser cvp = CMBValueParser.create(this.getFormulaAsString(), this.getCustomDataType())
  cvp.doParse()
   
  // combo
  NumberOfColumns = cvp.NumberOfColumns
   
  if (Elements.length() > 0)
  {
    QappCore.DTTLogMessage(formatMessage("CustomData: |1, Elements are being re-assigned, they had already been copmputed before. parseCmbValue recopmutes Elements so for example the depends on elements are 
          cleared by calling it again", this.getCaption(), ...), ..., DTTInfo)
  }
  Elements = cvp.Elements
  ComboStyle = cvp.ComboStyle
  DependsOnFieldId = cvp.DependsOnFieldId
  ElementsIndex = cvp.ElementsIndex
   
   
  // memo
  NumberOfVisibleLinesInMemo = cvp.NumberOfVisibleLinesInMemo
   
  // text
  TextFieldMask = cvp.TextFieldMask
  MaskeditSaveLiterals = cvp.MaskeditSaveLiterals
  MaskeditPlaceholderChar = cvp.MaskeditPlaceholderChar
   
  // float and decimal
  DecimalPrecision = cvp.DecimalPrecision
  FloatFieldMask = cvp.FloatFieldMask
  NullAsZero = cvp.NullAsZero
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void DatoPersonalizzatoInfo.setCustomDataType(
  int dataType // 
)
{
  QappCore.DTTLogMessage("ABSTRACT", ..., DTTError)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void DatoPersonalizzatoInfo.setFormulaAsString(
  string formulaString // 
)
{
  QappCore.DTTLogMessage("ABSTRACT", ..., DTTError)
}


// ──────────────────────────────────

// *******************************************************
// this method return Inde Field DataType from CD DataType
// *******************************************************
public int DatoPersonalizzatoInfo.getIndeFieldDataType()
{
  int:dataTypes dataType = null
  int:cdataType type = this.getCustomDataType()
  switch (type)
  {
    case IntegerCDataType:
      dataType = Integer
    break
    case ComboCDataType:
      dataType = Character
    break
    case MemoCDataType:
    case TextCDataType:
    case BooleanCDataType:
      dataType = Character
    break
    case DateCDataType:
      dataType = Date
    break
    case FloatCDataType:
    case CalcFloatCDataType:
      dataType = FixedDecimal
    break
    case ArticoloCDataType:
    case CliForCDataType:
    case ContattoCDataType:
    case EventoCDataType:
    case AltraAnagraficaCDataType:
    case PersonaleCDataType:
    case ProgettoCDataType:
    case FunzioneCDataType:
    case PrivatoCDataType:
    case DocumentoCDataType:
      dataType = Character
    break
  }
  return dataType
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean DatoPersonalizzatoInfo.isModuleType()
{
  boolean isMOduleType = false
  int:cdataType type = this.getCustomDataType()
  switch (type)
  {
    case ArticoloCDataType:
    case CliForCDataType:
    case ContattoCDataType:
    case EventoCDataType:
    case AltraAnagraficaCDataType:
    case PersonaleCDataType:
    case ProgettoCDataType:
    case FunzioneCDataType:
    case PrivatoCDataType:
    case DocumentoCDataType:
      isMOduleType = true
    break
  }
  return isMOduleType
}


// ──────────────────────────────────

// **********************************************************************************
// centralized method to get field field Maxlength based on field type
// this method will be used mostly while rendering and before callinmg panel.AddField
// **********************************************************************************
public int DatoPersonalizzatoInfo.getFieldMaxLength()
{
  int maxLength = 256
  int:cdataType type = this.getCustomDataType()
  switch (type)
  {
    case MemoCDataType:
    case TextCDataType:
      maxLength = 100000000
    break
    default:
      maxLength = 256
    break
  }
  return maxLength
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean DatoPersonalizzatoInfo.isMandatory()
{
  QappCore.DTTLogMessage("ABSTRACT", ..., DTTError)
  return null
}


// ──────────────────────────────────

public float DatoPersonalizzatoInfo.getMinValue()
{
  QappCore.DTTLogMessage("ABSTRACT", ..., DTTError)
  return null
}


// ──────────────────────────────────

public float DatoPersonalizzatoInfo.getMaxValue()
{
  QappCore.DTTLogMessage("ABSTRACT", ..., DTTError)
  return null
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string DatoPersonalizzatoInfo.getCaption()
{
  QappCore.DTTLogMessage("ABSTRACT", ..., DTTError)
  return null
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean DatoPersonalizzatoInfo.isReadOnly()
{
  QappCore.DTTLogMessage("ABSTRACT", ..., DTTError)
  return null
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string DatoPersonalizzatoInfo.getCSSClass()
{
  string decodedCdataType = replace(decode(this.getCustomDataType(), CdataType), " ", "")
   
  // remove parenthesis (useful for "Calc (Float)")
  decodedCdataType = replace(decodedCdataType, "(", "")
  decodedCdataType = replace(decodedCdataType, ")", "")
  string fieldClass = formatMessage("customData|1", decodedCdataType, ...)
  return fieldClass
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string DatoPersonalizzatoInfo.getHintText()
{
  return ""
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string DatoPersonalizzatoInfo.getWatermark()
{
  return ""
}


// ──────────────────────────────────

// ******************
// getter of Elements
// ******************
public IDArray DatoPersonalizzatoInfo.getElements()
{
  return Elements
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public int DatoPersonalizzatoInfo.getComboElementValue(
  string name // 
)
{
  int elementValue = 0
  for (int i = 0; i < Elements.length(); i = i + 1)
  {
    IDMap nameValueCombination = (IDMap)Elements.getObject(i)
    IDArray keys = nameValueCombination.getKeys()
     
    // get first key name
    string nameInMap = keys.getValue(0)
    if (nameInMap == name)
    {
      elementValue = nameValueCombination.getValue(name)
      break 
    }
  }
  return elementValue
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public IDArray DatoPersonalizzatoInfo.getComboElementNames()
{
  IDArray comboNames = new()
  for (int i = 0; i < Elements.length(); i = i + 1)
  {
    IDMap nameValueCombination = (IDMap)Elements.getObject(i)
    IDArray ida = nameValueCombination.getKeys()
    string name = ida.getValue(0)
    comboNames.addValue(name)
  }
  return comboNames
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event MainModuleDatoPersonalizzatoInfo.OnInit()
{
  IDCDATAFLD = Sequence.getNextSequence(TABN_ID_COMMON_CD_FLD, ...)
  Active = Yes
  Mandatory = No
  MandatoryOnClosure = No
  Readonly = No
  RUBRICA = No
  Sequence = 1
//  NullAsZero = true
}


// ──────────────────────────────────

// ****************************************************
// Raised to the document during duplication operations
// ****************************************************
event MainModuleDatoPersonalizzatoInfo.OnDuplicate(
  IDDocument SourceDocument // This parameter contains the original document when duplicating the document header. Otherwise, its value is Null Object. This way, it is possible to distinguish the two cases, ...
)
{
  MainModuleDatoPersonalizzatoInfo original = (MainModuleDatoPersonalizzatoInfo)SourceDocument
  if (original and original.IDCDATAFLD > 0)
  {
    this.init()
    Caption = original.Caption + " (copia)"
    Sequence = original.Sequence
    CMBVALUE = original.CMBVALUE
    MINVALUE = original.MINVALUE
    MAXVALUE = original.MAXVALUE
    Readonly = original.Readonly
    RUBRICA = original.RUBRICA
    Mandatory = original.Mandatory
    MandatoryOnClosure = original.MandatoryOnClosure
    NumberOfColumns = original.NumberOfColumns
    Elements = original.Elements
    ComboStyle = original.ComboStyle
    DependsOnFieldId = original.DependsOnFieldId
    ElementsIndex = original.ElementsIndex
  }
}


// ──────────────────────────────────

// ************************************************************
// Event raised to the document during the validation procedure
// ************************************************************
event MainModuleDatoPersonalizzatoInfo.OnValidate(
  int:validateReasons Reason // An integer specifying the reason the document is being validated. The most common values are 0 to indicate complete validation before saving, or 1 to indicate a quick validati...
  inout boolean Error        // A boolean output parameter. If set to True, specifies an error condition and stops the validation immediately.
  inout boolean Skip         // A boolean output parameter. If set to True, does not check the rules for automatic validation of the document (see notes).
)
{
  if (Type == ComboCDataType and updated)
  {
    boolean isValid = CustomDataComboFieldValidation.isValid(CMBVALUE)
    if (!(isValid))
    {
       
      // to support not stored sections in db (useful for temp objects created in unit tests) we explicitly handle the case of null retrieved section
      CdataSection cs = CdataSection.get(IDCDATASEC)
      string sectionDescription = if(cs != null, cs.Description, "")
      this.setPropertyError(formatMessage("Configurazione errata del dato personalizzato combo |1 (|2)", Caption, sectionDescription, ...), CMBVALUE)
      Error = true
    }
  }
   
  if (Type == CalcFloatCDataType and updated)
  {
    string errors = CustomDataCalcFloatValidation.isValid(CMBVALUE)
    if (errors != "")
    {
      Error = true
      this.setPropertyError(errors, CMBVALUE)
    }
  }
   
}


// ──────────────────────────────────

// ********************************************************************
// Event raised to the document before loading a collection of its type
// ********************************************************************
event MainModuleDatoPersonalizzatoInfo.BeforeLoadCollection(
  IDDocument Parent                     // The parent document that requested loading of the collection. It can be Null Object if the event is raised by the LoadCollectionByExample procedure.
  IDCollection Collection of IDDocument // The collection to be loaded.
  inout boolean Skip                    // A boolean output parameter. If set to True, standard loading will not be performed.
)
{
  if (Collection.loaded)
  {
    return 
  }
  if (CdataSection.isMyInstance(Parent))
  {
    CdataSection cs = (CdataSection)Parent
     
    QappCore.DTTLogMessage(cs.Name, 5555, ...)
    Skip = true
     
    boolean skipRenderingOfSection = cs.shouldSkipRendering()
     
    IDCollection allCdataFieldsOfSection of MainModuleDatoPersonalizzatoInfo = new()
    select into collection (allCdataFieldsOfSection)
    from 
      MainModuleDatoPersonalizzatoInfo // master table
    where
      Active == Yes
      IDCDATASEC == cs.IDCDATASEC
    order by
      Sequence
      Caption
     
    // if the section is not visible by the user (because "skip rendering of section") we set in the collection only the calcFloat data: in this way we load in memory all the calc(float) fields so the
    // computations can be done correctly
    if (skipRenderingOfSection)
    {
      for each MainModuleDatoPersonalizzatoInfo cfi in allCdataFieldsOfSection
      {
         if (cfi.Type != CalcFloatCDataType)
         {
           cfi.deleted = true
         }
      }
      allCdataFieldsOfSection.removeDeleted()
    }
     
     
    Collection.addAll(allCdataFieldsOfSection, true)
    Collection.loaded = true
     
    // since the collection is just retrieved we mark as original or it will look modified
    Collection.setOriginal()
  }
}


// ──────────────────────────────────

// **************************
// Creates a Cdata Field Info
// **************************
public static MainModuleDatoPersonalizzatoInfo MainModuleDatoPersonalizzatoInfo.create(
  CdataSection section            // 
  int:cdataType type              // 
  string caption                  // 
  optional string mandatory = "N" // 
)
{
  MainModuleDatoPersonalizzatoInfo cfi = new()
  cfi.init()
  cfi.IDCDATASEC = section.IDCDATASEC
  cfi.Caption = caption
  cfi.Type = type
  cfi.Mandatory = mandatory
   
  return cfi
}


// ──────────────────────────────────

// ************************************************
// get CdataFieldInfo object for given IdCDataField
// ************************************************
public static MainModuleDatoPersonalizzatoInfo MainModuleDatoPersonalizzatoInfo.get(
  int idCdataField // 
)
{
  MainModuleDatoPersonalizzatoInfo cfi = new()
  cfi.IDCDATAFLD = idCdataField
  try 
  {
    cfi.loadFromDB(0)
  }
  catch 
  {
    QappCore.DTTLogMessage(formatMessage("Unable to load cdata Field information for ID: |1", idCdataField, ...), ..., DTTError)
  }
  return cfi
}


// ──────────────────────────────────



// ──────────────────────────────────

// ****************************************************************
// given a kordapp it returns the matching QualibusModule CdataType
// ****************************************************************
public static int MainModuleDatoPersonalizzatoInfo.translateKordToCdataType(
  int:kordapp kordapp // 
)
{
  int:cdataType type = 0
  switch (kordapp)
  {
    case Articoli:
      type = ArticoloCDataType
    break
    case ClientiFornitori:
      type = CliForCDataType
    break
    case AltreAnagrafiche:
      type = AltraAnagraficaCDataType
    break
    case Progetti:
      type = ProgettoCDataType
    break
    case Eventi:
      type = EventoCDataType
    break
    case Personale:
      type = PersonaleCDataType
    break
    case Funzioni:
      type = FunzioneCDataType
    break
    case Privati:
      type = PrivatoCDataType
    break
    case DocumentoCDataType:
      type = Documenti
    break
  }
  return type
}


// ──────────────────────────────────

// ******************************************************************
// method to find if Cdata field is non active and of type CalcFloat 
// ******************************************************************
public boolean MainModuleDatoPersonalizzatoInfo.isNonActiveCalcField()
{
  boolean nonActiveCalcCdataField = (Active == No) and (Type == CalcFloatCDataType)
   
  return nonActiveCalcCdataField
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean MainModuleDatoPersonalizzatoInfo.isReadonlyForUi(
  string existingValue       // 
  optional UrlToken urlToken // 
)
{
  // special mode to force the input for readonly fields
  boolean forceEnableSpecialInputModeForReadonlyField = urlToken != null and urlToken.SbloccaCampiReadonly
   
  if (this.Type == CalcFloatCDataType)
  {
    return true
  }
   
  if (this.Readonly == Yes)
  {
    if (existingValue != null)
    {
      return true
    }
    return !(forceEnableSpecialInputModeForReadonlyField)
  }
  return false
}


// ──────────────────────────────────

// **********************************************************************
// converts custom data type in kordappp (it works only for module types)
// **********************************************************************
public static int MainModuleDatoPersonalizzatoInfo.getKordAppFromModuleTypeCustomDataType(
  int:cdataType cdataType // 
)
{
  int:kordapp computedKordApp = 0
  switch (cdataType)
  {
    case ArticoloCDataType:
      computedKordApp = Articoli
    break
    case CliForCDataType:
      computedKordApp = ClientiFornitori
    break
    case ContattoCDataType:
      computedKordApp = ClientiFornitori
    break
    case EventoCDataType:
      computedKordApp = Eventi
    break
    case AltraAnagraficaCDataType:
      computedKordApp = AltreAnagrafiche
    break
    case PersonaleCDataType:
      computedKordApp = Personale
    break
    case ProgettoCDataType:
      computedKordApp = Progetti
    break
    case FunzioneCDataType:
      computedKordApp = Funzioni
    break
    case PrivatoCDataType:
      computedKordApp = Privati
    break
    case DocumentoCDataType:
      computedKordApp = Documenti
    break
  }
  return computedKordApp
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string MainModuleDatoPersonalizzatoInfo.getUniqueCode()
{
  return "CFI" + toString(IDCDATAFLD)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean MainModuleDatoPersonalizzatoInfo.isCalcField()
{
  return Type == CalcFloatCDataType
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string MainModuleDatoPersonalizzatoInfo.getFormulaAsString()
{
  return CMBVALUE
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean MainModuleDatoPersonalizzatoInfo.isComboField()
{
  return Type == ComboCDataType
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public int MainModuleDatoPersonalizzatoInfo.getFieldID()
{
  return IDCDATAFLD
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public int MainModuleDatoPersonalizzatoInfo.getCustomDataType()
{
  return Type
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void MainModuleDatoPersonalizzatoInfo.setFormulaAsString(
  string formulaString // 
)
{
  CMBVALUE = formulaString
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void MainModuleDatoPersonalizzatoInfo.setCustomDataType(
  int dataType // 
)
{
  Type = dataType
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean MainModuleDatoPersonalizzatoInfo.isMandatory()
{
  return Mandatory == Yes
}


// ──────────────────────────────────

public float MainModuleDatoPersonalizzatoInfo.getMinValue()
{
  return nullValue(MINVALUE, 0)
}


// ──────────────────────────────────

public float MainModuleDatoPersonalizzatoInfo.getMaxValue()
{
  return nullValue(MAXVALUE, 0)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string MainModuleDatoPersonalizzatoInfo.getCaption()
{
  return Caption
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean MainModuleDatoPersonalizzatoInfo.isReadOnly()
{
  return Readonly == Yes
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string MainModuleDatoPersonalizzatoInfo.getHintText()
{
  return HINTTEXT
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string MainModuleDatoPersonalizzatoInfo.getWatermark()
{
  return WATERMARK
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean MainModuleDatoPersonalizzatoInfo.isModuleType()
{
  boolean isMOduleType = false
  switch (Type)
  {
    case ArticoloCDataType:
    case CliForCDataType:
    case ContattoCDataType:
    case EventoCDataType:
    case AltraAnagraficaCDataType:
    case PersonaleCDataType:
    case ProgettoCDataType:
    case FunzioneCDataType:
    case PrivatoCDataType:
    case DocumentoCDataType:
      isMOduleType = true
    break
  }
  return isMOduleType
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public int MainModuleDatoPersonalizzatoInfo.getkordApp()
{
  CdataSection cs = getLinkedDocument(false, CdataSection.className(...), ...)
  return cs.KordApp
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static RiferimentoDatoPersonalizzatoInfo RiferimentoDatoPersonalizzatoInfo.create(
  ReferenceType refType           // 
  string name                     // 
  int:cdataType dataType          // 
  optional string mandatory = "N" // 
)
{
  RiferimentoDatoPersonalizzatoInfo ri = new()
  ri.init()
  ri.IDTipoRiferimento = refType.IDTipoRiferimento
  ri.Nome = name
  ri.Datatype = dataType
  ri.Obbligatorio = mandatory
   
  return ri
}


// ──────────────────────────────────

// *****************************************************
// get RefdataInfo object for given ID(ID_EVA_REF_CDATA)
// *****************************************************
public static RiferimentoDatoPersonalizzatoInfo RiferimentoDatoPersonalizzatoInfo.get(
  int ID // 
)
{
  RiferimentoDatoPersonalizzatoInfo rdi = new()
  rdi.ID = ID
  try 
  {
    rdi.loadFromDB(0)
  }
  catch 
  {
    rdi = null
    QappCore.DTTLogMessage(formatMessage("Unable to load reference cdata Field information for ID: |1", ID, ...), ..., DTTError)
  }
  return rdi
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string RiferimentoDatoPersonalizzatoInfo.getUniqueCode()
{
  return "RDI" + toString(ID)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean RiferimentoDatoPersonalizzatoInfo.isCalcField()
{
  return Datatype == CalcFloatRefDataType
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string RiferimentoDatoPersonalizzatoInfo.getFormulaAsString()
{
  return CMBVALUE
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean RiferimentoDatoPersonalizzatoInfo.isComboField()
{
  return Datatype == ComboRefDataType
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public int RiferimentoDatoPersonalizzatoInfo.getFieldID()
{
  return ID
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public int RiferimentoDatoPersonalizzatoInfo.getCustomDataType()
{
  return Datatype
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void RiferimentoDatoPersonalizzatoInfo.setFormulaAsString(
  string formulaString // 
)
{
  CMBVALUE = formulaString
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void RiferimentoDatoPersonalizzatoInfo.setCustomDataType(
  int dataType // 
)
{
  Datatype = dataType
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean RiferimentoDatoPersonalizzatoInfo.isMandatory()
{
  return Obbligatorio == Yes
}


// ──────────────────────────────────

public float RiferimentoDatoPersonalizzatoInfo.getMinValue()
{
  return MINVALUE
}


// ──────────────────────────────────

public float RiferimentoDatoPersonalizzatoInfo.getMaxValue()
{
  return MAXVALUE
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string RiferimentoDatoPersonalizzatoInfo.getCaption()
{
  return Nome
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean RiferimentoDatoPersonalizzatoInfo.isReadOnly()
{
  return SOLOLETTURA == Yes
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static IDCollection RiferimentoDatoPersonalizzatoInfo.getSequenzaBasedSortedCollection(
  ReferenceType refType // 
)
{
  refType.loadRefCdataInfo()
  IDCollection refCDs of RiferimentoDatoPersonalizzatoInfo = refType.DatipersRiferimenti
  RiferimentoDatoPersonalizzatoInfo rdpi = new()
  int sequenzaPropertyIndex = rdpi.getPropertyIndex("SEQUENZA", true, true, true, true)
  refCDs.addSortCriteria(sequenzaPropertyIndex)
  refCDs.doSort()
   
  return refCDs
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event RiferimentoDatoPersonalizzatoInfo.OnInit()
{
  ID = Sequence.getNextSequence(EVAN_ID_REF_CDATA, ...)
  Sequenza = 1
  Attivo = Yes
  Obbligatorio = No
  WIDTH = 0
  SOLOLETTURA = No
}


// ──────────────────────────────────

// ****************************************************
// Raised to the document during duplication operations
// ****************************************************
event RiferimentoDatoPersonalizzatoInfo.OnDuplicate(
  IDDocument SourceDocument // This parameter contains the original document when duplicating the document header. Otherwise, its value is Null Object. This way, it is possible to distinguish the two cases, ...
)
{
  ID = Sequence.getNextSequence(EVAN_ID_REF_CDATA, ...)
}


// ──────────────────────────────────

// ************************************************************
// Event raised to the document during the validation procedure
// ************************************************************
event RiferimentoDatoPersonalizzatoInfo.OnValidate(
  int:validateReasons Reason // An integer specifying the reason the document is being validated. The most common values are 0 to indicate complete validation before saving, or 1 to indicate a quick validati...
  inout boolean Error        // A boolean output parameter. If set to True, specifies an error condition and stops the validation immediately.
  inout boolean Skip         // A boolean output parameter. If set to True, does not check the rules for automatic validation of the document (see notes).
)
{
  if (Datatype == ComboCDataType and updated)
  {
    boolean isValid = CustomDataComboFieldValidation.isValid(CMBVALUE)
    if (!(isValid))
    {
      ReferenceType rt = ReferenceType.get(IDTipoRiferimento)
      this.setPropertyError(formatMessage("Configurazione errata del dato personalizzato combo |1 (|2)", Nome, rt.Name, ...), CMBVALUE)
      Error = true
    }
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static DatoPersonalizzatoSnapshot DatoPersonalizzatoSnapshot.factory(
  IDDocument owner // 
)
{
  DatoPersonalizzatoSnapshot dps = null
   
  boolean ownerIsMainModule = MainModule.isMyInstance(owner)
  boolean ownerIsRiferimento = Riferimento.isMyInstance(owner)
   
  if (ownerIsMainModule)
  {
    MainModule mm = (MainModule)owner
    dps = CdataSnapshot.create(mm, QappCore.Loggeduser)
  }
  else if (ownerIsRiferimento)
  {
    Riferimento r = (Riferimento)owner
    dps = RefCDSnapshot.create(r, QappCore.Loggeduser)
  }
  else 
  {
    QappCore.DTTLogMessage("Only main module and riferimenti supported", ..., DTTError)
  }
  return dps
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public void DatoPersonalizzatoSnapshot.SetValue(
  MainModuleDatoPersonalizzato datoPers // 
)
{
  QappCore.DTTLogMessage("ABSTRACT", ..., DTTError)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static CdataSnapshot CdataSnapshot.create(
  MainModule mainModule // 
  Utente utente         // 
)
{
  CdataSnapshot cs = new()
  cs.init()
  int thisKordApp = mainModule.getKordApp()
   
  cs.IDMODULERECID = mainModule.getMainID()
  cs.Timestamp = now()
  cs.KORDAPP = thisKordApp
  if (utente)
    cs.IDUTENTE = utente.IDUTENTE
   
  boolean parentIsModello = mainModule.typeName() == ModelloEvento.className(...)
  cs.ISMODELLO = if(parentIsModello, Yes, No)
   
  return cs
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void CdataSnapshot.loadHistoryCdata()
{
  IDCollection result of CDHistoryValue = new()
   
  // history boolean
  IDCollection bools of CDHistoryBooleanValue = new()
  select into collection (bools)
    set IDCDATAHISTBOOLEANVALUES = IDCDATAHISTBOOLEANVALUES
    set IDCDATAHISTLIST = IDCDATAHISTLIST
    set IDCDATAFLD = IDCDATAFLD
    set DATA = DATA
    set IDDOMANDE = IDDOMANDE
  from 
    CDATAHISTBOOLEANVALUES // master table
  where
    IDCDATAHISTLIST = IDCDATAHISTLIST
  result.addAll(bools, ...)
   
  // history combo
  IDCollection comboVal of CDHistoryComboValue = new()
  select into collection (comboVal)
    set IDCDATAHISTCOMBOVALUES = IDCDATAHISTCOMBOVALUES
    set IDCDATAHISTLIST = IDCDATAHISTLIST
    set IDCDATAFLD = IDCDATAFLD
    set DATA = DATA
    set IDDOMANDE = IDDOMANDE
  from 
    CDATAHISTCOMBOVALUES // master table
  where
    IDCDATAHISTLIST = IDCDATAHISTLIST
  result.addAll(comboVal, ...)
   
  // history date
  IDCollection dataVal of CDHistoryDateValue = new()
  select into collection (dataVal)
    set IDCDATAHISTDATEVALUES = IDCDATAHISTDATEVALUES
    set IDCDATAHISTLIST = IDCDATAHISTLIST
    set IDCDATAFLD = IDCDATAFLD
    set DATA = DATA
    set IDDOMANDE = IDDOMANDE
  from 
    CDATAHISTDATEVALUES // master table
  where
    IDCDATAHISTLIST = IDCDATAHISTLIST
  result.addAll(dataVal, ...)
   
  // history float
  IDCollection floatVal of CDHistoryFloatValue = new()
  select into collection (floatVal)
    set IDCDATAHISTFLOATVALUES = IDCDATAHISTFLOATVALUES
    set IDCDATAHISTLIST = IDCDATAHISTLIST
    set IDCDATAFLD = IDCDATAFLD
    set DATA = DATA
    set IDDOMANDE = IDDOMANDE
  from 
    CDATAHISTFLOATVALUES // master table
  where
    IDCDATAHISTLIST = IDCDATAHISTLIST
  result.addAll(floatVal, ...)
   
  // hystory integer
  IDCollection intVal of CDHistoryIntegerValue = new()
  select into collection (intVal)
    set IDCDATAHISTINTEGERVALUES = IDCDATAHISTINTEGERVALUES
    set IDCDATAHISTLIST = IDCDATAHISTLIST
    set IDCDATAFLD = IDCDATAFLD
    set DATA = DATA
    set IDDOMANDE = IDDOMANDE
  from 
    CDATAHISTINTEGERVALUES // master table
  where
    IDCDATAHISTLIST = IDCDATAHISTLIST
  result.addAll(intVal, ...)
   
  // history memo
  IDCollection memoVal of CDHistoryMemoValue = new()
  select into collection (memoVal)
    set IDCDATAHISTMEMOVALUES = IDCDATAHISTMEMOVALUES
    set IDCDATAHISTLIST = IDCDATAHISTLIST
    set IDCDATAFLD = IDCDATAFLD
    set DATA = DATA
    set IDDOMANDE = IDDOMANDE
  from 
    CDATAHISTMEMOVALUES // master table
  where
    IDCDATAHISTLIST = IDCDATAHISTLIST
  result.addAll(memoVal, ...)
   
  // history module
  IDCollection moduleVal of CDHistoryModuleValue = new()
  select into collection (moduleVal)
    set IDCDATAHISTMODULEVALUES = IDCDATAHISTMODULEVALUES
    set IDCDATAHISTLIST = IDCDATAHISTLIST
    set IDCDATAFLD = IDCDATAFLD
    set DATA = DATA
    set IDDOMANDE = IDDOMANDE
  from 
    CDATAHISTMODULEVALUES // master table
  where
    IDCDATAHISTLIST = IDCDATAHISTLIST
  result.addAll(moduleVal, ...)
   
  // history text
  IDCollection textVal of CDHistoryTextValue = new()
  select into collection (textVal)
    set IDCDATAHISTTEXTVALUES = IDCDATAHISTTEXTVALUES
    set IDCDATAHISTLIST = IDCDATAHISTLIST
    set IDCDATAFLD = IDCDATAFLD
    set DATA = DATA
    set IDDOMANDE = IDDOMANDE
  from 
    CDATAHISTTEXTVALUES // master table
  where
    IDCDATAHISTLIST = IDCDATAHISTLIST
  result.addAll(textVal, ...)
   
  // clean the collection and add all the data found
  CDHistoryValue.clear()
  CDHistoryValue.addAll(result, ...)
  CDHistoryValue.loaded = true
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event CdataSnapshot.OnInit()
{
  this.IDCDATAHISTLIST = Sequence.getNextSequence(TABN_ID_COMMON_CD_HIST_LIST, ...)
}


// ──────────────────────────────────

// **************************************************************************************
// Event raised to the document during the initial steps of the document saving procedure
// **************************************************************************************
event throws exception CdataSnapshot.BeforeSave(
  inout boolean Skip         // A boolean output parameter. If set to True, tells the framework not to perform operations on the database, because they will be managed independently.
  inout boolean Cancel       // A boolean output parameter. If set to True, tells the framework to cancel the save procedure due to errors that occurred. Since the entire document is saved in the same transa...
  int:beforeSavePhases Phase // An integer from 0 to 3 that specifies the progress status of the save procedure as detailed at the beginning of the article.
)
{
  if (deleted)
  {
    for each CDHistoryValue cdhv in CDHistoryValue
    {
      cdhv.deleted = true
      cdhv.saveToDB(...)
    }
  }
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public void CDHistoryValue.setValue(
  string value // 
)
{
   
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public static CDHistoryValue CDHistoryValue.GetInstance(
  MainModuleDatoPersonalizzatoInfo cfi // 
  int fkHistList                       // 
)
{
   
  CDHistoryValue cdhv = null
   
  switch (cfi.Type)
  {
    case IntegerCDataType:
      cdhv = CDHistoryIntegerValue.getConcreteInstance(cfi, fkHistList)
    break
    case BooleanCDataType:
      cdhv = CDHistoryBooleanValue.getConcreteInstance(cfi, fkHistList)
    break
    case FloatCDataType:
      cdhv = CDHistoryFloatValue.getConcreteInstance(cfi, fkHistList)
    break
    case ComboCDataType:
      cdhv = CDHistoryComboValue.getConcreteInstance(cfi, fkHistList)
    break
    case DateCDataType:
      cdhv = CDHistoryDateValue.getConcreteInstance(cfi, fkHistList)
    break
    case MemoCDataType:
      cdhv = CDHistoryMemoValue.getConcreteInstance(cfi, fkHistList)
    break
    case TextCDataType:
      cdhv = CDHistoryTextValue.getConcreteInstance(cfi, fkHistList)
    break
    case CalcFloatCDataType:
      cdhv = CDHistoryFloatValue.getConcreteInstance(cfi, fkHistList)
    break
    case AltraAnagraficaCDataType:
      cdhv = CDHistoryModuleValue.getConcreteInstance(cfi, fkHistList)
    break
    case PersonaleCDataType:
      cdhv = CDHistoryModuleValue.getConcreteInstance(cfi, fkHistList)
    break
    case ProgettoCDataType:
      cdhv = CDHistoryModuleValue.getConcreteInstance(cfi, fkHistList)
    break
    case PrivatoCDataType:
      cdhv = CDHistoryModuleValue.getConcreteInstance(cfi, fkHistList)
    break
    case EventoCDataType:
      cdhv = CDHistoryModuleValue.getConcreteInstance(cfi, fkHistList)
    break
    case FunzioneCDataType:
      cdhv = CDHistoryModuleValue.getConcreteInstance(cfi, fkHistList)
    break
    case DocumentoCDataType:
      cdhv = CDHistoryModuleValue.getConcreteInstance(cfi, fkHistList)
    break
    case ArticoloCDataType:
      cdhv = CDHistoryModuleValue.getConcreteInstance(cfi, fkHistList)
    break
    case ContattoCDataType:
      cdhv = CDHistoryModuleValue.getConcreteInstance(cfi, fkHistList)
    break
    case CliForCDataType:
      cdhv = CDHistoryModuleValue.getConcreteInstance(cfi, fkHistList)
    break
    default:
      cdhv = new()
    break
  }
   
  return cdhv
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public static CDHistoryValue CDHistoryValue.getConcreteInstance()
{
  return null
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public static CDHistoryValue CDHistoryBooleanValue.getConcreteInstance(
  MainModuleDatoPersonalizzatoInfo cfi // 
  int fkHist                           // 
)
{
   
  CDHistoryBooleanValue cdhiv = new()
   
  cdhiv.IDCDATAFLD = cfi.IDCDATAFLD
  cdhiv.IDCDATAHISTLIST = fkHist
   
  try 
  {
    cdhiv.loadFromDB(...)
  }
  catch 
  {
    cdhiv.init()
  }
   
  return cdhiv
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public void CDHistoryBooleanValue.setValue(
  string value // 
)
{
  if (value == toString(true))
  {
    DATA = Yes
  }
  else 
  {
    DATA = No
  }
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public static CDHistoryValue CDHistoryIntegerValue.getConcreteInstance(
  MainModuleDatoPersonalizzatoInfo cfi // 
  int fkHist                           // 
)
{
   
  CDHistoryIntegerValue cdhiv = new()
   
  cdhiv.IDCDATAFLD = cfi.IDCDATAFLD
  cdhiv.IDCDATAHISTLIST = fkHist
   
  try 
  {
    cdhiv.loadFromDB(...)
  }
  catch 
  {
    cdhiv.init()
  }
   
  return cdhiv
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public void CDHistoryIntegerValue.setValue(
  string value // 
)
{
  DATA = toInteger(value)
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public void CDHistoryFloatValue.setValue(
  string value // 
)
{
  DATA = toFloat(value)
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public static CDHistoryValue CDHistoryFloatValue.getConcreteInstance(
  MainModuleDatoPersonalizzatoInfo cfi // 
  int fkHist                           // 
)
{
   
  CDHistoryFloatValue cdhiv = new()
   
  cdhiv.IDCDATAFLD = cfi.IDCDATAFLD
  cdhiv.IDCDATAHISTLIST = fkHist
   
  try 
  {
    cdhiv.loadFromDB(...)
  }
  catch 
  {
    cdhiv.init()
  }
   
  return cdhiv
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public static CDHistoryValue CDHistoryComboValue.getConcreteInstance(
  MainModuleDatoPersonalizzatoInfo cfi // 
  int fkHist                           // 
)
{
   
  CDHistoryComboValue cdhiv = new()
   
  cdhiv.IDCDATAFLD = cfi.IDCDATAFLD
  cdhiv.IDCDATAHISTLIST = fkHist
   
  try 
  {
    cdhiv.loadFromDB(...)
  }
  catch 
  {
    cdhiv.init()
  }
   
  return cdhiv
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public void CDHistoryComboValue.setValue(
  string value // 
)
{
  DATA = value
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public void CDHistoryDateValue.setValue(
  string value // 
)
{
  string dateFormatString = UIParameters.getDateFormat()
  string formatted = format(value, dateFormatString, ...)
  int dd = toInteger(left(formatted, 2))
  int yyyy = toInteger(right(formatted, 4))
  int mm = toInteger(left(right(formatted, 7), 2))
   
  DATA = toDate(yyyy, mm, dd)
}


// ──────────────────────────────────



// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public static CDHistoryValue CDHistoryMemoValue.getConcreteInstance(
  MainModuleDatoPersonalizzatoInfo cfi // 
  int fkHist                           // 
)
{
   
  CDHistoryMemoValue cdhiv = new()
   
  cdhiv.IDCDATAFLD = cfi.IDCDATAFLD
  cdhiv.IDCDATAHISTLIST = fkHist
   
  try 
  {
    cdhiv.loadFromDB(...)
  }
  catch 
  {
    cdhiv.init()
  }
   
  return cdhiv
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public void CDHistoryMemoValue.setValue(
  string value // 
)
{
  DATA = value
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public static CDHistoryValue CDHistoryTextValue.getConcreteInstance(
  MainModuleDatoPersonalizzatoInfo cfi // 
  int fkHist                           // 
)
{
   
  CDHistoryTextValue cdhiv = new()
   
  cdhiv.IDCDATAFLD = cfi.IDCDATAFLD
  cdhiv.IDCDATAHISTLIST = fkHist
   
  try 
  {
    cdhiv.loadFromDB(...)
  }
  catch 
  {
    cdhiv.init()
  }
   
  return cdhiv
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public void CDHistoryTextValue.setValue(
  string value // 
)
{
  DATA = value
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public static CDHistoryValue CDHistoryModuleValue.getConcreteInstance(
  MainModuleDatoPersonalizzatoInfo cfi // 
  int fkHist                           // 
)
{
   
  CDHistoryModuleValue cdhiv = new()
   
  cdhiv.IDCDATAFLD = cfi.IDCDATAFLD
  cdhiv.IDCDATAHISTLIST = fkHist
   
  try 
  {
    cdhiv.loadFromDB(...)
  }
  catch 
  {
    cdhiv.init()
  }
   
  return cdhiv
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public void CDHistoryModuleValue.setValue(
  string value // 
)
{
  DATA = toInteger(value)
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public void RefCDSnapshot.setValue(
  DatoPersonalizzato rDatoPers // 
)
{
  string value = ""
  RiferimentoDatoPersonalizzatoInfo rdi = new()
  RefCDHistoryValue refCDataHistoryValue = new()
   
   
  value = rDatoPers.getValueAsString()
  if (nullValue(value, "") != "")
  {
    rdi = cast(rDatoPers.getCustomDataInfo())
     
    // catch the correct type of RefCDHistoryValue
    refCDataHistoryValue = RefCDHistoryValue.GetInstance(rdi, IDREFCDATAHISTLIST)
     
    refCDataHistoryValue.setValue(value)
     
    RefCDHistoryValue.add(refCDataHistoryValue)
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void RefCDSnapshot.loadAllValues()
{
  IDCollection coll of RefCDHistoryValue = RefCDHistoryValue.computeAllValuesForRefCDSnapshot(this)
  RefCDHistoryValue = coll
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static RefCDSnapshot RefCDSnapshot.create(
  Riferimento riferimento // 
  Utente utente           // 
)
{
  RefCDSnapshot refCDSnapshot = new()
  refCDSnapshot.init()
  //  
  // IDEVAREFERENCES is the ID of the Reference
  refCDSnapshot.IDEVAREFERENCES = riferimento.IDRiferimento
  refCDSnapshot.Timestamp = now()
   
  if (utente)
  {
    refCDSnapshot.IDUTENTE = utente.IDUTENTE
  }
   
  // if parent instance is ModelloEvento we set Y otherwise no
  boolean isModelloEventoParentInstance = ModelloEvento.isMyInstance(riferimento.parent)
  refCDSnapshot.ISMODELLO = if(isModelloEventoParentInstance, Yes, No)
   
  return refCDSnapshot
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event RefCDSnapshot.OnInit()
{
  IDREFCDATAHISTLIST = Sequence.getNextSequence(TABN_ID_REF_CD_HIST_LIST, ...)
}


// ──────────────────────────────────

// **************************************************************************************
// Event raised to the document during the initial steps of the document saving procedure
// **************************************************************************************
event throws exception RefCDSnapshot.BeforeSave(
  inout boolean Skip         // A boolean output parameter. If set to True, tells the framework not to perform operations on the database, because they will be managed independently.
  inout boolean Cancel       // A boolean output parameter. If set to True, tells the framework to cancel the save procedure due to errors that occurred. Since the entire document is saved in the same transa...
  int:beforeSavePhases Phase // An integer from 0 to 3 that specifies the progress status of the save procedure as detailed at the beginning of the article.
)
{
  if (deleted and Phase == PreSave)
  {
    for each RefCDHistoryValue rcdhv in RefCDHistoryValue
    {
      rcdhv.deleted = true
      rcdhv.saveToDB(...)
    }
  }
}


// ──────────────────────────────────

// ************************************************************
// Event raised by the document after loading from the database
// ************************************************************
event RefCDSnapshot.AfterLoad(
  boolean AlreadyLoaded     // A boolean parameter that specifies whether the document was previously loaded.
  IDArray LoadedCollections // IDArray type parameter that contains the collections loaded by the framework. If the value of the Already Loaded parameter is False, the Loaded Collections parameter is NULL.
)
{
  if (!(AlreadyLoaded))
  {
    if (!(RefCDHistoryValue.loaded))
    {
      this.loadAllValues()
    }
  }
   
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public void RefCDHistoryValue.setValue(
  string value // 
)
{
  // overwritten
   
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public static RefCDHistoryValue RefCDHistoryValue.GetConcreteInstance()
{
  return null
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static IDCollection RefCDHistoryValue.getAllSnapshotValues(
  RefCDSnapshot refCDSnapshot // 
)
{
  QappCore.DTTLogMessage("ABSTRACT ERROR", ..., DTTError)
  return null
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static IDCollection RefCDHistoryValue.computeAllValuesForRefCDSnapshot(
  RefCDSnapshot refCDSnapshot // Write a comment for this parameter or press backspace to delete this comment
)
{
  IDCollection allValues of RefCDHistoryValue = new()
   
  IDCollection coll of RefCDHistoryValue = RefCDHistoryBooleanValue.getAllSnapshotValues(refCDSnapshot)
  IDCollection coll1 of RefCDHistoryValue = RefCDHistoryComboValue.getAllSnapshotValues(refCDSnapshot)
  IDCollection coll2 of RefCDHistoryValue = RefCDHistoryDateValue.getAllSnapshotValues(refCDSnapshot)
  IDCollection coll3 of RefCDHistoryValue = RefCDHistoryFloatValue.getAllSnapshotValues(refCDSnapshot)
  IDCollection coll4 of RefCDHistoryValue = RefCDHistoryIntegerValue.getAllSnapshotValues(refCDSnapshot)
  IDCollection coll5 of RefCDHistoryValue = RefCDHistoryMemoValue.getAllSnapshotValues(refCDSnapshot)
  IDCollection coll6 of RefCDHistoryValue = RefCDHistoryTextValue.getAllSnapshotValues(refCDSnapshot)
   
  allValues.addAll(coll, true)
  allValues.addAll(coll1, true)
  allValues.addAll(coll2, true)
  allValues.addAll(coll3, true)
  allValues.addAll(coll4, true)
  allValues.addAll(coll5, true)
  allValues.addAll(coll6, true)
   
  refCDSnapshot.RefCDHistoryValue = allValues
   
  return allValues
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public static RefCDHistoryValue RefCDHistoryValue.GetInstance(
  RiferimentoDatoPersonalizzatoInfo rdi // needed for know the datatype

  int fkRefHistList                     // fk of the Reference CData History List
)
{
  RefCDHistoryValue refCDHValue = null
   
  switch (rdi.Datatype)
  {
    case IntegerCDataType:
      refCDHValue = RefCDHistoryIntegerValue.GetConcreteInstance(rdi, fkRefHistList)
    break
    case BooleanCDataType:
      refCDHValue = RefCDHistoryBooleanValue.GetConcreteInstance(rdi, fkRefHistList)
    break
    case FloatCDataType:
      refCDHValue = RefCDHistoryFloatValue.GetConcreteInstance(rdi, fkRefHistList)
    break
    case ComboCDataType:
      refCDHValue = RefCDHistoryComboValue.GetConcreteInstance(rdi, fkRefHistList)
    break
    case DateCDataType:
      refCDHValue = RefCDHistoryDateValue.GetConcreteInstance(rdi, fkRefHistList)
    break
    case MemoCDataType:
      refCDHValue = RefCDHistoryMemoValue.GetConcreteInstance(rdi, fkRefHistList)
    break
    case TextCDataType:
      refCDHValue = RefCDHistoryTextValue.GetConcreteInstance(rdi, fkRefHistList)
    break
    case CalcFloatCDataType:
      refCDHValue = RefCDHistoryFloatValue.GetConcreteInstance(rdi, fkRefHistList)
    break
    default:
      refCDHValue = new()
    break
  }
   
  return refCDHValue
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public void RefCDHistoryBooleanValue.setValue(
  string value // 
)
{
  if (value == toString(true))
  {
    VALUE = Yes
  }
  else 
  {
    VALUE = No
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static IDCollection RefCDHistoryBooleanValue.getAllSnapshotValues(
  RefCDSnapshot refCDSnapshot // 
)
{
  IDCollection allRefHistoryBooleanValues of RefCDHistoryBooleanValue = new()
  select into collection (allRefHistoryBooleanValues)
  from 
    RefCDHistoryBooleanValue // master table
  where
    IDREFCDATAHISTLIST == refCDSnapshot.IDREFCDATAHISTLIST
  return allRefHistoryBooleanValues
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public void RefCDHistoryComboValue.setValue(
  string value // 
)
{
  string valueToBeStored = value
  VALUE = valueToBeStored
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public static RefCDHistoryValue RefCDHistoryComboValue.GetConcreteInstance(
  RiferimentoDatoPersonalizzatoInfo rci // 
  int fkRefCDHystoryList                // 
)
{
  RefCDHistoryComboValue refCDCV = new()
  refCDCV.IDREFCDATAHISTLIST = fkRefCDHystoryList
  refCDCV.IDEVAREFCDATA = rci.ID
   
  try 
  {
    refCDCV.loadFromDB(...)
  }
  catch 
  {
    refCDCV.init()
  }
  return refCDCV
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static IDCollection RefCDHistoryComboValue.getAllSnapshotValues(
  RefCDSnapshot refCDSnapshot // 
)
{
  IDCollection allRefHistoryComboValues of RefCDHistoryComboValue = new()
  select into collection (allRefHistoryComboValues)
  from 
    RefCDHistoryComboValue // master table
  where
    IDREFCDATAHISTLIST == refCDSnapshot.IDREFCDATAHISTLIST
  return allRefHistoryComboValues
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public void RefCDHistoryDateValue.setValue(
  string value // 
)
{
  string dateFormatString = UIParameters.getDateFormat()
  string format = format(value, dateFormatString, ...)
  int dd = toInteger(left(format, 2))
  int mm = toInteger(left(right(format, 7), 2))
  int yyyy = toInteger(right(format, 4))
  date dateInputvalue = toDate(yyyy, mm, dd)
  VALUE = dateInputvalue
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public static RefCDHistoryValue RefCDHistoryDateValue.GetConcreteInstance(
  RiferimentoDatoPersonalizzatoInfo rci // 
  int fkRefCDHystoryList                // 
)
{
  RefCDHistoryDateValue refCDDV = new()
  refCDDV.IDREFCDATAHISTLIST = fkRefCDHystoryList
  refCDDV.IDEVAREFCDATA = rci.ID
   
  try 
  {
    refCDDV.loadFromDB(...)
  }
  catch 
  {
    refCDDV.init()
  }
  return refCDDV
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static IDCollection RefCDHistoryDateValue.getAllSnapshotValues(
  RefCDSnapshot refCDSnapshot // 
)
{
  IDCollection allRefHistoryDateValues of RefCDHistoryDateValue = new()
  select into collection (allRefHistoryDateValues)
  from 
    RefCDHistoryDateValue // master table
  where
    IDREFCDATAHISTLIST == refCDSnapshot.IDREFCDATAHISTLIST
  return allRefHistoryDateValues
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public void RefCDHistoryFloatValue.setValue(
  string value // 
)
{
  string valueToBeStored = value
  float floatToBeStored = toFloat(valueToBeStored)
  VALUE = floatToBeStored
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public static RefCDHistoryValue RefCDHistoryFloatValue.GetConcreteInstance(
  RiferimentoDatoPersonalizzatoInfo rci // 
  int fkRefCDHystoryList                // 
)
{
  RefCDHistoryFloatValue refCDFV = new()
  refCDFV.IDREFCDATAHISTLIST = fkRefCDHystoryList
  refCDFV.IDEVAREFCDATA = rci.ID
   
  try 
  {
    refCDFV.loadFromDB(...)
  }
  catch 
  {
    refCDFV.init()
  }
  return refCDFV
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static IDCollection RefCDHistoryFloatValue.getAllSnapshotValues(
  RefCDSnapshot refCDSnapshot // 
)
{
  IDCollection allRefHistoryFloatValues of RefCDHistoryFloatValue = new()
  select into collection (allRefHistoryFloatValues)
  from 
    RefCDHistoryFloatValue // master table
  where
    IDREFCDATAHISTLIST == refCDSnapshot.IDREFCDATAHISTLIST
  return allRefHistoryFloatValues
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public void RefCDHistoryIntegerValue.setValue(
  string value // 
)
{
  int valueToBeStored = toInteger(value)
  VALUE = valueToBeStored
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public static RefCDHistoryValue RefCDHistoryIntegerValue.GetConcreteInstance(
  RiferimentoDatoPersonalizzatoInfo rci // 
  int fkRefCDHystoryList                // 
)
{
  RefCDHistoryIntegerValue refCDIV = new()
  refCDIV.IDREFCDATAHISTLIST = fkRefCDHystoryList
  refCDIV.IDEVAREFCDATA = rci.ID
   
  try 
  {
    refCDIV.loadFromDB(...)
  }
  catch 
  {
    refCDIV.init()
  }
  return refCDIV
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static IDCollection RefCDHistoryIntegerValue.getAllSnapshotValues(
  RefCDSnapshot refCDSnapshot // 
)
{
  IDCollection allRefHistoryIntegerValues of RefCDHistoryIntegerValue = new()
  select into collection (allRefHistoryIntegerValues)
  from 
    RefCDHistoryIntegerValue // master table
  where
    IDREFCDATAHISTLIST == refCDSnapshot.IDREFCDATAHISTLIST
  return allRefHistoryIntegerValues
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public void RefCDHistoryMemoValue.setValue(
  string value // 
)
{
  string valueToBeStored = value
  VALUE = valueToBeStored
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public static RefCDHistoryValue RefCDHistoryMemoValue.GetConcreteInstance(
  RiferimentoDatoPersonalizzatoInfo rci // 
  int fkRefCDHystoryList                // 
)
{
  RefCDHistoryMemoValue refCDMV = new()
  refCDMV.IDREFCDATAHISTLIST = fkRefCDHystoryList
  refCDMV.IDEVAREFCDATA = rci.ID
   
  try 
  {
    refCDMV.loadFromDB(...)
  }
  catch 
  {
    refCDMV.init()
  }
  return refCDMV
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static IDCollection RefCDHistoryMemoValue.getAllSnapshotValues(
  RefCDSnapshot refCDSnapshot // 
)
{
  IDCollection allRefHistoryMemoValues of RefCDHistoryMemoValue = new()
  select into collection (allRefHistoryMemoValues)
  from 
    RefCDHistoryMemoValue // master table
  where
    IDREFCDATAHISTLIST == refCDSnapshot.IDREFCDATAHISTLIST
  return allRefHistoryMemoValues
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public void RefCDHistoryTextValue.setValue(
  string value // 
)
{
  string valueToBeStored = value
  VALUE = valueToBeStored
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public static RefCDHistoryValue RefCDHistoryTextValue.GetConcreteInstance(
  RiferimentoDatoPersonalizzatoInfo rci // 
  int fkRefCDHystoryList                // 
)
{
  RefCDHistoryTextValue refCDTV = new()
  refCDTV.IDREFCDATAHISTLIST = fkRefCDHystoryList
  refCDTV.IDEVAREFCDATA = rci.ID
   
  try 
  {
    refCDTV.loadFromDB(...)
  }
  catch 
  {
    refCDTV.init()
  }
  return refCDTV
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static IDCollection RefCDHistoryTextValue.getAllSnapshotValues(
  RefCDSnapshot refCDSnapshot // 
)
{
  IDCollection allRefHistoryTextValues of RefCDHistoryTextValue = new()
  select into collection (allRefHistoryTextValues)
  from 
    RefCDHistoryTextValue // master table
  where
    IDREFCDATAHISTLIST == refCDSnapshot.IDREFCDATAHISTLIST
  return allRefHistoryTextValues
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event CdataSection.OnInit()
{
  IDCDATASEC = Sequence.getNextSequence(TABN_ID_COMMON_CD_SEC, ...)
  Active = Yes
  ISCOMMON = No
  HIDDEN = No
  Sequence = 1
}


// ──────────────────────────────────

// **************************************************************************************
// Event raised to the document during the initial steps of the document saving procedure
// **************************************************************************************
event throws exception CdataSection.BeforeSave(
  inout boolean Skip         // A boolean output parameter. If set to True, tells the framework not to perform operations on the database, because they will be managed independently.
  inout boolean Cancel       // A boolean output parameter. If set to True, tells the framework to cancel the save procedure due to errors that occurred. Since the entire document is saved in the same transa...
  int:beforeSavePhases Phase // An integer from 0 to 3 that specifies the progress status of the save procedure as detailed at the beginning of the article.
)
{
  if (Phase == PreSave)
  {
    this.handleSavingOfModuleTypeSelection()
    this.handleSavingUtentiPermessi()
     
    if (deleted == true)
    {
      for each MainModuleDatoPersonalizzatoInfo cfi in CDATAFIELDSINFO
      {
         cfi.deleted = true
         cfi.saveToDB(0, ...)
      }
       
       
      for each CDATAMODULETYPE cdatamoduletype in CDATAMODULETYPE
      {
         cdatamoduletype.deleted = true
         cdatamoduletype.saveToDB(...)
      }
       
      for each CDATASECTIONSPERMESSI cdatasectionspermessi in CDATASECTIONSPERMESSI
      {
         cdatasectionspermessi.deleted = true
         cdatasectionspermessi.saveToDB(...)
      }
    }
  }
   
}


// ──────────────────────────────────



// ──────────────────────────────────

// ****************************************************
// Raised to the document during duplication operations
// ****************************************************
event CdataSection.OnDuplicate(
  IDDocument SourceDocument // This parameter contains the original document when duplicating the document header. Otherwise, its value is Null Object. This way, it is possible to distinguish the two cases, ...
)
{
  CdataSection original = (CdataSection)SourceDocument
  if (original and original.IDCDATASEC > 0)
  {
    CdataSection newCDSection = original.duplicateSection()
    IDCDATASEC = newCDSection.IDCDATASEC
    Name = newCDSection.Name
    Description = newCDSection.Description
    Sequence = newCDSection.Sequence
    Active = newCDSection.Active
    KordApp = newCDSection.KordApp
    ISCOMMON = newCDSection.ISCOMMON
    DEPRECATEDIDTEMPLATE = newCDSection.DEPRECATEDIDTEMPLATE
    DEPRECATEDTEMPLATE = newCDSection.DEPRECATEDTEMPLATE
    DEPRECATEDIDPARENTTEMPLATEREMOTE = newCDSection.DEPRECATEDIDPARENTTEMPLATEREMOTE
    HIDDEN = newCDSection.HIDDEN
    CDATAFIELDSINFO = newCDSection.CDATAFIELDSINFO
    CDATAMODULETYPE = newCDSection.CDATAMODULETYPE
    CDATAMODULETYPETransientForPanel = newCDSection.CDATAMODULETYPETransientForPanel
    CDATAFIELDSINFO.clear()
    for each MainModuleDatoPersonalizzatoInfo cfi in original.CDATAFIELDSINFO
    {
      MainModuleDatoPersonalizzatoInfo duplicate = (MainModuleDatoPersonalizzatoInfo)cfi.duplicate(...)
      duplicate.Caption = cfi.Caption
      CDATAFIELDSINFO.add(duplicate)
    }
  }
}


// ──────────────────────────────────

// ************************************************************
// Event raised to the document during the validation procedure
// ************************************************************
event CdataSection.OnValidate(
  int:validateReasons Reason // An integer specifying the reason the document is being validated. The most common values are 0 to indicate complete validation before saving, or 1 to indicate a quick validati...
  inout boolean Error        // A boolean output parameter. If set to True, specifies an error condition and stops the validation immediately.
  inout boolean Skip         // A boolean output parameter. If set to True, does not check the rules for automatic validation of the document (see notes).
)
{
  if (isNull(Name) or Name == "")
  {
    this.setPropertyError("Il campo non può essere vuoto", Name)
    Error = true
  }
  if (isNull(Sequence) or toString(Sequence) == "")
  {
    this.setPropertyError("Il campo non può essere vuoto", Sequence)
    Error = true
  }
   
}


// ──────────────────────────────────

// ******************************************************************************************************************
// this method allows to set a section for  "consider it for loading cdatafieldsinfo" but "do not render it"
// this is used for calc data loading even if user cannot see them and based on toberendered they are rendered or not
// ******************************************************************************************************************
public void CdataSection.markAsSkipRendering()
{
  SkipRendering = true
}


// ──────────────────────────────────

// ********************************************************
// if true it means the UI must avoid rendering the section
// ********************************************************
public boolean CdataSection.shouldSkipRendering()
{
  return SkipRendering
}


// ──────────────────────────────────

// *****************************
// Creates a valid Cdata Section
// *****************************
public static CdataSection CdataSection.create(
  int:kordapp kordApp              // 
  string name                      // 
  optional string description = "" // 
  optional string common = "N"     // 
)
{
  CdataSection cs = new()
  cs.init()
  cs.Name = name
  cs.Description = description
  cs.KordApp = kordApp
  cs.ISCOMMON = common
  cs.populateAllModuleTypesSelectionPerSection()
  return cs
   
}


// ──────────────────────────────────

// *****************************
// Creates a valid Cdata Section
// *****************************
public static CdataSection CdataSection.get(
  int idCDSection // 
)
{
  CdataSection cs = new()
  cs.IDCDATASEC = idCDSection
  try 
  {
    cs.loadFromDB(...)
  }
  catch 
  {
    cs = null
  }
   
  return cs
   
}


// ──────────────────────────────────

// *********************************************************************
// return collection of cdata sections based on passed kordApp parameter
// *********************************************************************
public static IDCollection CdataSection.getCollection(
  int:kordapp kordApp // 
)
{
  IDCollection moduleWiseCdataSections of CdataSection = new()
   
  select into collection (moduleWiseCdataSections)
  from 
    CdataSection // master table
  where
    KordApp == kordApp
   
  for each CdataSection cs in moduleWiseCdataSections
  {
    // load Cdata Types
    cs.loadCdataTypes()
    cs.loadCdataSectionPermessi()
     
    // load Cdata Fields
    if (!(cs.CDATAFIELDSINFO.loaded))
      cs.loadCollectionFromDB(cs.CDATAFIELDSINFO, ...)
     
    cs.setOriginal()
  }
   
  return moduleWiseCdataSections
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public CdataSection CdataSection.duplicateSection()
{
  string newSectionName = Name + " (copia)"
  CdataSection newSection = this.create(KordApp, newSectionName, newSectionName, ISCOMMON)
  newSection.Sequence = Sequence
  for each MainModuleDatoPersonalizzatoInfo sourceCFI in CDATAFIELDSINFO
  {
    MainModuleDatoPersonalizzatoInfo newCFI = MainModuleDatoPersonalizzatoInfo.create(newSection, sourceCFI.Type, sourceCFI.Caption, sourceCFI.Mandatory)
    newCFI.Sequence = sourceCFI.Sequence
    newSection.CDATAFIELDSINFO.add(newCFI)
  }
  for each CDATAMODULETYPE sourceModuleType in CDATAMODULETYPETransientForPanel
  {
    for each CDATAMODULETYPE destination in newSection.CDATAMODULETYPETransientForPanel
    {
      if (sourceModuleType.IDTIPO == destination.IDTIPO)
      {
         destination.Selected = sourceModuleType.Selected
      }
    }
  }
   
  return newSection
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void CdataSection.populateAllModuleTypesSelectionPerSection()
{
  for each row (readonly)
  {
    select
      IDTIPO = IDTIPO
    from 
      VMODULETIPI // master table
    where
      KORDAPP == KordApp
     
    CDATAMODULETYPE cdatamoduletype = new()
    cdatamoduletype.IDCDATASEC = IDCDATASEC
    cdatamoduletype.IDTIPO = IDTIPO
    for each CDATAMODULETYPE cdmoduletype in CDATAMODULETYPE
    {
      if (cdmoduletype.IDTIPO == IDTIPO)
      {
         cdatamoduletype.Selected = true
         break 
      }
    }
     
    CDATAMODULETYPETransientForPanel.add(cdatamoduletype)
  }
  CDATAMODULETYPETransientForPanel.parent = this
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public CDATAMODULETYPE CdataSection.findModuleTypeInCollection(
  int idCdataSection // 
  int idTipo         // 
)
{
  CDATAMODULETYPE cdatamoduletype = null
  for each CDATAMODULETYPE cdmoduletype in CDATAMODULETYPE
  {
    if ((cdmoduletype.IDCDATASEC == idCdataSection) and (cdmoduletype.IDTIPO == idTipo))
    {
      cdatamoduletype = cdmoduletype
      break 
    }
  }
  return cdatamoduletype
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void CdataSection.handleSavingOfModuleTypeSelection()
{
  // if cdataModuleTypeTransient Collection is modified at panel side we populate actual non transient collection and saving is automatically handled by INDE
  if (CDATAMODULETYPETransientForPanel.isModified())
  {
    for each CDATAMODULETYPE cdatamoduletype in CDATAMODULETYPETransientForPanel
    {
      if (cdatamoduletype.isModified(...))
      {
         if (cdatamoduletype.Selected)
         {
           CDATAMODULETYPE cdmoduletype = new()
           cdmoduletype.init()
           cdmoduletype.IDCDATASEC = cdatamoduletype.IDCDATASEC
           cdmoduletype.IDTIPO = cdatamoduletype.IDTIPO
           CDATAMODULETYPE.add(cdmoduletype)
         }
         else 
         {
           CDATAMODULETYPE cdatamoduletypeInCollection = this.findModuleTypeInCollection(cdatamoduletype.IDCDATASEC, cdatamoduletype.IDTIPO)
           if (cdatamoduletypeInCollection)
           {
             cdatamoduletypeInCollection.deleted = true
           }
         }
      }
    }
     
    // force setoriginal so it does not try to save transient collection
    CDATAMODULETYPETransientForPanel.setOriginal()
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void CdataSection.selectAllModuleTypes(
  boolean selection // 
)
{
  for each CDATAMODULETYPE cdmoduletype in CDATAMODULETYPETransientForPanel
  {
    cdmoduletype.Selected = selection
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void CdataSection.loadCdataTypes()
{
  if (!(CDATAMODULETYPE.loaded))
  {
    this.loadCollectionFromDB(CDATAMODULETYPE, ...)
  }
  CDATAMODULETYPETransientForPanel.clear()
  this.populateAllModuleTypesSelectionPerSection()
  this.setOriginal()
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void CdataSection.LoadUtenteWithPermessoPerSection()
{
  IDCollection utenti of Utente = new()
  select into collection (utenti)
  from 
    Utente // master table
  where
    ATTIVO == Yes
   
  for each Utente u in utenti
  {
    u.skipValidation()
    for each CDATASECTIONSPERMESSI cdatasectionspermessi in CDATASECTIONSPERMESSI
    {
      if (cdatasectionspermessi.IDUTENTE == u.IDUTENTE)
      {
         u.Selected = true
      }
    }
    UtentiWithPermesso.add(u)
  }
  UtentiWithPermesso.parent = this
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public CDATASECTIONSPERMESSI CdataSection.findPermessoInCollection(
  int idCdataSection // 
  int idUntente      // 
)
{
  CDATASECTIONSPERMESSI cdataSectionPermessi = null
  for each CDATASECTIONSPERMESSI cdataSecPermessi in CDATASECTIONSPERMESSI
  {
    if ((cdataSecPermessi.IDCDATASEC == idCdataSection) and (cdataSecPermessi.IDUTENTE == idUntente))
    {
      cdataSectionPermessi = cdataSecPermessi
      break 
    }
  }
  return cdataSectionPermessi
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void CdataSection.handleSavingUtentiPermessi()
{
  // if cdataModuleTypeTransient Collection is modified at panel side we populate actual non transient collection and saving is automatically handled by INDE
  if (UtentiWithPermesso.isModified())
  {
    for each Utente u in UtentiWithPermesso
    {
      if (u.isModified(...))
      {
         if (u.Selected)
         {
           CDATASECTIONSPERMESSI cdSectionUtentePermesso = new()
           cdSectionUtentePermesso.init()
           cdSectionUtentePermesso.IDCDATASEC = IDCDATASEC
           cdSectionUtentePermesso.IDUTENTE = u.IDUTENTE
           CDATASECTIONSPERMESSI.add(cdSectionUtentePermesso)
         }
         else 
         {
           CDATASECTIONSPERMESSI cdataSectionPermesso = this.findPermessoInCollection(IDCDATASEC, u.IDUTENTE)
           if (cdataSectionPermesso)
           {
             cdataSectionPermesso.deleted = true
           }
         }
      }
    }
     
    // force setoriginal so it does not try to save transient collection
    UtentiWithPermesso.setOriginal()
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void CdataSection.loadCdataSectionPermessi()
{
  CDATASECTIONSPERMESSI.loaded = false
  this.loadCollectionFromDB(CDATASECTIONSPERMESSI, ...)
  UtentiWithPermesso.clear()
  this.LoadUtenteWithPermessoPerSection()
  this.setOriginal()
}


// ──────────────────────────────────

// ***********************************************************
// Event raised to the document when the transaction is closed
// ***********************************************************
event CDATAMODULETYPE.OnEndTransaction()
{
   
  if (wasModified(Selected))
  {
    if (!(Selected))
    {
      this.makeCDSectionIsCommonToFalse()
    }
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void CDATAMODULETYPE.makeCDSectionIsCommonToFalse()
{
  if (parent)
  {
    if (CdataSection.isMyInstance(parent))
    {
      CdataSection cs = parent
      if (cs.ISCOMMON == Yes)
      {
         cs.ISCOMMON = No
      }
    }
  }
}


// ──────────────────────────────────

// ***********************************************************************************************************************
// centalized logic to show errors in red also for subforms (checkgroup and depends on) of cdata subforms
// at the moment of writing it has been done in this method because it needs to be shared between Qualibus and QualibusWeb
// ***********************************************************************************************************************
public static void UICustomDataCentralizedTools.ShowErrorsInCustomDataPanel(
  IDPanel customDataidPanel // 
  MainModule mainModule     // 
)
{
  // if inihibit is applied we do not want to refresh errors in panel
  if (QappCore.InhibitCdataRenderingHelperQuickValidation)
    return 
   
  MainModuleCdataRenderingHelper crh = cast(customDataidPanel.document)
   
   
  // Subform's panel may not have a Document yet if the subform was lazy-loaded 
  // and never received LOAD. Caller (handleErrorDisplay) should ensure LOAD is
  // sent before invoking SHOWERRORS, but we guard here too to fail safe.
  if (crh == null)
  {
    QappCore.DTTLogMessage("ShowErrorsInCustomDataPanel: customDataIdPanel.document is null — subform likely not LOADed", ..., DTTWarning)
    return 
  }
  crh.validate(...)
   
  // validate the mainModule to update errors in the properties so below getPropertyErrorByIndex is updated with the current panel data
  mainModule.validate(...)
   
  // from here to the end: set the proper visual style to the checkgroup subform to display error properly
  IDDocumentStructure idds = crh.getStructure()
   
  // getPropertyCount is base 1 index
  for (int j = 1; j <= idds.getPropertyCount(); j = j + 1)
  {
    IDPanel customData = customDataidPanel
    string keyFieldValue = "VALUE" + toString(j)
    MainModuleDatoPersonalizzatoInfo cfi = (MainModuleDatoPersonalizzatoInfo)crh.CdataMapping.getObject(keyFieldValue)
    if (cfi)
    {
      QappCore.DTTLogMessage(formatMessage("in field |1", cfi.Caption, ...), ...)
      customData.setFieldVisualStyle(j - 1, CustomDataNormalState)
    }
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static boolean CustomDataComboFieldValidation.isValid(
  string textValue // 
)
{
  boolean isValid = textValue != null and textValue != ""
  if (isValid)
  {
    CustomDataComboFieldValidation cdfv = CustomDataComboFieldValidation.create(textValue)
    boolean hasParenthesis = cdfv.hasParenthesis()
    if (hasParenthesis)
    {
      isValid = isValid and cdfv.parenthesisContentIsValid()
    }
    if (isValid)
    {
      boolean contentIsValid = cdfv.isValidContent()
      isValid = isValid and contentIsValid
    }
  }
  return isValid
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static CustomDataComboFieldValidation CustomDataComboFieldValidation.create(
  string valueTextToBeValidated // 
)
{
  CustomDataComboFieldValidation validator = new()
  validator.ComboValue = valueTextToBeValidated
  return validator
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean CustomDataComboFieldValidation.hasParenthesis()
{
  boolean hasParenthesis = false
  hasParenthesis = this.regexHasAtLeastOneMatch(ComboValue, hasParenthesis)
  return hasParenthesis
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean CustomDataComboFieldValidation.regexHasAtLeastOneMatch(
  string inputString // 
  string regex       // 
)
{
  string result = replaceRegEx(inputString, regex, "")
  int originalLenght = length(inputString)
  int afterReplacement = length(result)
  boolean atLeastOneMatchFound = originalLenght > afterReplacement
  return atLeastOneMatchFound
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean CustomDataComboFieldValidation.parenthesisContentIsValid()
{
  string simplifiedCMBValue = replace(ComboValue, " ", "")
  boolean typeInsertedProperly = this.regexHasAtLeastOneMatch(simplifiedCMBValue, TypeValidation)
  boolean contentIsValid = typeInsertedProperly
   
  if (find(lower(simplifiedCMBValue), "columns=", ...) > 0)
  {
    boolean validColumnPart = this.regexHasAtLeastOneMatch(simplifiedCMBValue, columnValidation)
    contentIsValid = contentIsValid and validColumnPart
  }
   
  // dependsOn must be handled considering the ids of the other CDataFields
  if (find(lower(simplifiedCMBValue), "dependson=", ...) > 0)
  {
    boolean validDependsOn = this.regexHasAtLeastOneMatch(simplifiedCMBValue, dependsOnValidation)
    contentIsValid = contentIsValid and validDependsOn
  }
   
  boolean parenthesisContentContainsTheRightValues = this.ParenthesisContentContainsTheRightValues()
  contentIsValid = contentIsValid and parenthesisContentContainsTheRightValues
   
  return contentIsValid
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean CustomDataComboFieldValidation.ParenthesisContentContainsTheRightValues()
{
  boolean allTokensAreCorrect = true
   
  string squareContent = SH.leftUpToDelimiter(ComboValue, "]", ...)
  squareContent = lower(replace(squareContent, "[", ""))
  StringTokenizer st = new()
  st.setString(squareContent, ",", ...)
  while (st.hasNextToken())
  {
    string token = lower(st.nextToken())
    boolean validToken = find(token, "columns", ...) > 0 or find(token, "dependson", ...) > 0 or find(token, "type", ...) > 0
    if (!(validToken))
    {
      allTokensAreCorrect = false
      break 
    }
  }
  return allTokensAreCorrect
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean CustomDataComboFieldValidation.isValidContent()
{
  boolean validContent = true
  string content = SH.rightUpToDelimiter(ComboValue, "]", ...)
  StringTokenizer st = new()
  st.setString(content, ";", ...)
  while (st.hasNextToken())
  {
    string token = st.nextToken()
    if (token == "" or token == null)
    {
      validContent = false
    }
  }
   
  boolean errorAnalysingTypes = false
  string:comboContentTypes comboContentType = this.identifyCMBContentType(errorAnalysingTypes)
  if (!(errorAnalysingTypes))
  {
    switch (comboContentType)
    {
      case simple:
         // Simple combobox validation
         boolean isValidSimpleCombo = this.regexHasAtLeastOneMatch(content, SimpleComboContent)
         validContent = validContent and isValidSimpleCombo
      break
      case nameValue:
         // name values separated with values validation
         boolean isValidComboNameValue = this.regexHasAtLeastOneMatch(content, ComboNameValueContent)
         validContent = validContent and isValidComboNameValue
      break
      case dependsOn:
         // with dependsOnId
         boolean isValidComboDependsOn = this.regexHasAtLeastOneMatch(content, ComboDependsOnContent)
         validContent = validContent and isValidComboDependsOn
      break
      default:
         validContent = false
      break
    }
  }
  else 
  {
    validContent = false
  }
  return validContent
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string CustomDataComboFieldValidation.identifyCMBContentType(
  inout boolean errors // 
)
{
  string:comboContentTypes type = ""
  string content = SH.rightUpToDelimiter(ComboValue, "]", ...)
  StringTokenizer st = new()
  st.setString(content, ";", ...)
  string:comboContentTypes previousType = ""
  while (st.hasNextToken())
  {
    string token = st.nextToken()
    if (find(token, "@", ...) > 0)
    {
      type = dependsOn
    }
    else if (this.regexHasAtLeastOneMatch(token, nameValueValidation))
    {
      type = nameValue
    }
    else 
    {
      type = simple
    }
     
    if (previousType != type and previousType != "")
    {
      errors = true
    }
    previousType = type
  }
  return type
}


// ──────────────────────────────────



// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static CustomDataCalcFloatValidation CustomDataCalcFloatValidation.create(
  string valueTextToBeValidated // 
)
{
  CustomDataCalcFloatValidation validator = new()
  validator.ComboValue = valueTextToBeValidated
  return validator
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean CustomDataCalcFloatValidation.isInteger(
  string value // 
)
{
  int tempValue = toInteger(value)
  if (toString(tempValue) == value)
  {
    return true
  }
  return false
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string CustomDataCalcFloatValidation.validateSemicolonValue()
{
  string errorMessage = ""
   
  if (find(ComboValue, ";", 0) > 0)
  {
    string decimalPart = SH.rightUpToDelimiter(ComboValue, ";", 1)
     
    if (decimalPart != "" and this.isInteger(decimalPart))
    {
      int precision = toInteger(decimalPart)
       
      if (!(precision >= 0 and precision <= 4))
      {
         errorMessage = formatMessage(" Valore dopo il punto e virgola non valido: |1, deve essere un numero intero compreso tra 0 e 4", decimalPart, ...)
      }
       
    }
    else 
    {
      errorMessage = " Il valore dopo il punto e virgola non è un intero"
    }
  }
   
  return errorMessage
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean CustomDataCalcFloatValidation.validateFieldFromDB(
  string fieldID // 
)
{
  int icDataFld = toInteger(fieldID)
  boolean isValid = false
  int found = 0
   
  select into variables (found)
    set found = IDCDATAFLD
  from 
    CDATAFIELDSINFO // master table
  where
    (Type == 2 or Type == 1) and IDCDATAFLD == icDataFld
   
  if (found)
  {
    isValid = true
  }
  return isValid
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string CustomDataCalcFloatValidation.validateFieldsId()
{
  string errorMessages = ""
  string tempValue = ComboValue
   
  // loop through the tempValue as long as it contains "["
  while (find(tempValue, "[", 0) > 0)
  {
     
    string s = SH.leftUpToDelimiter(tempValue, "]", ...)
    //  
    // Extract the field ID from within the square brackets "[...]"
    string fieldID = SH.rightUpToDelimiter(s, "[", ...)
     
    if (fieldID == "")
    {
      string errorEmptyField = "Campo vuoto"
      if (errorMessages != "")
      {
         errorMessages = errorMessages + ", "
      }
      errorMessages = errorMessages + errorEmptyField
    }
     
    // check if the extracted field ID is not an integer
    if (!(this.isInteger(fieldID)))
    {
      string errorInteger = formatMessage("Il campo con ID [|1] non è un numero", fieldID, ...)
      if (errorMessages != "")
      {
         errorMessages = errorMessages + ", "
      }
      errorMessages = errorMessages + errorInteger
    }
    //  
    // check if the field ID is an integer but not a valid field in the database
    if (!(this.validateFieldFromDB(fieldID)) and this.isInteger(fieldID))
    {
      string errorFieldDB = formatMessage("Il campo con ID [|1] non è valido", fieldID, ...)
      if (errorMessages != "")
      {
         errorMessages = errorMessages + ", "
      }
      errorMessages = errorMessages + errorFieldDB
    }
     
    // find the position of the first "]" and move the pointer
    int endPos = find(tempValue, "]", 0) + 1
     
    // if a "]" is found, remove the processed part from tempValue
    if (endPos > 0)
    {
      tempValue = SH.removeLeftChars(tempValue, endPos)
    }
     
    if (SH.rightUpToDelimiter(errorMessages, ",", 1) == ",")
    {
      errorMessages = SH.removeRightChars(errorMessages, 1)
    }
     
    if (find(tempValue, "[", 0) <= 0)
    {
      break 
    }
     
  }
   
  return errorMessages
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string CustomDataCalcFloatValidation.validateAndRemoveNullAsZero()
{
  string nullAsZeroTrueSearchKey = "[NullAsZero=true]"
  string nullAsZeroFalseSearchKey = "[NullAsZero=false]"
   
   
  int posTrue = find(lower(ComboValue), lower(nullAsZeroTrueSearchKey), 0)
  int posFalse = find(lower(ComboValue), lower(nullAsZeroFalseSearchKey), 0)
   
  if (posTrue > 0)
  {
    ComboValue = replace(lower(ComboValue), lower(nullAsZeroTrueSearchKey), "")
    return ""
  }
  else if (posFalse > 0)
  {
    ComboValue = replace(lower(ComboValue), lower(nullAsZeroFalseSearchKey), "")
    return ""
  }
   
  if (find(lower(ComboValue), "[null", 0) > 0)
  {
    return "Formato NullAsZero non valido"
  }
  return ""
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string CustomDataCalcFloatValidation.validateCalcfloat()
{
  string errorMessages = ""
  if (ComboValue == "")
  {
    errorMessages = "Il campo non può essere vuoto"
    return errorMessages
  }
   
  // Step 1 - Verify and remove "nullaszero"
   
  string errorNullAsZero = this.validateAndRemoveNullAsZero()
  if (errorNullAsZero != "")
  {
    errorMessages = errorNullAsZero + ","
  }
   
   
  // Step 2 - validate fields in "[]"
   
  string errorFields = this.validateFieldsId()
   
  if (errorFields != "")
  {
    errorMessages = errorMessages + errorFields + ","
  }
   
   
  // Step 3 - check the value after ";"
   
  string errorSemicolon = this.validateSemicolonValue()
  if (errorSemicolon != "")
  {
    errorMessages = errorMessages + errorSemicolon
  }
   
   
//  if (SH.rightUpToDelimiter(errorMessages, ",", 1) == ",")
//  {
//    errorMessages = SH.removeRightChars(errorMessages, 1)
//  }
   
  int errorMessagesLenght = length(errorMessages)
  if (find(errorMessages, ",", errorMessagesLenght))
  {
    errorMessages = SH.removeRightChars(errorMessages, 1)
  }
  if (find(errorMessages, ", ", errorMessagesLenght))
  {
    errorMessages = SH.removeRightChars(errorMessages, 2)
  }
  return errorMessages
}


// ──────────────────────────────────



// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void RenderingHelper.setOwner(
  IDatoPersonalizzatoOwner owner // 
)
{
  Owner = owner
  this.parent = cast(owner)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public IDDocument RenderingHelper.getOwner()
{
  return cast(Owner)
}


// ──────────────────────────────────

// **********************************************************************************************
// This method search in owner.datipersonalizzati collection for matching datoPersonalizzato Info
// **********************************************************************************************
public DatoPersonalizzato RenderingHelper.getMatchingDatoPersonalizzatoInOwner(
  DatoPersonalizzatoInfo info // 
)
{
  DatoPersonalizzato matchedDatoPersonalizzato = null
  IDCollection coll of DatoPersonalizzato = Owner.getDatoPersonalizzatoCollection()
  for each DatoPersonalizzato dp in coll
  {
    DatoPersonalizzatoInfo dpi = dp.getCustomDataInfo()
    if (info.getFieldID() == dpi.getFieldID())
    {
      matchedDatoPersonalizzato = dp
      break 
    }
  }
  return matchedDatoPersonalizzato
   
}


// ──────────────────────────────────

// ************************************
// get field index of given custom data
// ************************************
public int RenderingHelper.getIndexOfField(
  DatoPersonalizzatoInfo cdInfo // 
)
{
  int foundindex = -1
  string keyFound = ""
  IDArray ida = CdataMapping.getKeys()
  for (int i = 0; i < ida.length(); i = i + 1)
  {
    string currentKey = ida.getValue(i)
    DatoPersonalizzatoInfo cfi = (DatoPersonalizzatoInfo)CdataMapping.getObject(currentKey)
    if (cfi.getFieldID() == cdInfo.getFieldID())
    {
      keyFound = currentKey
      break 
    }
  }
  if (keyFound != "")
  {
    foundindex = toInteger(replace(keyFound, "VALUE", ""))
  }
  return foundindex
}


// ──────────────────────────────────

// *************************************************************************************************
// NOTE: this is never executed for moduleType cdata
// 
// This method update customdata based on given property index of field for following conditions
// 1 . update normal customdata
// 2 . update calc customdata 
// 3 . update combo/Radio/Checkgroup elements based on dependsOn ID
// 
// the only not handled case is when dato personalizzato info is moduleType is true
// (this happens for module type main module cdata)
// for this case the handlign is done in the "after lookup" mechanism that calls setModuleFieldValue
// *************************************************************************************************
public void RenderingHelper.updateDatoPersonalizzatoInOwner(
  int PropertyIndex // 
)
{
   
  // set customdata value for given property index
  string propertyValue = getProperty(PropertyIndex)
   
  QappCore.DTTLogMessage("property Value: " + propertyValue, 11111, DTTInfo)
   
  // if property value is null, that can occur on some initialization, we return because propertyValue in this case is not a json and this method makes sense only of propertyValue is a json, since rendering
  // helper manipulates json only
  if (propertyValue == "")
    return 
   
  DatoPersonalizzatoInfo cfi = (DatoPersonalizzatoInfo)CdataMapping.getObject(formatMessage("VALUE|1", PropertyIndex, ...))
   
  string valueToBeSet = ""
  valueToBeSet = this.extractActualValueAsStringFromMultiTypeFieldJsonValue(cfi.getCustomDataType(), cfi.ComboStyle, propertyValue)
   
  if (Owner)
    Owner.setCustomData(valueToBeSet, cast(cfi))
   
  // notify the UI compeltely in the classes
  DatoPersonalizzato dp = this.getMatchingDatoPersonalizzatoInOwner(cfi)
  if (dp)
    dp.notifyUiResponsibleAboutUpdatedObsevers()
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void RenderingHelper.addCdataFieldInfoToMap(
  int fieldIndex             // 
  DatoPersonalizzatoInfo cfi // 
)
{
  string propertyCode = formatMessage("VALUE|1", fieldIndex, ...)
   
  // assign UIName property of the field prior to adding into CdataMapping map
  // the following method has been made when workin on riferimenti to have common id Document strcture code, the UI name updating shoudl be a common task performed y rendergin helper base classs
  IdDocumentTools.updatePropertyUiNameOnSpecificDocument(this, fieldIndex, cfi.getCaption())
  DevTools.ToBeReviewed("updating UI name should be a task of base rendering helper")
   
  // add object in cdatahelper.CdataMapping so it can be referred at form side
  CdataMapping.setObject(propertyCode, cfi)
   
}


// ──────────────────────────────────

// *****************************************************************************************************************************************************************
// Sets a custom data field value in the rendering context.
// Maps the field for form access, updates combo mappings if needed,
// and assigns the value via module or standard property depending on field type.
// 
// This is tipically called after the panel field is created, so basically this method sets the value of the property that the just created panel field will display
// for module type custom data a special arrangement is done, for other cases a property is directly set
// 
// *****************************************************************************************************************************************************************
public void RenderingHelper.updateRenderingHelperProperty(
  DatoPersonalizzatoInfo cfi // Write a comment for this parameter or press backspace to delete this comment
  int fieldIndex             // Write a comment for this parameter or press backspace to delete this comment
  string currentValue        // 
)
{
  if (cfi)
  {
    int panelIndex = this.getFieldPanelIndex(fieldIndex)
     
    string caption = cfi.getCaption()
    if (cfi.isMandatory())
    {
      caption = caption + " *"
    }
     
    if (this.Panel)
    {
      Panel.setFieldCaption(panelIndex, caption)
      Panel.setFieldVisible(panelIndex, true)
      Panel.setFieldPage(panelIndex, -1, CurrentGroupId)
       
      int fieldHeight = this.ComputeFieldHeight(cfi)
       
      if (this.LayoutType == Form)
      {
         int forceWidth = 2000
         Panel.setFieldRect(panelIndex, Form, 0, 0, forceWidth, fieldHeight, Stretch, None)
      }
      else 
      {
         int fieldWidth = this.ComputeFieldWidth(cfi, true, MaxFieldsLeftPosition)
          
         CurrentLeftPosition = CurrentLeftPosition + HorizontalGap + fieldWidth
      }
       
      Panel.setFieldHeaderSize(panelIndex, Form, 20)
      Panel.setFieldHeaderSize(panelIndex, List, 20)
       
      Panel.setFieldClassName(panelIndex, "extraVerticalMargin")
      Panel.setFieldTooltip(panelIndex, cfi.getHintText())
       
      Panel.setFieldActive(panelIndex)
      Panel.calcLayout()
    }
     
    // add object in cdatahelper.CdataMapping so it can be referred at form side
    this.addCdataFieldInfoToMap(fieldIndex, cfi)
     
    string propertyValue = currentValue
    int propertyIndex = getPropertyIndex(formatMessage("VALUE|1", fieldIndex, ...), true, true, true, true)
     
    this.setPropertyClientComponentReady(propertyValue, propertyIndex, cfi)
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void RenderingHelper.setModuleFieldValue(
  int index                                     // 
  int ModuleID                                  // 
  string ModuleDescription                      // 
  DatoPersonalizzatoInfo datoPersonalizzatoInfo // 
)
{
  QappCore.DTTLogMessage("ABSTRACT", ..., DTTError)
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string RenderingHelper.computePropertyValueForModuleType(
  DatoPersonalizzatoInfo cfi // 
  string currentCFIValue     // 
)
{
  QappCore.DTTLogMessage("ABSTRACT", ..., DTTError)
  return null
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public DatoPersonalizzatoInfo RenderingHelper.retrieveOwnerCdataFieldInfoAlreadyWithAddtionalProperties(
  DatoPersonalizzatoInfo cfiToBeSearched // 
)
{
  DatoPersonalizzatoInfo linkedMainModuleCdataFieldInfo = null
  DatoPersonalizzato dpFound = this.getMatchingDatoPersonalizzatoInOwner(cfiToBeSearched)
  if (dpFound)
  {
    linkedMainModuleCdataFieldInfo = cast(dpFound.getCustomDataInfo())
  }
  return linkedMainModuleCdataFieldInfo
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string RenderingHelper.prepareMultiTypeFieldCompatibleJsonString(
  DatoPersonalizzatoInfo cfi        // 
  string propertyValue              // 
  optional string decodedValue = "" // 
)
{
  string multiTypeFieldCompatibleString = null
  IDArray optionsArray = new()
   
  int:cdataType dataType = cfi.getCustomDataType()
   
   
  IDMap jsonMap = new()
  string:widgetModes widgetMode = this.getClientComponentMode(cfi)
  jsonMap.setValue("type", widgetMode)
   
  boolean storeMinAndMax = false
  boolean storeNormalWatermark = false
   
  if (cfi.DependsOnFieldId <= 0)
  {
    cfi.parseCmbValue()
  }
  switch (dataType)
  {
    case ComboCDataType:
      string:comboStyles comboType = cfi.ComboStyle
      IDArray comboNames = cfi.getComboElementNames()
      string semicolonSeparatedItems = ""
      for (int i = 0; i < comboNames.length(); i = i + 1)
      {
         string currentElement = comboNames.getValue(i)
         semicolonSeparatedItems = SH.Concat(semicolonSeparatedItems, currentElement, ";")
         optionsArray.addValue(currentElement)
      }
      //  
       
      // options is common to all comboTypes
      jsonMap.setObject("options", optionsArray)
       
       
      // columns makes sense only for radio and checkgroup
      if (comboType == RadioGroup or comboType == CheckGroup)
      {
         int numberOfColumns = cfi.NumberOfColumns
         jsonMap.setValue("columns", numberOfColumns)
      }
       
      // setting the value in a "custom" way since it depends on comboType
      if (comboType == RadioGroup or comboType == ComboBox)
      {
         jsonMap.setValue("value", propertyValue)
      }
      else if (comboType == CheckGroup)
      {
         IDArray valuesArray = SH.tokenizeToArray(propertyValue, ";", ...)
         jsonMap.setObject("value", valuesArray)
      }
       
    break
    case MemoCDataType:
      storeNormalWatermark = true
      int numberOfVisibileLines = cfi.NumberOfVisibleLinesInMemo
      jsonMap.setValue("lines", numberOfVisibileLines)
       
      // replace Delphi new line char (if any) with "\n" new line char
      propertyValue = replace(propertyValue, Tools.getNewLineChar(), "\n")
    break
    case CalcFloatCDataType:
    case FloatCDataType:
      storeMinAndMax = true
       
      int decimalPrecision = cfi.DecimalPrecision
      jsonMap.setValue("decimals", decimalPrecision)
       
       
    break
    case IntegerCDataType:
      storeMinAndMax = true
       
    break
    case TextCDataType:
      storeNormalWatermark = true
      string mask = cfi.TextFieldMask
      if (mask != "")
      {
         jsonMap.setValue("mask", mask)
         boolean saveLiterals = cfi.MaskeditSaveLiterals
         jsonMap.setBoolean("saveLiterals", saveLiterals)
         string placeholderChar = cfi.MaskeditPlaceholderChar
         jsonMap.setValue("placeholderChar", placeholderChar)
      }
    break
    case BooleanCDataType:
       
      // no specific code for boolean, it is simply handled below to set the proper json value datatype
       
    break
    case DateCDataType:
       
      // for Date currently it is all hardcoded (when we'll localize the app we'll modify here of course)
      jsonMap.setValue("dateFormat", "d/m/Y")
      jsonMap.setValue("locale", "it")
       
      // watermark is custom for Date (so we do not set it at the end of the method)
      jsonMap.setValue("watermark", "gg/mm/aaaa")
    break
    case ArticoloCDataType:
    case CliForCDataType:
    case ContattoCDataType:
    case EventoCDataType:
    case AltraAnagraficaCDataType:
    case ProgettoCDataType:
    case FunzioneCDataType:
    case PrivatoCDataType:
    case DocumentoCDataType:
    case PersonaleCDataType:
      storeNormalWatermark = true
      //  
      // openButton could be removed for example in "eventi da web"
      boolean showOpenButtonInLookup = toInteger(QappCore.QappCoreRuntimeBehavior.getBehaviorParameter(showOpenButtonInLookupCdata))
      jsonMap.setBoolean("showOpenBtn", showOpenButtonInLookup)
      if (decodedValue != "")
      {
         jsonMap.setValue("decodedValue", decodedValue)
      }
    break
    default:
      QappCore.DTTLogMessage("DEFAULT SHOULD NEVER BE HIT MAKE SURE EACH CASE IS IMPLEMENTED ABOVE", ..., DTTError)
    break
  }
   
  string:jsonValuePropertyDatatypes valueDatatype = this.getJsonValueDataTypeGivenCdataType(dataType)
  //  
  // setting the value
  switch (valueDatatype)
  {
    case integer:
      int intValue = if(propertyValue == "", null, toInteger(propertyValue))
      jsonMap.setValue("value", intValue)
    break
    case boolean:
      boolean booleanValue = toInteger(propertyValue)
      jsonMap.setBoolean("value", booleanValue)
    break
    case float:
      float floatValue = if(propertyValue == "", null, toFloat(propertyValue))
      jsonMap.setValue("value", floatValue)
    break
    case string:
      jsonMap.setValue("value", propertyValue)
    break
    case custom:
      QappCore.DTTLogMessage("value has been set already since it is not fixed", ..., DTTInfo)
       
      // just in case check that value has been set above (in principle custom is used only by checkgroup and radiogroup that share most of the logic but not the value one)
      if (!(jsonMap.containsKey("value")))
      {
         QappCore.DTTLogMessage("custom valuedatatype not set! this should not happen", ..., DTTError)
      }
    break
  }
   
  if (storeMinAndMax)
  {
     
    // getMinValue and getMaxValue already make sure 0 is returned in case of null!
    float min = cfi.getMinValue()
    float max = cfi.getMaxValue()
     
    // if both min and max are at zero we do not pass min and max
    boolean bothMinAndMaxAreZero = nullValue(min, 0) == 0 and nullValue(max, 0) == 0
     
    if (!(bothMinAndMaxAreZero))
    {
      jsonMap.setValue("min", min)
      jsonMap.setValue("max", max)
    }
  }
   
  if (storeNormalWatermark)
  {
    string watermark = cfi.getWatermark()
    jsonMap.setValue("watermark", watermark)
  }
   
  // compute the enabled property
   
  boolean enabledValue = false
   
  boolean disabled = cfi.isReadOnly() or this.ownerIsLocked()
  boolean enableNeedsToBeSet = !(disabled) and dataType != CalcFloatCDataType
  if (enableNeedsToBeSet)
  {
    enabledValue = true
  }
  if (cfi.UrlTokenForUi != null)
  {
    enabledValue = cfi.UrlTokenForUi.SbloccaCampiReadonly and propertyValue == ""
  }
  jsonMap.setBoolean("enabled", enabledValue)
   
   
  multiTypeFieldCompatibleString = JSON.stringify(jsonMap)
  return multiTypeFieldCompatibleString
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static string RenderingHelper.extractActualValueAsStringFromMultiTypeFieldJsonValue(
  int:dataTypes dataType        // 
  string:comboStyles comboStyle // 
  string propertyValue          // 
)
{
  string actualValue = ""
   
  IDMap jsonMap = cast(JSON.parse(propertyValue))
   
   
  string:jsonValuePropertyDatatypes jsonValueDatatype = this.getJsonValueDataTypeGivenCdataType(dataType)
   
  switch (jsonValueDatatype)
  {
    case string:
      actualValue = jsonMap.getValue("value")
    break
    case integer:
      int intValue = jsonMap.getValue("value")
      actualValue = toString(intValue)
    break
    case float:
      float floatValue = jsonMap.getValue("value")
      actualValue = toString(floatValue)
    break
    case boolean:
      boolean booleanValue = jsonMap.getValue("value")
      actualValue = toString(booleanValue)
    break
    case custom:
      switch (comboStyle)
      {
         case ComboBox:
           actualValue = jsonMap.getValue("value")
         break
         case RadioGroup:
           actualValue = jsonMap.getValue("value")
         break
         case CheckGroup:
           IDArray checkgroupSelectedValues = (IDArray)jsonMap.getObject("value")
           for (int i = 0; i < checkgroupSelectedValues.length(); i = i + 1)
           {
             string currentValue = checkgroupSelectedValues.getValue(i)
             actualValue = SH.Concat(actualValue, currentValue, ";")
           }
         break
      }
    break
    default:
      QappCore.DTTLogMessage("UNHANDLED: THIS SHOULD NEVER OCCUR", ..., DTTError)
    break
  }
   
   
  return actualValue
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string RenderingHelper.getClientComponentMode(
  DatoPersonalizzatoInfo cfi // 
)
{
  string:widgetModes clientComponentMode = null
   
  int:cdataType dataType = cfi.getCustomDataType()
   
  if (cfi.DependsOnFieldId <= 0)
  {
    cfi.parseCmbValue()
  }
  switch (dataType)
  {
    case ComboCDataType:
      string:comboStyles style = cfi.ComboStyle
      switch (style)
      {
         case ComboBox:
           clientComponentMode = combobox
         break
         case RadioGroup:
           clientComponentMode = radio
         break
         case CheckGroup:
           clientComponentMode = checkgroup
         break
      }
    break
    case MemoCDataType:
      clientComponentMode = memo
    break
    case IntegerCDataType:
      clientComponentMode = spinedit
    break
    case CalcFloatCDataType:
    case FloatCDataType:
      clientComponentMode = floatedit
    break
    case TextCDataType:
      string mask = cfi.TextFieldMask
      if (mask != "")
      {
         clientComponentMode = maskedit
      }
      else 
      {
         clientComponentMode = text
      }
    break
    case BooleanCDataType:
      clientComponentMode = boolean
    break
    case DateCDataType:
      clientComponentMode = date
    break
    case ArticoloCDataType:
    case CliForCDataType:
    case ContattoCDataType:
    case EventoCDataType:
    case AltraAnagraficaCDataType:
    case PersonaleCDataType:
    case ProgettoCDataType:
    case FunzioneCDataType:
    case PrivatoCDataType:
    case DocumentoCDataType:
      clientComponentMode = lookup
    break
    default:
      QappCore.DTTLogMessage("NOT SUPPORTED", ..., DTTError)
    break
  }
   
   
  return clientComponentMode
}


// ──────────────────────────────────

// *******************************************************************************************************************************************************************
// this method sets the property in the rendering helper, making sure the format needed by the client Js Component is applied (JSON is 
// 
// e.g.: to set "Asia" in a continents radiogroup it first prepares the property as
// radiogroup<SEPARATOR>Europe;Asia;Africa<SEPARATOR>3<SEPARATOR>Asia
// and the number of columns (3) and the available options (Europe;Asia;Africa) come from DatoPersonalizzato info
// 
// Basically this method must be called to set the value of the rendering helper (it centralizes the calls to prepareMultiuTypeFieldCompaatibleString and setProperty)
// *******************************************************************************************************************************************************************
public void RenderingHelper.setPropertyClientComponentReady(
  string value               // 
  int propertyIndex          // 
  DatoPersonalizzatoInfo dpi // 
)
{
  string decodedValue = ""
  if (dpi.isModuleType())
    decodedValue = this.computePropertyValueForModuleType(dpi, value)
   
  // decodedValue is not empty only if isModuleType() is true!
  string componentReadyValue = this.prepareMultiTypeFieldCompatibleJsonString(dpi, value, decodedValue)
   
  this.setProperty(propertyIndex, componentReadyValue)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public MainModule RenderingHelper.tryRetrieveMainModuleSetInModuleTypeRenderingHelperProperty(
  int fieldIndex // 
)
{
  string value = getProperty(fieldIndex)
  string key = formatMessage("VALUE|1", fieldIndex, ...)
  MainModuleDatoPersonalizzatoInfo dpi = (MainModuleDatoPersonalizzatoInfo)CdataMapping.getObject(key)
  value = this.extractActualValueAsStringFromMultiTypeFieldJsonValue(dpi.getCustomDataType(), dpi.ComboStyle, value)
  int mainId = toInteger(value)
  boolean isContatto = false
  int:kordapp kordApp = dpi.getRelatedKordApp(isContatto)
  MainModule mm = MainModule.retrieve(kordApp, mainId, ...)
  return mm
}


// ──────────────────────────────────

// *****************************************************************
// given a specific cdata Type the method returns the
// data type of the value stored in the JSON of the custom component
// 
// e.g.: if type is module type we store the value as integer
// 
// *****************************************************************
private static string RenderingHelper.getJsonValueDataTypeGivenCdataType(
  int:cdataType cdataType // 
)
{
  string:jsonValuePropertyDatatypes dataTypeInJsonValue = ""
   
  switch (cdataType)
  {
    case ComboCDataType:
      dataTypeInJsonValue = custom
    break
    case DateCDataType:
    case TextCDataType:
    case MemoCDataType:
      dataTypeInJsonValue = string
    break
    case FloatCDataType:
    case CalcFloatCDataType:
      dataTypeInJsonValue = float
    break
    case IntegerCDataType:
      dataTypeInJsonValue = integer
    break
    case BooleanCDataType:
      dataTypeInJsonValue = boolean
    break
    case ArticoloCDataType:
    case CliForCDataType:
    case ContattoCDataType:
    case EventoCDataType:
    case AltraAnagraficaCDataType:
    case ProgettoCDataType:
    case FunzioneCDataType:
    case PrivatoCDataType:
    case DocumentoCDataType:
    case PersonaleCDataType:
      dataTypeInJsonValue = integer
    break
    default:
      QappCore.DTTLogMessage("CASE NOT HANDLED: THIS SHOULD NOT OCCUR", ..., DTTError)
    break
  }
   
   
   
  return dataTypeInJsonValue
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void RenderingHelper.renderGroup(
  string sectionName // 
)
{
  QappCore.DTTLogMessage("ABSTRACT", ..., DTTError)
  return 
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void RenderingHelper.prepareAndRender(
  boolean renderMandatoryOnly   // 
  optional int extraParams = 0  // 
  optional string idResult = "" // 
)
{
  QappCore.DTTLogMessage("ABSTRACT", ..., DTTError)
  return 
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public IDCollection RenderingHelper.populateCollectionToBeShownAtPanel(
  IDCollection references of Riferimento // 
  ReferenceType rt                       // 
)
{
  IDCollection coll of RiferimentoRenderingHelper = new()
   
  for each Riferimento r in references
  {
    RiferimentoRenderingHelper rrh = cast(RiferimentoRenderingHelper.create(r, Panel, ...))
    r.ReferenceType = rt
    r.SetRenderingHelper(rrh)
    coll.add(rrh)
  }
  return coll
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void RenderingHelper.SetFormProperties(
  IDPanel panel           // 
  int:layoutValues layout // 
)
{
  Panel = panel
  panel.layout = layout
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void RenderingHelper.initializePropertiesFromMapping()
{
  IDArray keys = CdataMapping.getKeys()
   
  for (int i = 0; i < keys.length(); i = i + 1)
  {
    string propertyName = keys.getValue(i)
     
    DatoPersonalizzatoInfo cfi = cast(CdataMapping.getObject(propertyName))
     
    DatoPersonalizzatoInfo ownerCFI = cast(this.retrieveOwnerCdataFieldInfoAlreadyWithAddtionalProperties(cfi))
     
    string value = ""
     
    if (ownerCFI)
    {
      DatoPersonalizzato dp = this.getMatchingDatoPersonalizzatoInOwner(ownerCFI)
       
      if (dp)
      {
         value = dp.getValueAsString()
      }
    }
     
    int propertyIndex = this.getPropertyIndex(propertyName, true, true, true, true)
     
    if (ownerCFI)
    {
      this.setPropertyClientComponentReady(value, propertyIndex, ownerCFI)
    }
    else 
    {
      this.setPropertyClientComponentReady("", propertyIndex, cfi)
    }
  }
}


// ──────────────────────────────────

// ************************************************
// this procedure computes the field height: 
// 1. in case of Subform the height is fixed to 200
// 2. otherwise it depends on the configuration
// ************************************************
public int RenderingHelper.ComputeFieldHeight(
  DatoPersonalizzatoInfo cfi // 
)
{
  int fieldHeight = 70
  QappCore.DTTLogMessage(formatMessage("Computing height for Customdata |1", cfi.getCaption(), ...), 666666, ...)
  if (cfi.getCustomDataType() == MemoCDataType)
  {
    if (cfi.NumberOfVisibleLinesInMemo > 0)
    {
      fieldHeight = (20 * cfi.NumberOfVisibleLinesInMemo) + 50
    }
    if (fieldHeight <= 50)
    {
      fieldHeight = 110
    }
  }
  else if (cfi.getCustomDataType() == ComboCDataType and cfi.ComboStyle != ComboBox)
  {
    IDArray elementsInRadio = cfi.getElements()
    int length = elementsInRadio.length()
    float noOfRows = toFloat(length) / cfi.NumberOfColumns
    noOfRows = ceil(noOfRows)
    fieldHeight = noOfRows * 40
    int fieldMinimumHeightForRadioOrCheckGroup = 95
    if (noOfRows > 1)
    {
      if (fieldHeight < fieldMinimumHeightForRadioOrCheckGroup)
      {
         fieldHeight = fieldMinimumHeightForRadioOrCheckGroup
      }
    }
    else if (noOfRows == 1)
    {
      fieldHeight = 55
    }
  }
  else 
  {
    switch (cfi.getCustomDataType())
    {
      case MemoCDataType:
         fieldHeight = 110
      break
      default:
         fieldHeight = 70
      break
    }
  }
  return fieldHeight
}


// ──────────────────────────────────

// **************************************************************************
// this procedure computes the width of the field we just insert in the panel
// in case of Combo or main module we put the maximum size
// **************************************************************************
public int RenderingHelper.ComputeFieldWidth(
  DatoPersonalizzatoInfo cfi // 
  boolean isListLayout       // 
  int maxFieldsLeftPosition  // 
)
{
  int fieldWidth = 220
  DevTools.ToBeReviewed("Review this code for width computaton when data type is Combo")
   
  QappCore.DTTLogMessage(formatMessage("Computing width for Field: |1", cfi.getCaption(), ...), ...)
  if (cfi.getCustomDataType() == ComboCDataType and cfi.ComboStyle != ComboBox)
  {
    if (isListLayout)
    {
      IDArray ida = cfi.getComboElementNames()
      if (ida.length() == 0)
      {
         // re parse again, becaue in case of riferimenti, customdata is not loaded and parsing is still not done
         cfi.parseCmbValue()
         ida = cfi.getComboElementNames()
      }
      fieldWidth = ida.length() * 100 + 50
    }
    else 
      fieldWidth = maxFieldsLeftPosition - 50
  }
  else if (cfi.getCustomDataType() == ComboCDataType and cfi.ComboStyle == ComboBox)
  {
    int maxLength = 0
    IDArray ida = cfi.getComboElementNames()
    for (int i = 0; i < ida.length(); i = i + 1)
    {
      string name = ida.getValue(i)
      int currentElLength = length(name)
      if (name != "" and name != null and currentElLength > maxLength)
      {
         maxLength = currentElLength
      }
    }
    fieldWidth = maxLength * 10 + 50
    if (fieldWidth < 200)
    {
      fieldWidth = 300
    }
  }
  else 
  {
    // old WidthConfigurations
    switch (cfi.getCustomDataType())
    {
      case BooleanCDataType:
         fieldWidth = 200
      break
      case DateCDataType:
         fieldWidth = 200
      break
      case CalcFloatCDataType:
         fieldWidth = 200
      break
      case ComboCDataType:
         fieldWidth = 300
      break
      case TextCDataType:
         fieldWidth = 300
      break
      case MemoCDataType:
         fieldWidth = 300
      break
      case IntegerCDataType:
         fieldWidth = 220
      break
      case FloatCDataType:
         fieldWidth = 220
      break
    }
  }
  return fieldWidth
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public int RenderingHelper.getFieldPanelIndex(
  int fieldCount // 
)
{
  QappCore.DTTLogMessage("ABSTRACT", ..., DTTError)
  return 0
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public IDCollection RenderingHelper.getRenderedDatoPersonalizzatoCollection()
{
  IDCollection renderedDPs of DatoPersonalizzato = new()
   
  if (CdataMapping == null)
  {
    return renderedDPs
  }
   
  IDArray keys = CdataMapping.getKeys()
  QappCore.DTTLogMessage(keys.length(), 777, ...)
   
  for (int i = 0; i < keys.length(); i = i + 1)
  {
    string propertyName = keys.getValue(i)
    DatoPersonalizzatoInfo info = cast(CdataMapping.getObject(propertyName))
     
    if (info)
    {
      DatoPersonalizzato dp = this.getMatchingDatoPersonalizzatoInOwner(info)
       
      if (dp)
      {
         boolean alreadyAdded = false
          
         for each DatoPersonalizzato existingDp in renderedDPs
         {
           if (existingDp == dp)
           {
             alreadyAdded = true
             break 
           }
         }
          
         if (!(alreadyAdded))
         {
           renderedDPs.addRef(dp)
         }
      }
    }
  }
  return renderedDPs
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean RenderingHelper.ValidateRenderedDatiPers()
{
  boolean valid = true
  boolean allCustomDataPassed = true
   
  if (Owner)
  {
    IDCollection renderedDPs of DatoPersonalizzato = this.getRenderedDatoPersonalizzatoCollection()
     
    for each DatoPersonalizzato dp in renderedDPs
    {
      valid = dp.validate(...)
      if (!(valid))
      {
         allCustomDataPassed = false
         break 
      }
    }
  }
  return allCustomDataPassed
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public int RenderingHelper.supportedFieldsCount()
{
  QappCore.DTTLogMessage("ABSTRACT", ..., DTTError)
  return 0
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string RenderingHelper.composeFieldLimitExceedWarningMessage()
{
  string message = formatMessage("Attenzione!<br>Alcuni campi dei dati personalizzati non vengono visualizzati perché è stato superato "<br> "il numero massimo consentito (<b>|1</b>).<br><br> Contattare 
           l'amministratore di sistema per rivedere la configurazione dei campi.", this.supportedFieldsCount(), ...)
  return message
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean RenderingHelper.ownerIsLocked()
{
  QappCore.DTTLogMessage("ABSTRACT", ..., DTTError)
  return false
}


// ──────────────────────────────────

// ***********************************************************
// Event raised to the document when the transaction is closed
// ***********************************************************
event RenderingHelper.OnEndTransaction()
{
  IDDocumentStructure idds = getStructure()
  int editedFieldIndex = 0
  for (int i = 1; i <= idds.getPropertyCount(); i = i + 1)
  {
    if (wasModified(i))
    {
      editedFieldIndex = i
      this.updateDatoPersonalizzatoInOwner(editedFieldIndex)
      break 
    }
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static MainModuleCdataRenderingHelper MainModuleCdataRenderingHelper.Create(
  MainModule mainModule                    // 
  IDPanel panel                            // 
  optional int verticalGap = 30            // 
  optional int horizontalGap = 30          // 
  optional int MaxFieldLeftPosition = 1500 // 
)
{
  MainModuleCdataRenderingHelper ch = new()
  ch.setOwner(mainModule)
   
  mainModule.SetRenderingHelper(ch)
  ch.Panel = panel
   
  if (panel)
  {
    ch.LayoutType = panel.layout
    panel.setDocument(ch, ...)
  }
   
  ch.CurrentLeftPosition = 0
  ch.CurrentTopPosition = 0
  ch.CurrentGroupId = -1
   
  ch.HorizontalGap = horizontalGap
  ch.VerticalGap = verticalGap
  ch.MaxFieldsLeftPosition = MaxFieldLeftPosition
  ch.OverrideSupportedFieldsCountForUnitTest = 0
   
   
  return ch
}


// ──────────────────────────────────

// **********
//  
// **********
public void MainModuleCdataRenderingHelper.setModuleFieldValue(
  int index                                     // 
  int ModuleID                                  // 
  string ModuleDescription                      // 
  DatoPersonalizzatoInfo datoPersonalizzatoInfo // 
)
{
   
  // the rendering helper holds Description in the property
  this.setProperty(index, ModuleDescription)
   
  DatoPersonalizzatoInfo dpi = datoPersonalizzatoInfo
   
  IDDocument doc = base.getOwner()
  if (doc)
  {
    MainModule mm = cast(doc)
    mm.SetCustomData(toString(ModuleID), cast(dpi))
  }
}


// ──────────────────────────────────

// ****************************************************************
// small helper to compute description given the passed information
// ****************************************************************
private string MainModuleCdataRenderingHelper.computeDescription(
  int mainModuleMainId                                              // 
  MainModuleDatoPersonalizzatoInfo mainModuelDatoPersonalizzatoInfo // 
)
{
  // extarct mainmodule description
  string description = ""
  MainModule mainmodule = null
  int cdFieldmoduleMainID = mainModuleMainId
  boolean isContatto = false
  MainModuleDatoPersonalizzatoInfo mmcfi = mainModuelDatoPersonalizzatoInfo
  int:kordapp moduleKordApp = mmcfi.getRelatedKordApp(isContatto)
  if (moduleKordApp > 0 and cdFieldmoduleMainID > 0)
  {
    if (isContatto)
    {
      Contatto c = Contatto.get(cdFieldmoduleMainID)
      mainmodule = MainModule.retrieve(ClientiFornitori, c.IDCONTO, ...)
      description = formatMessage("|1 |2 (|3)", c.COGNOME, c.NOME, mainmodule.getDescription(), ...)
    }
    else 
    {
      mainmodule = MainModule.retrieve(moduleKordApp, cdFieldmoduleMainID, ...)
      if (mainmodule)
      {
         description = mainmodule.getDescription()
      }
    }
  }
  return description
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string MainModuleCdataRenderingHelper.computePropertyValueForModuleType(
  DatoPersonalizzatoInfo cfi // 
  string currentCFIValue     // 
)
{
   
  int integerValue = toInteger(currentCFIValue)
   
  MainModuleDatoPersonalizzatoInfo mmdpi = (MainModuleDatoPersonalizzatoInfo)cfi
   
  // extract MainModule description
  string description = this.computeDescription(integerValue, mmdpi)
   
   
  return description
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public int MainModuleCdataRenderingHelper.getFieldPanelIndex(
  int fieldCount // 
)
{
  return fieldCount - 1
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void MainModuleCdataRenderingHelper.renderGroup(
  string sectionName // 
)
{
  if (!(Panel))
  {
    return 
  }
   
  string groupHeader = upper(trim(sectionName))
   
  int groupFlags = 0 | Visible | Enabled
   
  int groupIndex = Panel.addGroup(groupHeader, groupFlags, CustomDataStyle)
   
  Panel.setGroupCollapsable(groupIndex, true)
  Panel.setGroupHeaderSize(groupIndex, 0, 150)
   
  Panel.calcLayout()
   
  this.CurrentGroupId = groupIndex
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void MainModuleCdataRenderingHelper.prepareAndRender(
  boolean renderMandatoryOnly   // 
  optional int extraParams = 0  // 
  optional string idResult = "" // 
)
{
  CurrentTopPosition = 0
  CurrentLeftPosition = 0
  FieldLimitExceededWhileRendering = false
   
  MainModule mainModule = (MainModule)Owner
   
  IDCollection sections of CdataSection = null
   
  if (renderMandatoryOnly)
  {
    sections = mainModule.getMandatoryCustomdataSections()
  }
  else 
  {
    sections = mainModule.GetAvailableCustomDataSections(...)
  }
   
  int fieldCount = 1
   
  for each CdataSection cs in sections
  {
    if (cs.shouldSkipRendering())
    {
      continue 
    }
     
    this.renderGroup(cs.Name)
    CurrentLeftPosition = 0
     
    for each MainModuleDatoPersonalizzatoInfo currentCFI in cs.CDATAFIELDSINFO
    {
      if (currentCFI.Active == Yes)
      {
          
         FieldLimitExceededWhileRendering = fieldCount > this.supportedFieldsCount()
         if (FieldLimitExceededWhileRendering)
           break 
          
         MainModuleDatoPersonalizzatoInfo cfi = cast(this.retrieveOwnerCdataFieldInfoAlreadyWithAddtionalProperties(currentCFI))
          
          
         if (cfi)
         {
           UrlToken ut = (UrlToken)mainModule.getObjectTag("URL_TOKEN")
           if (ut)
           {
             cfi.UrlTokenForUi = ut
           }
           string value = mainModule.getDatoPersonalizzatoValueAsString(cfi)
           this.updateRenderingHelperProperty(cfi, fieldCount, value)
           fieldCount = fieldCount + 1
         }
      }
    }
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public int MainModuleCdataRenderingHelper.supportedFieldsCount()
{
  int supportedCustomDataFieldsCount = 250
  if (OverrideSupportedFieldsCountForUnitTest > 0)
  {
    supportedCustomDataFieldsCount = OverrideSupportedFieldsCountForUnitTest
  }
  return supportedCustomDataFieldsCount
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean MainModuleCdataRenderingHelper.ownerIsLocked()
{
  MainModule mm = cast(Owner)
  return mm.isLocked()
}


// ──────────────────────────────────

// **********************************************************************
//  Creates a RenderingHelper instance for a single Riferimento.
//  
//  This method initializes a RenderingHelper that is meant to manage the
//  rendering of custom data fields for one specific Riferimento only.
//  The created helper acts as a "row helper" and is not responsible for
//  rendering a list of references.
// **********************************************************************
public static RiferimentoRenderingHelper RiferimentoRenderingHelper.create(
  Riferimento riferimento                  // 
  IDPanel panel                            // 
  optional int verticalGap = 30            // 
  optional int horizontalGap = 30          // 
  optional int MaxFieldLeftPosition = 1500 // 
)
{
  RiferimentoRenderingHelper ch = new()
  ch.setOwner(riferimento)
  riferimento.SetRenderingHelper(ch)
  ch.setInitialHeaderValues()
   
  ch.Panel = panel
  ch.IsListHost = false
   
  // properties initialize
  ch.CurrentLeftPosition = 0
  ch.CurrentTopPosition = 0
  ch.CurrentGroupId = -1
   
  ch.HorizontalGap = horizontalGap
  ch.VerticalGap = verticalGap
  ch.MaxFieldsLeftPosition = MaxFieldLeftPosition
  ch.OverrideSupportedFieldsCountForUnitTest = 0
  if (panel and panel.layout != List)
  {
    ch.LayoutType = panel.layout
    panel.setDocument(ch, ...)
  }
   
  return ch
}


// ──────────────────────────────────

// ********************************************************************************
//  Creates a RenderingHelper instance acting as the host for a list of references.
//  
//  Unlike the standard create(...) method, this helper represents the controller
//  responsible for rendering an entire list of Riferimento objects inside a panel.
//  It orchestrates the creation and management of row-level RenderingHelpers,
//  one for each reference in the list.
// ********************************************************************************
public static RiferimentoRenderingHelper RiferimentoRenderingHelper.createListHost(
  IDDocument owner                         // 
  IDPanel panel                            // 
  optional int verticalGap = 30            // 
  optional int horizontalGap = 30          // 
  optional int MaxFieldLeftPosition = 1500 // 
)
{
  RiferimentoRenderingHelper ch = new()
  ch.setOwner(cast(owner))
  ch.Panel = panel
  ch.IsListHost = true
   
  // properties initialize
  ch.CurrentLeftPosition = 0
  ch.CurrentTopPosition = 0
  ch.CurrentGroupId = -1
   
  ch.HorizontalGap = horizontalGap
  ch.VerticalGap = verticalGap
  ch.MaxFieldsLeftPosition = MaxFieldLeftPosition
   
  if (panel)
  {
    ch.LayoutType = panel.layout
    panel.setDocument(ch, ...)
  }
   
  return ch
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean RiferimentoRenderingHelper.shouldHeader2BeVisible()
{
  boolean header2ShouldBeAvailable = false
  Riferimento r = cast(base.getOwner())
  if (!(r.ReferenceType))
    r.ReferenceType = r.getReferenceType()
   
  if (r.ReferenceType.SourceKordapp == ClientiFornitori or r.ReferenceType.SourceKordapp == Personale or r.ReferenceType.SourceKordapp == AltreAnagrafiche)
  {
    header2ShouldBeAvailable = true
  }
   
  return header2ShouldBeAvailable
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string RiferimentoRenderingHelper.getHeader2Caption()
{
  string header2Caption = ""
  Riferimento r = cast(base.getOwner())
  if (!(r.ReferenceType))
  {
    r.ReferenceType = r.getReferenceType()
  }
  switch (r.ReferenceType.SourceKordapp)
  {
    case ClientiFornitori:
      header2Caption = "Contatto"
    break
    case Personale:
      header2Caption = "Tipo personale"
    break
    case AltreAnagrafiche:
      header2Caption = "Intervento"
    break
  }
   
  return header2Caption
}


// ──────────────────────────────────

// ****************************************************************************
// if isContatto is true, Id = IdContatto and description= contatto description
// if isContatto is False,Id = IdForAll 
// ****************************************************************************
public void RiferimentoRenderingHelper.updateOwnerValues(
  boolean isContatto // 
  int id             // 
  string description // 
)
{
  Riferimento r = cast(base.getOwner())
  if (r)
  {
    if (isContatto)
    {
      Header2 = description
      r.IDCONTATTO = id
    }
    else 
    {
      r.IDForAll = id
      r.computeDestination()
      Header1 = r.getDestinationDescription()
       
      // clear contatto values
      r.IDCONTATTO = null
      Header2 = ""
    }
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void RiferimentoRenderingHelper.setInitialHeaderValues()
{
  Riferimento r = cast(base.getOwner())
  if (r)
  {
    r.computeDestination()
    this.setProperty(toPropertyIndex(Header1), r.DestinationDescription)
    if (this.shouldHeader2BeVisible())
    {
      r.computeAdditionalModuleValue()
      this.setProperty(toPropertyIndex(Header2), r.AdditionalModuleValue)
    }
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void RiferimentoRenderingHelper.copyCdataMappingFromTemplate(
  IDMap cdataMap // 
)
{
  IDArray ida = cdataMap.getKeys()
  for (int i = 0; i < ida.length(); i = i + 1)
  {
    string fieldName = ida.getValue(i)
    DatoPersonalizzatoInfo dpi = (DatoPersonalizzatoInfo)cdataMap.getObject(fieldName)
     
    CdataMapping.setObject(fieldName, dpi)
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void RiferimentoRenderingHelper.delete()
{
  Riferimento riferimento = cast(base.getOwner())
  riferimento.deleted = true
  this.deleted = true
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string RiferimentoRenderingHelper.getReferenceTypeString()
{
  string refTypeDescription = ""
  Riferimento r = cast(base.getOwner())
  if (!(r.ReferenceType))
  {
    r.ReferenceType = ReferenceType.get(r.IDTipoRiferimento)
  }
  refTypeDescription = r.ReferenceType.Name
  return refTypeDescription
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public int RiferimentoRenderingHelper.getFieldPanelIndex(
  int fieldCount // 
)
{
  return fieldCount + 2
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void RiferimentoRenderingHelper.prepareAndRender(
  boolean renderMandatoryOnly1  // 
  optional int extraParams = 0  // 
  optional string idResult = "" // 
)
{
  if (!(IsListHost))
  {
    return 
  }
  FieldLimitExceededWhileRendering = false
   
  MainModule mainModule = (MainModule)Owner
   
  int idReferenceType = extraParams
  ReferenceType rt = ReferenceType.get(idReferenceType)
   
  IDCollection references of Riferimento = mainModule.GetRiferimenti(rt)
   
  IDCollection refCDs of RiferimentoDatoPersonalizzatoInfo = RiferimentoDatoPersonalizzatoInfo.getSequenzaBasedSortedCollection(rt)
   
  IDCollection rifRenderingHelperCollection of RiferimentoRenderingHelper = this.populateCollectionToBeShownAtPanel(references, rt)
   
  Panel.setCollection(rifRenderingHelperCollection, true)
   
  this.initializeHeader2Column(rifRenderingHelperCollection)
   
  int fieldCount = 1
   
  for each RiferimentoDatoPersonalizzatoInfo currentRCDataInfo in refCDs
  {
    if (currentRCDataInfo.Attivo == Yes)
    {
      QappCore.DTTLogMessage(formatMessage("rendering field ID: |1, Seq: |2, caption: |3, References count: |4", currentRCDataInfo.getFieldID(), currentRCDataInfo.Sequenza, currentRCDataInfo.getCaption(), 
            references.count(), ...), ...)
      FieldLimitExceededWhileRendering = fieldCount > this.supportedFieldsCount()
      if (FieldLimitExceededWhileRendering)
         break 
       
      for each Riferimento r in references
      {
         r.loadRDatiPersonalizzati()
         RiferimentoRenderingHelper rrh = cast(r.GetRenderingHelper())
          
         RiferimentoDatoPersonalizzatoInfo rdpi = cast(rrh.retrieveOwnerCdataFieldInfoAlreadyWithAddtionalProperties(currentRCDataInfo))
          
         string initialValue = r.getRDatoPersonalizzato(rdpi)
          
         rrh.updateRenderingHelperProperty(rdpi, fieldCount, initialValue)
      }
      fieldCount = fieldCount + 1
       
    }
  }
  Panel.calcLayout()
   
  if (!(mainModule.inserted))
  {
    mainModule.Riferimenti.setOriginal()
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public int RiferimentoRenderingHelper.supportedFieldsCount()
{
  int supportedCustomDataFieldsCount = 100
  if (OverrideSupportedFieldsCountForUnitTest > 0)
  {
    supportedCustomDataFieldsCount = OverrideSupportedFieldsCountForUnitTest
  }
  return supportedCustomDataFieldsCount
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean RiferimentoRenderingHelper.ownerIsLocked()
{
  boolean ownerIsLocked = false
   
  Riferimento owner = cast(Owner)
  if (owner)
  {
    MainModule parentOfRiferimento = (MainModule)owner.parent
    if (parentOfRiferimento)
    {
      ownerIsLocked = parentOfRiferimento.isLocked()
    }
  }
   
  return ownerIsLocked
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void RiferimentoRenderingHelper.initializeHeader2Column(
  IDCollection renderingHelperCollection of RenderingHelper // 
)
{
  renderingHelperCollection.moveFirst()
  RiferimentoRenderingHelper rh = (RiferimentoRenderingHelper)renderingHelperCollection.getAt()
   
  // header2
  int header2FieldIndex = Panel.findField("HEADER2")
  if (header2FieldIndex > -1)
  {
    if (rh.shouldHeader2BeVisible())
    {
      Panel.setFieldCaption(header2FieldIndex, rh.getHeader2Caption())
    }
    else 
    {
      Panel.setFieldVisible(header2FieldIndex, false)
    }
  }
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static IDCollection RiferimentoRenderingHelper.addIntoExistingCollection(
  IDCollection existingCollection of RiferimentoRenderingHelper // 
  RiferimentoRenderingHelper renderingHelper                    // 
)
{
  if (existingCollection == null)
  {
    existingCollection = new()
  }
   
  if (existingCollection.count() > 0)
  {
    existingCollection.moveFirst()
    RiferimentoRenderingHelper template = cast(existingCollection.getAt())
     
    if (template && template.CdataMapping)
    {
      renderingHelper.copyCdataMappingFromTemplate(template.CdataMapping)
    }
  }
   
  existingCollection.add(renderingHelper)
   
  return existingCollection
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static WkfresultCdataRenderingHelper WkfresultCdataRenderingHelper.Create(
  MainModule mainModule                    // 
  IDPanel panel                            // 
  optional int verticalGap = 30            // 
  optional int horizontalGap = 30          // 
  optional int MaxFieldLeftPosition = 1500 // 
)
{
  WkfresultCdataRenderingHelper ch = new()
  ch.setOwner(mainModule)
   
  mainModule.SetRenderingHelper(ch)
  ch.Panel = panel
   
  if (panel)
  {
    ch.LayoutType = panel.layout
    panel.setDocument(ch, ...)
  }
   
//  ch.CurrentLeftPosition = 0
//  ch.CurrentTopPosition = 0
  ch.CurrentGroupId = -1
   
  ch.HorizontalGap = horizontalGap
  ch.VerticalGap = verticalGap
  ch.MaxFieldsLeftPosition = MaxFieldLeftPosition
  ch.OverrideSupportedFieldsCountForUnitTest = 0
   
  return ch
}


// ──────────────────────────────────

// **********
//  
// **********
public void WkfresultCdataRenderingHelper.setModuleFieldValue(
  int index                                     // 
  int ModuleID                                  // 
  string ModuleDescription                      // 
  DatoPersonalizzatoInfo datoPersonalizzatoInfo // 
)
{
   
  // the rendering helper holds Description in the property
  this.setProperty(index, ModuleDescription)
   
  DatoPersonalizzatoInfo dpi = datoPersonalizzatoInfo
   
  IDDocument doc = base.getOwner()
  if (doc)
  {
    MainModule mm = cast(doc)
    mm.SetCustomData(toString(ModuleID), cast(dpi))
  }
}


// ──────────────────────────────────

// ****************************************************************
// small helper to compute description given the passed information
// ****************************************************************
private string WkfresultCdataRenderingHelper.computeDescription(
  int mainModuleMainId                                              // 
  MainModuleDatoPersonalizzatoInfo mainModuelDatoPersonalizzatoInfo // 
)
{
  // extarct mainmodule description
  string description = ""
  MainModule mainmodule = null
  int cdFieldmoduleMainID = mainModuleMainId
  boolean isContatto = false
  MainModuleDatoPersonalizzatoInfo mmcfi = mainModuelDatoPersonalizzatoInfo
  int:kordapp moduleKordApp = mmcfi.getRelatedKordApp(isContatto)
  if (moduleKordApp > 0 and cdFieldmoduleMainID > 0)
  {
    if (isContatto)
    {
      Contatto c = Contatto.get(cdFieldmoduleMainID)
      mainmodule = MainModule.retrieve(ClientiFornitori, c.IDCONTO, ...)
      description = formatMessage("|1 |2 (|3)", c.COGNOME, c.NOME, mainmodule.getDescription(), ...)
    }
    else 
    {
      mainmodule = MainModule.retrieve(moduleKordApp, cdFieldmoduleMainID, ...)
      if (mainmodule)
      {
         description = mainmodule.getDescription()
      }
    }
  }
  return description
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public IDMap WkfresultCdataRenderingHelper.getCdataLinkFromResult(
  string idResult // 
)
{
  int idResult = toInteger(idResult)
  WkfResult wr = new()
  wr.IDRESULT = idResult
  wr.loadFromDB(...)
  wr.LoadCdataWkfResult(idResult)
   
  IDMap resultCdata = new()
  for each CdataWkfResultLink cwrl in wr.CdataWkfResultLinks
  {
    IDDocument doc = new()
    doc.setTag("mandatory", cwrl.OBBLIGATORIO)
    doc.setTag("readonly", cwrl.ISREADONLY)
     
    resultCdata.setObject(toString(cwrl.IDCDATAFLD), doc)
  }
  return resultCdata
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string WkfresultCdataRenderingHelper.computePropertyValueForModuleType(
  DatoPersonalizzatoInfo cfi // 
  string currentCFIValue     // 
)
{
   
  int integerValue = toInteger(currentCFIValue)
   
  MainModuleDatoPersonalizzatoInfo mmdpi = (MainModuleDatoPersonalizzatoInfo)cfi
   
  // extract MainModule description
  string description = this.computeDescription(integerValue, mmdpi)
   
   
  return description
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public int WkfresultCdataRenderingHelper.getFieldPanelIndex(
  int fieldCount // 
)
{
  return fieldCount - 1
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void WkfresultCdataRenderingHelper.renderGroup(
  string sectionName // 
)
{
  if (!(Panel))
  {
    return 
  }
   
  string groupHeader = upper(trim(sectionName))
   
  int groupFlags = 0 | Visible | Enabled
   
  int groupIndex = Panel.addGroup(groupHeader, groupFlags, CustomDataStyle)
   
  Panel.setGroupCollapsable(groupIndex, true)
  Panel.setGroupHeaderSize(groupIndex, 0, 150)
   
  Panel.calcLayout()
   
  this.CurrentGroupId = groupIndex
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void WkfresultCdataRenderingHelper.prepareAndRender(
  boolean renderMandatoryOnly   // 
  optional int extraParams = 0  // 
  optional string idResult = "" // 
)
{
  FieldLimitExceededWhileRendering = false
  RenderedFieldsCount = 0
  IDMap resultCDataLinks = this.getCdataLinkFromResult(idResult)
   
  CurrentTopPosition = 0
  CurrentLeftPosition = 0
   
  MainModule mainModule = (MainModule)Owner
   
  IDCollection sections of CdataSection = null
   
  if (renderMandatoryOnly)
  {
    sections = mainModule.getMandatoryCustomdataSections()
  }
  else 
  {
    sections = mainModule.GetAvailableCustomDataSections(...)
  }
   
  int fieldCount = 1
   
  for each CdataSection cs in sections
  {
    if (cs.shouldSkipRendering())
    {
      continue 
    }
     
    this.renderGroup(cs.Name)
    CurrentLeftPosition = 0
     
    for each MainModuleDatoPersonalizzatoInfo currentCFI in cs.CDATAFIELDSINFO
    {
      if (currentCFI.Active == Yes)
      {
         IDDocument doc = (IDDocument)resultCDataLinks.getObject(toString(currentCFI.IDCDATAFLD))
          
         if (doc == null)
         {
           continue 
         }
         FieldLimitExceededWhileRendering = fieldCount > this.supportedFieldsCount()
         if (FieldLimitExceededWhileRendering)
           break 
          
         MainModuleDatoPersonalizzatoInfo cfi = cast(this.retrieveOwnerCdataFieldInfoAlreadyWithAddtionalProperties(currentCFI))
          
         if (cfi)
         {
           cfi.Mandatory = doc.getTag("mandatory")
           cfi.Readonly = doc.getTag("readonly")
            
           string value = mainModule.getDatoPersonalizzatoValueAsString(cfi)
           base.updateRenderingHelperProperty(cfi, fieldCount, value)
           fieldCount = fieldCount + 1
            
           RenderedFieldsCount = RenderedFieldsCount + 1
         }
      }
    }
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public int WkfresultCdataRenderingHelper.supportedFieldsCount()
{
  int supportedCustomDataFieldsCount = 250
  if (OverrideSupportedFieldsCountForUnitTest > 0)
  {
    supportedCustomDataFieldsCount = OverrideSupportedFieldsCountForUnitTest
  }
  return supportedCustomDataFieldsCount
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean WkfresultCdataRenderingHelper.ownerIsLocked()
{
  MainModule mm = cast(Owner)
  return mm.isLocked()
}


// ──────────────────────────────────

// ***************************************
// Carica la collection di cartelle figlie
// ***************************************
public void Cartella.CaricaCartelleFiglie()
{
  IDCollection coll of Cartella = this.getCollectionOfVisibileCartelle(QappCore.Loggeduser, this)
   
  DOCCARTELLE.addAll(coll, true)
   
   
  DOCCARTELLE.resetSortCriteria()
  DOCCARTELLE.addSortCriteria(toPropertyIndex(DESCRCARTELLA))
  DOCCARTELLE.doSort()
   
}


// ──────────────────────────────────

// *****************************************************************
// copy a cartella and it's children to desired destination cartella
// *****************************************************************
public Cartella Cartella.Copy(
  Cartella DestinationCartella // 
  int IdCreator                // 
)
{
  Idcreatorutente = IdCreator
  Cartella ModelloCartella = this
   
  // create cartella
  Cartella cartella = new()
  cartella.init()
  cartella.IDCARTELLA = Sequence.getNextSequence(DOCN_ID_CARTELLA, ...)
   
  if (DestinationCartella)
  {
    cartella.IDPADRE = DestinationCartella.IDCARTELLA
  }
  else 
  {
    cartella.IDPADRE = null
  }
   
   
  // assign values from modello
  cartella.CopyChildValues(IdCreator, ModelloCartella)
   
  // copy children cartella- do same as above for all children at all level using modello
  cartella.CopyChildren(ModelloCartella)
   
   
  return cartella
}


// ──────────────────────────────────

// ***********************************************************************
// debug method - print cartella ID and Descr of child in recursive manner
// ***********************************************************************
public void Cartella.PrintCartella()
{
  for each Cartella cartella in DOCCARTELLE
  {
    cartella.PrintCartella()
  }
}


// ──────────────────────────────────

// *************************************************************
// cut the cartella and assign IDPadre with NewFather
// basically it is equivalent to cut and paste at given cartella
// *************************************************************
public void Cartella.cut(
  Cartella newfather // 
)
{
  if (newfather)
  {
    IDPADRE = newfather.IDCARTELLA
  }
  else 
  {
    IDPADRE = null
  }
}


// ──────────────────────────────────

// ***********************************************************************************************************
// recursive method to set permissions to upper level cartella based on the just created/copied child cartella
// 
// ***********************************************************************************************************
public Cartella Cartella.SetCartellaPermissionofParent()
{
  Cartella RootNode = this
  if (!(isNull(IDPADRE)))
  {
    Cartella Padre = new()
    Padre.IDCARTELLA = IDPADRE
    try 
    {
      Padre.loadFromDB(0)
      for each DOCPERMESSI Childdocpermessi in DOCPERMESSI
      {
         boolean MatchFound = Padre.UserExistsInPermessi(Childdocpermessi.IDUTENTE)
          
         if (!(MatchFound))
         {
           DOCPERMESSI docpermessi = new()
           docpermessi.init()
           docpermessi.IDUTENTE = Childdocpermessi.IDUTENTE
           docpermessi.IDCARTELLA = Padre.IDCARTELLA
           docpermessi.LETTURA = Yes
           docpermessi.MODIFICA = No
           docpermessi.VERIFICA = No
           docpermessi.APPROVAZIONE = No
           docpermessi.DISTRIBUZIONE = No
           docpermessi.ADMIN = No
           docpermessi.REVISIONE = No
           docpermessi.INSERIMENTO = No
           docpermessi.saveToDB(...)
           RootNode = Padre
         }
      }
      if (!(isNull(RootNode.IDPADRE)))
      {
         RootNode = Padre.SetCartellaPermissionofParent()
      }
    }
  }
  return RootNode
}


// ──────────────────────────────────

// **********************************************************************************************************************************************************************
// This procedure adds a doc_documento into a Doc_Folder. 
// The way to do this is: 
// 1. Create a DoCDocumento 
// 2. Create the first Revision of DocDocumento (rev0). The one just created will have the docFile to be creaded inside the DocFiles collection.
// 3. Create the DocFile. It will contain the File Blob in the DOCUMENT property.
// 
// keepOriginalFile must be set to true to avoid deletion of the file (default is False for backwards compatibility, but it is a ODD value, by default it should be kept)
// **********************************************************************************************************************************************************************
public void Cartella.addDocumentoFromFile(
  string path                           // 
  string nameOfDocumento                // 
  string CodDocumento                   // 
  DOCTIPIDOCUMENTO documentType         // 
  optional boolean keepOriginalFile = 0 // 
)
{
  // 1. Create DocDocumento
  // PS: Instead of passing the this isntance, i pass the only property needed to do the operations
   
  int idCurrentCartella = IDCARTELLA
  Documento newDocumento = Documento.create(path, nameOfDocumento, CodDocumento, documentType, idCurrentCartella, keepOriginalFile)
   
  // 2. add the docDocumento into the Collection of DocDocumenti
  DOCDOCUMENTI.add(newDocumento)
}


// ──────────────────────────────────

// *******************************************
// this method makes sense only for QDOcs app!
// *******************************************
public static IDCollection Cartella.getCollectionOfVisibileCartelle(
  Utente user                     // 
  optional Cartella cartellaPadre // 
)
{
  DevTools.ToBeReviewed("this method was originally developed for Qdocs, in fact this pers1 call is for Qdocs. it should not be used and better use the other ones insead")
   
  boolean isPers1 = user.hasPers1OnCurrentQapp()
   
  boolean getFirstLevelFolders = !(cartellaPadre)
   
  int idCartellaPadre = if(getFirstLevelFolders, 0, cartellaPadre.IDCARTELLA)
   
  IDCollection visibileCartelle of Cartella = new()
  select into collection (visibileCartelle)
  from 
    Cartella // master table
  where
    (getFirstLevelFolders == false and IDPADRE == idCartellaPadre) or (getFirstLevelFolders == true and nullValue(IDPADRE, 0) == 0)
    isPers1 == true or IDCARTELLA in subquery
      select // 
         IDCARTELLA
      from 
         DOCPERMESSI // master table
      where
         DOCPERMESSI.LETTURA == Yes
         DOCPERMESSI.IDUTENTE == user.IDUTENTE
  order by
    DESCRCARTELLA
   
  return visibileCartelle
}


// ──────────────────────────────────

// ****************************************************************************************
// The full path cartella (such as \\Radice\Cartella 1\Cartella N) is computed and returned
// ****************************************************************************************
public string Cartella.getFullPathDescription()
{
  string slash = "\\"
  string fullPathDescription = DESCRCARTELLA + slash
  int idFatherCartella = IDPADRE
  while (idFatherCartella != null)
  {
    int vIdPadre = 0
    string vDESCRCARTELLA = ""
    select into variables (found variable)
      set vIdPadre = IDPADRE
      set vDESCRCARTELLA = DESCRCARTELLA
    from 
      DOCCARTELLE // master table
    where
      IDCARTELLA == idFatherCartella
     
    // compose
     
    fullPathDescription = SH.Concat(vDESCRCARTELLA, fullPathDescription, slash)
    idFatherCartella = vIdPadre
  }
  fullPathDescription = slash + slash + fullPathDescription
   
  return fullPathDescription
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static Cartella Cartella.get(
  int idCartella // 
)
{
  Cartella cartella = new()
  cartella.IDCARTELLA = idCartella
  try 
  {
    cartella.loadFromDB(...)
  }
  catch 
  {
    cartella = null
    QappCore.DTTLogMessage(formatMessage("Unable to load cartella for idCartella: |1", idCartella, ...), ..., DTTError)
  }
  return cartella
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static Cartella Cartella.create(
  string description               // 
  Utente Creator                   // 
  optional Cartella parentCartella // 
)
{
  Cartella cartella = new()
  cartella.init()
  cartella.DESCRCARTELLA = description
  if (parentCartella)
  {
    cartella.IDPADRE = parentCartella.IDCARTELLA
     
    cartella.CopyCartellaPermission(parentCartella)
  }
  else 
    cartella.IDPADRE = null
   
  cartella.IDCREATORE = Creator.IDUTENTE
  cartella.IDMODIFICATORE = Creator.IDUTENTE
   
  return cartella
}


// ──────────────────────────────────



// ──────────────────────────────────

// ************************************************************************************************************************************
// to avoid load DocPermessi and DocAutoDistribution in after load as non transient collections we have a method to load them directly.
// 
// By "real" collections we mean the ones based on FK, even if we marked them as transient (for optimizing speed)
// ************************************************************************************************************************************
public void Cartella.LoadRealCollections()
{
  this.loadCollectionFromDB(DOCPERMESSI, ...)
  this.loadCollectionFromDB(DOCAUTODISTRIBUTION, ...)
}


// ──────────────────────────────────

// **********************************************************************************************************
// cartella documents are exported in the output path, if outputPath is not provided a random one is assigned
// 
// it returns the OutputPath so it can be insepcted in case it was not passed
// 
// the inout parameters are needed to have the trackphase mechanism
// **********************************************************************************************************
public string Cartella.exportFiles(
  inout int trackphaseCount       // 
  inout int subfoldersCount       // 
  optional string outputPath = "" // 
)
{
  if (subfoldersCount == 0)
  {
    subfoldersCount = this.countAllSubfolders()
    QappCore.startPhase(subfoldersCount, "Esportazione Cartella", false)
  }
  else 
  {
    trackphaseCount = trackphaseCount + 1
    QappCore.trackPhase(trackphaseCount, subfoldersCount)
  }
   
  string actualOutputPath = ""
  if (outputPath == "")
  {
    actualOutputPath = this.computeTemporaryOutputPath()
  }
  else 
  {
    actualOutputPath = outputPath
  }
   
  string cleanedName = SH.RemoveBadFileNameChars(DESCRCARTELLA)
  string fullOutputPath = actualOutputPath + FH.getSeparator() + cleanedName
  QappCore.makeDirectory(actualOutputPath)
  QappCore.makeDirectory(fullOutputPath)
   
   
  if (!(DOCDOCUMENTI.loaded))
    this.loadCollectionFromDB(DOCDOCUMENTI, ...)
   
  for each Documento d in DOCDOCUMENTI
  {
    string errorMessage = ""
    boolean downloadDocument = false
    string documentFilePath = d.GetDownloadbleFilePath(errorMessage, downloadDocument, ...)
     
    // in case it is not possible to retrieve the blob we continue
    if (documentFilePath == "")
    {
      QappCore.DTTLogMessage("blob is corrupted, continue to next document", ..., DTTInfo)
      continue 
    }
     
    string fileName = SH.rightUpToDelimiter(documentFilePath, FH.getSeparator(), ...)
    if (ExportFilenameOnly)
    {
      fileName = SH.RemoveBadFileNameChars(d.DESCRDOCUMENTO) + d.EstensioneFile
    }
    string destinationPath = fullOutputPath + FH.getSeparator() + fileName
     
    QappCore.copyFile(documentFilePath, destinationPath)
  }
  if (ExportSubfolders)
  {
     
    this.CaricaCartelleFiglie()
     
    for each Cartella c in DOCCARTELLE
    {
       
      // assign the same export options to the subfolder:
      c.ExportSubfolders = ExportSubfolders
      c.ExportFilenameOnly = ExportFilenameOnly
      c.exportFiles(trackphaseCount, subfoldersCount, fullOutputPath)
    }
  }
  return actualOutputPath
}


// ──────────────────────────────────



// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public int Cartella.countAllSubfolders()
{
  string recurisveSubFoldersCountQuery = formatMessage("WITH foldersStructure (ID_CARTELLA) AS (SELECT ID_CARTELLA FROM DOC_CARTELLE WHERE ID_CARTELLA = |1 UNION ALL SELECT dc.ID_CARTELLA FROM DOC_CARTELLE dc 
           INNER JOIN foldersStructure c ON dc.ID_PADRE = c.ID_CARTELLA) SELECT COUNT(*) AS Totale FROM foldersStructure", IDCARTELLA, ...)
   
  Recordset r = QualibusDB.SQLQuery(recurisveSubFoldersCountQuery)
   
  r.moveFirst()
   
  int foldersCount = toInteger(r.getFieldValueIdx(1))
   
  QappCore.DTTLogMessage(formatMessage("Folders count for cartella |1: |2: ", IDCARTELLA, foldersCount, ...) + toString(foldersCount), ..., DTTInfo)
   
  return foldersCount
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public IDCollection Cartella.getUtentiWithPermessiFromPadre()
{
  IDCollection authorizedUsers of Utente = new()
  this.loadPermessi()
  IDCollection permessiCorrente of DOCPERMESSI = this.DOCPERMESSI
   
  if (this.IDPADRE != null)
  {
    Cartella padre = new()
    padre.get(this.IDPADRE)
    padre.IDCARTELLA = this.IDPADRE
     
    padre.loadPermessi()
    IDCollection permessiPadre of DOCPERMESSI = padre.DOCPERMESSI
    QappCore.DTTLogMessage(formatMessage("il numero dei permessi padre è |1", padre.DOCPERMESSI.count(), ...), ...)
     
    for each DOCPERMESSI docPermessiPadre in permessiPadre
    {
      boolean alreadyInPermessi = false
       
      for each DOCPERMESSI docpermessi in permessiCorrente
      {
         if (docPermessiPadre.IDUTENTE == docpermessi.IDUTENTE)
         {
           alreadyInPermessi = true
           break 
         }
          
      }
       
      if (!(alreadyInPermessi))
      {
         Utente u = new()
         u.IDUTENTE = docPermessiPadre.IDUTENTE
         authorizedUsers.add(u)
      }
    }
  }
   
  return authorizedUsers
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean Cartella.isLoggedUserFolderAdmin()
{
  this.loadPermessi()
  IDCollection permessi of DOCPERMESSI = this.DOCPERMESSI
   
  for each DOCPERMESSI p in permessi
  {
    if (p.IDUTENTE == QappCore.Loggeduser.IDUTENTE and p.ADMIN == Yes)
    {
      return true
    }
  }
  return false
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public IDCollection Cartella.GetAllSubfolders()
{
  IDCollection subdirs of Cartella = new()
  select into collection (subdirs)
    set IDCARTELLA = IDCARTELLA
    set DESCRCARTELLA = DESCRCARTELLA
    set IDPADRE = IDPADRE
    set DATACREAZIONE = DATACREAZIONE
    set IDCREATORE = IDCREATORE
    set DATAMODIFICA = DATAMODIFICA
    set IDMODIFICATORE = IDMODIFICATORE
    set PROFID = PROFID
    set IDTEMPLATE = IDTEMPLATE
    set TEMPLATE = TEMPLATE
    set IDPARENTTEMPLATEREMOTE = IDPARENTTEMPLATEREMOTE
  from 
    DOCCARTELLE // master table
  where
    IDPADRE == this.IDCARTELLA
   
  return subdirs
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public IDCollection Cartella.getRecursiveSubfolders()
{
  IDCollection results of Cartella = new()
  IDCollection toExploreDown of Cartella = this.GetAllSubfolders()
   
  QappCore.DTTLogMessage(formatMessage("Cartella base ID = |1 - Sottocartelle immediate: |2", this.IDCARTELLA, toExploreDown.count(), ...), ...)
   
  while (toExploreDown.count() > 0)
  {
    IDCollection newCollToExplore of Cartella = new()
     
    for each Cartella c in toExploreDown
    {
      results.add(c)
       
      Cartella fresh = this.get(c.IDCARTELLA)
      QappCore.DTTLogMessage(formatMessage("esploro sottocartelle per IDCARTELLA = |1", c.IDCARTELLA, ...), ...)
      IDCollection subdirs of Cartella = fresh.GetAllSubfolders()
      newCollToExplore.addAll(subdirs, false)
    }
     
    toExploreDown = newCollToExplore
  }
  return results
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Cartella.deletePermessiForCartella(
  int idUtente // 
)
{
//  QappCore.DTTLogMessage(formatMessage("sono entrato nella cartella |1", this.IDCARTELLA, ...), 666, DTTError)
  this.loadPermessi()
  IDCollection permessiToDelete of DOCPERMESSI = DOCPERMESSI
   
  for each DOCPERMESSI docpermessi in permessiToDelete
  {
    if (docpermessi.IDUTENTE == idUtente)
    {
      docpermessi.deleted = true
//      QappCore.DTTLogMessage(formatMessage("cancello i permessi per la cartella corrente:|1", this.IDCARTELLA, ...), ..., DTTError)
//      docpermessi.saveToDB(...)
      docpermessi.saveToDB(...)
    }
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Cartella.propagatePermessiOnSubfoldersForSpecificUser(
  int idUtente // 
)
{
  IDCollection currentPermessi of DOCPERMESSI = DOCPERMESSI
   
  DOCPERMESSI permUtente = null
   
  for each DOCPERMESSI dp in currentPermessi
  {
    if (dp.IDUTENTE == idUtente)
    {
      permUtente = dp
      break 
    }
  }
   
  if (permUtente == null)
  {
    return 
  }
   
  Utente utente = Utente.get(idUtente)
  IDCollection subdirs of Cartella = this.getRecursiveSubfolders()
//  QappCore.DTTLogMessage(formatMessage("il nomero delle cartelle della collection è |1", subdirs.count(), ...), 666, DTTError)
   
  for each Cartella c in subdirs
  {
     
    c.deletePermessiForCartella(idUtente)
     
     
    if (permUtente.LETTURA != No)
    {
      DOCPERMESSI newPermessi = DOCPERMESSI.create(utente, c)
      newPermessi.LETTURA = permUtente.LETTURA
      newPermessi.MODIFICA = permUtente.MODIFICA
      newPermessi.INSERIMENTO = permUtente.INSERIMENTO
      newPermessi.REVISIONE = permUtente.REVISIONE
      newPermessi.VERIFICA = permUtente.VERIFICA
      newPermessi.APPROVAZIONE = permUtente.APPROVAZIONE
      newPermessi.DISTRIBUZIONE = permUtente.DISTRIBUZIONE
      newPermessi.ADMIN = permUtente.ADMIN
       
      newPermessi.saveToDB(...)
    }
     
     
  }
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean Cartella.hasPermessiOnCartelleFiglie(
  int idUtente // 
)
{
  IDCollection subfolders of Cartella = this.getRecursiveSubfolders()
   
  for each Cartella c in subfolders
  {
    c.loadPermessi()
    IDCollection permessi of DOCPERMESSI = c.DOCPERMESSI
     
    for each DOCPERMESSI dp in permessi
    {
      if (dp.IDUTENTE == idUtente)
      {
         return true
      }
    }
  }
  return false
}


// ──────────────────────────────────

// ***************************************
// Handle the modified on Permessi changes
// ***************************************
public void Cartella.handlePermessiChanges(
  inout string errorMessage                                // Write a comment for this parameter or press backspace to delete this comment
  inout boolean stopOperation                              //  use to handle the "Cancel" parameter on Unload event
  optional string:saveToDBModes saveToDBMode = "doNotSave" // 
)
{
  IDCollection coll of DOCPERMESSI = DOCPERMESSI
   
  for each DOCPERMESSI dp in coll
  {
    if (dp.LETTURA == No)
    {
      Cartella currentFolder = this.get(dp.IDCARTELLA)
       
      if (dp.PropagaRuoli == false)
      {
         if (currentFolder.hasPermessiOnCartelleFiglie(dp.IDUTENTE))
         {
           errorMessage = "Attenzione! Per togliere la lettura su questa cartella toglierla prima sulle cartelle di livello inferiore"
           stopOperation = true
           return 
         }
      }
      if (dp.PropagaRuoli == true)
      {
         this.propagatePermessiOnSubfoldersForSpecificUser(dp.IDUTENTE)
      }
    }
     
    if (saveToDBMode == SaveToDB)
    {
      dp.saveToDB(...)
    }
     
    if (dp.PropagaRuoli == true)
    {
      this.propagatePermessiOnSubfoldersForSpecificUser(dp.IDUTENTE)
    }
  }
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Cartella.loadPermessi()
{
  DOCPERMESSI.loaded = false
  this.loadCollectionFromDB(DOCPERMESSI, 0)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean Cartella.HasDistributePrivilege(
  Utente utente // 
)
{
  boolean hasDistribuzionePermesso = false
   
  if (!(DOCPERMESSI.loaded))
  {
    this.loadPermessi()
  }
   
  for each DOCPERMESSI dp in DOCPERMESSI
  {
    if (dp.IDUTENTE == utente.IDUTENTE)
    {
      hasDistribuzionePermesso = dp.DISTRIBUZIONE == Yes
      break 
       
    }
  }
  return hasDistribuzionePermesso
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean Cartella.hasOnlyReadPrivilege(
  Utente utente // 
)
{
  boolean hasOnlyReadPermesso = false
   
  if (!(DOCPERMESSI.loaded))
  {
    this.loadPermessi()
  }
   
  for each DOCPERMESSI dp in DOCPERMESSI
  {
    if (dp.IDUTENTE == utente.IDUTENTE)
    {
      boolean hasReadPriv = dp.LETTURA == Yes
      boolean hasModificaPriv = dp.MODIFICA == Yes
      boolean hasVerificaPriv = dp.VERIFICA == Yes
      boolean hasApprovazionePriv = dp.APPROVAZIONE == Yes
      boolean hasDistribuzionePriv = dp.DISTRIBUZIONE == Yes
      boolean hasRevisionePriv = dp.REVISIONE == Yes
      boolean hasInsermentoPriv = dp.INSERIMENTO == Yes
      boolean IsAdministrator = utente.hasSpecificPrivilegeForKordApp(Documenti, Pers1)
       
      // we want to check that logged-in user must have only LETTURA privilage
      hasOnlyReadPermesso = hasReadPriv and !(hasModificaPriv) and !(hasVerificaPriv) and !(hasApprovazionePriv) and !(hasDistribuzionePriv) and !(hasRevisionePriv) and !(hasInsermentoPriv) and !(
                IsAdministrator)
      break 
       
    }
  }
  return hasOnlyReadPermesso
}


// ──────────────────────────────────

// ****************************************************************
// copy Verifica of documents revision in the cartella from modello
// ****************************************************************
private void Cartella.CopyVerifica(
  Revision Rev        // 
  Revision ModelloRev // 
)
{
  if (not(ModelloRev.DOCVERIFICHE.loaded))
    this.loadCollectionFromDB(ModelloRev.DOCVERIFICHE, ...)
   
  for each DOCVERIFICHE ModelloDocVer in ModelloRev.DOCVERIFICHE
  {
    DOCVERIFICHE docver = new()
    docver = docver.CreateVerificaFromModello(Idcreatorutente, Rev.IDREVISIONE, ModelloDocVer)
    Rev.DOCVERIFICHE.add(docver)
  }
}


// ──────────────────────────────────

// ********************************************************
// copy Revisions of documents in the cartella from modello
// ********************************************************
private void Cartella.CopyRevision(
  Documento Doc        // 
  Documento ModelloDoc // 
)
{
   
  Revision modelloRev = ModelloDoc.GetLastRevisione(...)
   
  Revision DocRev = new()
  DocRev = DocRev.CreateRevisionFromMedello(Idcreatorutente, Doc.IDDOCUMENTO, modelloRev)
  this.CopyVerifica(DocRev, modelloRev)
}


// ──────────────────────────────────

// **************************************
// copy document of cartella from modello
// **************************************
private void Cartella.CopyDocuments(
  Cartella Modello // 
)
{
  if (!(Modello.DOCDOCUMENTI.loaded))
  {
    IDCollection modellodocs of Documento = new()
    select into collection (modellodocs)
    from 
      Documento // master table
    where
      IDCARTELLA = Modello.IDCARTELLA
    Modello.DOCDOCUMENTI = modellodocs
  }
   
  for each Documento ModelloDoc in Modello.DOCDOCUMENTI
  {
    Documento Doc = new()
    Doc = Doc.CreateDocumentfromModello(Idcreatorutente, IDCARTELLA, ModelloDoc)
    this.CopyRevision(Doc, ModelloDoc)
    DOCDOCUMENTI.add(Doc)
  }
}


// ──────────────────────────────────

// *************************************************
// copy DocAutoDistribution of cartella from modello
// *************************************************
private void Cartella.CopyDocAutoDistributions(
  Cartella Modello // 
)
{
  if (!(Modello.DOCAUTODISTRIBUTION.loaded))
  {
    Modello.loadCollectionFromDB(Modello.DOCAUTODISTRIBUTION, ...)
  }
  for each DOCAUTODISTRIBUTION docautodistribution in Modello.DOCAUTODISTRIBUTION
  {
    DOCAUTODISTRIBUTION docAutoDistrib = new()
    docAutoDistrib.init()
    docAutoDistrib.IDCARTELLA = IDCARTELLA
    docAutoDistrib.IDLISTADISTRIB = docautodistribution.IDLISTADISTRIB
    docAutoDistrib.ATTIVA = docautodistribution.ATTIVA
    docAutoDistrib.PROPAGATETOCHILDREN = docautodistribution.PROPAGATETOCHILDREN
    docAutoDistrib.NOTES = docautodistribution.NOTES
    docAutoDistrib.SENDMESSAGE = docautodistribution.SENDMESSAGE
    DOCAUTODISTRIBUTION.add(docAutoDistrib)
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private boolean Cartella.UserExistsInPermessi(
  int IdUser // 
)
{
  boolean MatchFound = false
  if (!(DOCPERMESSI.loaded))
    this.loadCollectionFromDB(DOCPERMESSI, 0)
  for each DOCPERMESSI docpermessi in DOCPERMESSI
  {
    if (docpermessi.IDUTENTE == IdUser)
    {
      MatchFound = true
      break 
    }
  }
  return MatchFound
}


// ──────────────────────────────────

// ********************************************
// copy doc permission from modello to cartella
// ********************************************
private void Cartella.CopyCartellaPermission(
  Cartella Modello // 
)
{
   
  if (!(Modello.DOCPERMESSI.loaded))
    Modello.loadCollectionFromDB(Modello.DOCPERMESSI, ...)
   
  for each DOCPERMESSI Modellodocpermessi in Modello.DOCPERMESSI
  {
    DOCPERMESSI docpermessi = new()
    docpermessi.init()
    docpermessi.IDUTENTE = Modellodocpermessi.IDUTENTE
    docpermessi.IDCARTELLA = IDCARTELLA
    docpermessi.LETTURA = Modellodocpermessi.LETTURA
    docpermessi.MODIFICA = Modellodocpermessi.MODIFICA
    docpermessi.VERIFICA = Modellodocpermessi.VERIFICA
    docpermessi.APPROVAZIONE = Modellodocpermessi.APPROVAZIONE
    docpermessi.DISTRIBUZIONE = Modellodocpermessi.DISTRIBUZIONE
    docpermessi.REVISIONE = Modellodocpermessi.REVISIONE
    docpermessi.INSERIMENTO = Modellodocpermessi.INSERIMENTO
    docpermessi.ADMIN = Modellodocpermessi.ADMIN
    DOCPERMESSI.add(docpermessi)
  }
}


// ──────────────────────────────────

// **********************************************************************************************
// create new children and assign values with corresponding children of modello and assign parent
// this is a recursive method 
// **********************************************************************************************
private void Cartella.CopyChildren(
  Cartella Modello // 
)
{
  Modello.CaricaCartelleFiglie()
  for each Cartella cartella in Modello.DOCCARTELLE
  {
    Cartella ChildCartella = new()
    ChildCartella.init()
    ChildCartella.IDCARTELLA = Sequence.getNextSequence(DOCN_ID_CARTELLA, ...)
    ChildCartella.IDPADRE = IDCARTELLA
    ChildCartella.CopyChildValues(Idcreatorutente, cartella)
    DOCCARTELLE.add(ChildCartella)
    ChildCartella.CopyChildren(cartella)
  }
}


// ──────────────────────────────────

// *************************************
// copy cartella properties from modello
// *************************************
private void Cartella.CopyChildValues(
  int IdCreator    // 
  Cartella Modello // 
)
{
  Idcreatorutente = IdCreator
  DESCRCARTELLA = Modello.DESCRCARTELLA
  DATACREAZIONE = now()
  IDCREATORE = Idcreatorutente
  PROFID = 0
   
  // Assign permissions to cartella same as modello
  this.CopyCartellaPermission(Modello)
   
  // Assign DocAutoDistributions to cartella same as modello
  this.CopyDocAutoDistributions(Modello)
   
  // copy documents (docs,revisioni, verifiche and docfiles) from modello
  this.CopyDocuments(Modello)
   
}


// ──────────────────────────────────

// ******************************************************************
// Event raised to the document to determine how to call the document
// ******************************************************************
event Cartella.OnGetName(
  inout string Name // A string input/output parameter. It initially contains the name that the framework has calculated for the document, and can be set to name the document as desired.
  int NameType      // This integer parameter specifies what "type" of name is requested. The framework may require different types of names; see the notes for more information.
  IDCollection CallerCollection of IDDocument // The collection containing the document. It may be different from ParentCollection if the document is contained by reference.
  int CallerForm    // The form containing the visual object for which the event was raised. Use the Me function of the Form object to make comparisons. It can be ZERO to not specify a form in particular.
  int CallerFrame   // The visual object for which the event is raised. Use the Me function to make comparisons. It can be ZERO to not specify a particular visual object.
)
{
  Name = DESCRCARTELLA
}


// ──────────────────────────────────

// *********************************************************************************************************
// Event raised to the document when a tree wants to determine if it has or may have subdocuments to display
// *********************************************************************************************************
event Cartella.OnMayHaveChildren(
  inout boolean HasChildren // A boolean input/output parameter. The initial value of this parameter is the result of the automatic calculation. It can be modified to return a different result: if set to Tru...
)
{
  IDCollection subCartelle of Cartella = this.getCollectionOfVisibileCartelle(QappCore.Loggeduser, this)
   
  HasChildren = subCartelle.count() > 0
}


// ──────────────────────────────────

// ********************************************************************
// Event raised to the document before loading a collection of its type
// ********************************************************************
event Cartella.BeforeLoadCollection(
  IDDocument Parent                     // The parent document that requested loading of the collection. It can be Null Object if the event is raised by the LoadCollectionByExample procedure.
  IDCollection Collection of IDDocument // The collection to be loaded.
  inout boolean Skip                    // A boolean output parameter. If set to True, standard loading will not be performed.
)
{
}


// ──────────────────────────────────

// ************************************************************
// Event raised by the document after loading from the database
// ************************************************************
event Cartella.AfterLoad(
  boolean AlreadyLoaded     // A boolean parameter that specifies whether the document was previously loaded.
  IDArray LoadedCollections // IDArray type parameter that contains the collections loaded by the framework. If the value of the Already Loaded parameter is False, the Loaded Collections parameter is NULL.
)
{
//  if (!(DOCDOCUMENTI.loaded))
//  {
//    this.loadCollectionFromDB(DOCDOCUMENTI, ...)
//  }
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event Cartella.OnInit()
{
  IDCARTELLA = Sequence.getNextSequence(DOCN_ID_CARTELLA, ...)
  PROFID = 0
  DATACREAZIONE = now()
  DATAMODIFICA = now()
}


// ──────────────────────────────────

// ******************************************************************
// Event raised to the document to determine how to call the document
// ******************************************************************
event Documento.OnGetName(
  inout string Name // A string input/output parameter. It initially contains the name that the framework has calculated for the document, and can be set to name the document as desired.
  int NameType      // This integer parameter specifies what "type" of name is requested. The framework may require different types of names; see the notes for more information.
  IDCollection CallerCollection of IDDocument // The collection containing the document. It may be different from ParentCollection if the document is contained by reference.
  int CallerForm    // The form containing the visual object for which the event was raised. Use the Me function of the Form object to make comparisons. It can be ZERO to not specify a form in particular.
  int CallerFrame   // The visual object for which the event is raised. Use the Me function to make comparisons. It can be ZERO to not specify a particular visual object.
)
{
  Name = CODDOCUMENTO + " - " + DESCRDOCUMENTO
   
}


// ──────────────────────────────────



// ──────────────────────────────────

// ************************************************************
// Event raised by the document after loading from the database
// ************************************************************
event Documento.AfterLoad(
  boolean AlreadyLoaded     // A boolean parameter that specifies whether the document was previously loaded.
  IDArray LoadedCollections // IDArray type parameter that contains the collections loaded by the framework. If the value of the Already Loaded parameter is False, the Loaded Collections parameter is NULL.
)
{
  base.AfterLoad(AlreadyLoaded, LoadedCollections)
   
  boolean IsQuickLoadRequested = base.quickLoadRequested()
   
  if (IsQuickLoadRequested)
  {
    return 
  }
   
  Revision foundrev = null
  string estfilealt = ""
  string estfile = ""
  date dataapprovazione = #1899/12/30#
   
  if (!(AlreadyLoaded))
  {
     
    foundrev = this.GetLastRevisione(true)
    if (foundrev != null)
    {
      estfile = foundrev.ESTENSIONEFILE
      estfilealt = foundrev.ESTENSIONEFILEALT
      dataapprovazione = foundrev.DATAAPPROVAZIONE
      LastNroRevsion = foundrev.NROREVISIONE
      DataApprovazione = dataapprovazione
      LastRevision = foundrev
    }
     
    if (estfilealt != null)
    {
      EstensioneFile = estfilealt
    }
    else 
    {
      EstensioneFile = estfile
    }
  }
   
  // SetDisplayIcon requires estensionefile to be set so it must be called after the code above
  this.SetDisplayIcon()
//  if (!(DOCCOLLEGATI.loaded))
//  {
//    this.loadCollectionFromDB(DOCCOLLEGATI, ...)
//  }
  this.computeAdditionalProperties()
  this.setOriginal()
}


// ──────────────────────────────────

// ****************************************************************************
// Event raised to the document to determine the definition of a named property
// ****************************************************************************
event Documento.OnGetNamedPropertyDefinition(
  string PropertyName                     // The name of the named property whose definition is sought.
  IDPropertyDefinition PropertyDefinition // The object of the IDPropertyDefinition type that will be used to communicate the property definition to the caller.
)
{
  switch (PropertyName)
  {
    case "PERCORSO_COMPLETO":
      PropertyDefinition.dataType = Character
    break
    case "LinksIcon":
      PropertyDefinition.dataType = Character
    break
    case "DownloadIcon":
      PropertyDefinition.dataType = Character
    break
  }
}


// ──────────────────────────────────

// ***********************************************************************
// Event raised to the document to determine the value of a named property
// ***********************************************************************
event Documento.OnGetNamedPropertyValue(
  string PropertyName        // The name of the named property whose value to retrieve.
  inout string PropertyValue // An output parameter containing the value of the named property.
)
{
  if (PropertyName == "PERCORSO_COMPLETO")
  {
    string query = formatMessage("select |1, dbo.sf_CreaPathCartella(|1) as PercorsoCompleto from DOC_CARTELLE", IDCARTELLA, ...)
    QualibusDB.maxRows = 1
    Recordset r = null
    r = QualibusDB.SQLQuery(query)
     
    if (r.recordCount() == 1)
    {
      r.moveFirst()
      string percorsoCompleto = r.getFieldValue("PercorsoCompleto")
      PropertyValue = percorsoCompleto
    }
    else 
    {
      PropertyValue = ""
    }
  }
   
  if (PropertyName == "LinksIcon")
  {
    int linksCount = this.GetTotalLinksCount()
     
    // the tag is set on the document, so the UI can easily know whether there are links or not without rerunning queries
    this.setTag("linksCount", linksCount)
     
    if (linksCount > 0)
    {
      PropertyValue = "{{icon-fa-external-link-square}}"
    }
    else 
    {
      PropertyValue = "{{icon-fa-minus-square}}"
    }
  }
   
  if (PropertyName == "DownloadIcon")
  {
    boolean docIsDownloadable = this.isDownloadable()
    if (docIsDownloadable)
    {
      PropertyValue = "{{icon-fa-download}}"
    }
    else 
    {
      PropertyValue = "{{icon-fa-ban}}"
    }
  }
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event Documento.OnInit()
{
  IDDOCUMENTO = Sequence.getNextSequence(DOCN_ID_DOCUMENTO, ...)
  IDUTENTEINS = QappCore.Loggeduser.IDUTENTE
  STATO = Attivo
  MODELLO = No
  NOTE = " "
  DATAINSERIMENTO = today()
//  DataApprovazione = today()
}


// ──────────────────────────────────



// ──────────────────────────────────

// **********************************************************************************************
// returns the extension (with dot like ".ext") of the downloadable file (last approved revision)
// **********************************************************************************************
public string Documento.GetDonwloadableFileExtension(
  optional boolean referBaseIdDocFile = 0 // 
)
{
  Revision Revision = this.GetLastRevisione(...)
  string EstensioneFile = ""
  if (Revision == null)
  {
    return ""
  }
   
  // file extensions are based on idDocFile or IdFileAlt and they both can exists in case of public file, if referBaseIdDocFile parameter is true we always return extension based on IdDocFile otherwise we give
  // priority to extension of idFileAlt (if exists)
  if (referBaseIdDocFile)
  {
    EstensioneFile = Revision.ESTENSIONEFILE
  }
  else 
  {
    if (Revision.IDFILEALT != null)
    {
      EstensioneFile = Revision.ESTENSIONEFILEALT
    }
    else 
    {
      EstensioneFile = Revision.ESTENSIONEFILE
    }
  }
   
   
   
  // better to make extension lowercase to avoid odd cases like ".PDF" that does not work in java
  string lowercaseFileExtension = lower(EstensioneFile)
   
  return lowercaseFileExtension
}


// ──────────────────────────────────

// *******************************************************************************************
// Returns the calcualted File Base Name (filename without extension) of the donwloadable file
// *******************************************************************************************
public string Documento.GetDownloadableFileBaseName()
{
   
  string RevisionNumber = ""
  Revision Revision = this.GetLastRevisione(...)
  string FileBaseName = ""
  try 
  {
    RevisionNumber = format(Revision.NROREVISIONE, "00", ...)
  }
  catch 
  {
    RevisionNumber = ""
  }
   
  if (CODDOCUMENTO = "" or isNull(CODDOCUMENTO))
  {
    FileBaseName = "__"
  }
  else 
  {
    FileBaseName = CODDOCUMENTO
  }
  // 
  if (!(DESCRDOCUMENTO = "" or isNull(DESCRDOCUMENTO)))
  {
    FileBaseName = Tools.Concatenate(FileBaseName, DESCRDOCUMENTO, ...)
  }
   
  // left 60 because too long URLs arenot ok in tomcat, removebadfilechars to make
  // a valid URL
  string DescriptionWithoutBadChars = left(Tools.RemoveBadFileNameChars(FileBaseName), 60)
  FileBaseName = DescriptionWithoutBadChars + " - Rev. " + RevisionNumber
   
  return FileBaseName
}


// ──────────────────────────────────

// ************************************************
// Returns last appproved revision for the document
// ************************************************
public Revision Documento.GetLastRevisione(
  optional boolean skipAfterLoad = 0 // 
)
{
  if (skipAfterLoad)
  {
    QualibusDB.maxRows = 1
    int idLastRevisione = 0
     
    select into variables (found variable)
      set idLastRevisione = IDREVISIONE
    from 
      Revisions // master table
    where
      IDDOCUMENTO = IDDOCUMENTO
      !(isNull(DATAAPPROVAZIONE))
    order by
      NROREVISIONE
     
    Revision lastRevision = new()
    if (idLastRevisione > 0)
    {
      lastRevision.IDREVISIONE = idLastRevisione
      lastRevision.setTag("skipAfterLoad", true)
      lastRevision.loadFromDB(0)
    }
     
    return lastRevision
  }
   
  IDCollection ApprovedRevsions of Revision = new()
  ApprovedRevsions.maxRows = 1
  select into collection (ApprovedRevsions)
  from 
    Revision // master table
  where
    !(isNull(DATAAPPROVAZIONE))
    IDDOCUMENTO = IDDOCUMENTO
  order by
    NROREVISIONE
   
   
  // since the collection master query is ordered by nro revisione descending
  // we know that the first item is the last approved revisione
  // since collection does not have getObject(i) method this for each trick
  // is used
   
  Revision Revision = null
   
  for each Revision r in ApprovedRevsions
  {
    Revision = r
    break 
  }
   
  return Revision
}


// ──────────────────────────────────

// *************************************
// Create a document object from modello
// *************************************
public Documento Documento.CreateDocumentfromModello(
  int IdUtente         // 
  int IdCartella       // 
  Documento ModelloDoc // 
)
{
  Documento Doc = new()
  Doc.init()
  Doc.IDDOCUMENTO = Sequence.getNextSequence(DOCN_ID_DOCUMENTO, ...)
  Doc.IDCARTELLA = IdCartella
  Doc.IDTIPODOCUMENTO = ModelloDoc.IDTIPODOCUMENTO
  Doc.STATO = ModelloDoc.STATO
  Doc.CODDOCUMENTO = ModelloDoc.CODDOCUMENTO
  Doc.DESCRDOCUMENTO = ModelloDoc.DESCRDOCUMENTO
  Doc.NOTE = ModelloDoc.NOTE
  Doc.IDUTENTEINS = IdUtente
  Doc.DATAINSERIMENTO = now()
   
  DevTools.ToBeReviewed("Following two fields- should we copy it or not?")
  Doc.IDUTENTEOBS = ModelloDoc.IDUTENTEOBS
  Doc.DATAOBSOLETO = ModelloDoc.DATAOBSOLETO
  DevTools.ToBeReviewed("Should we copy tempaltes field?")
  Doc.IDTEMPLATE = ModelloDoc.IDTEMPLATE
  Doc.TEMPLATE = ModelloDoc.TEMPLATE
  Doc.IDPARENTTEMPLATEREMOTE = ModelloDoc.IDPARENTTEMPLATEREMOTE
   
   
  return Doc
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public DocFile Documento.GetDOCFILE()
{
  DocFile docfiles = new()
   
  // Cerco la chiave di DOC FILES
  int key = 0
  try 
  {
    docfiles.IDDOCFILE = key
    docfiles.loadFromDB(...)
  }
  catch 
  {
    docfiles = null
  }
   
  return docfiles
}


// ──────────────────────────────────

// ****************************************************************************************************************
// create a new Link document for given document and Idrevision and add in to collection of Documenti doc collegati
// ****************************************************************************************************************
public void Documento.AddDocCollegati(
  Documento Document // 
)
{
  DOCCOLLEGATI dd = new()
  dd.init()
  dd.IDDOC = IDDOCUMENTO
  dd.IDDOCUMENTO = Document.IDDOCUMENTO
  dd.inserted = true
   
  DOCCOLLEGATI.add(dd)
}


// ──────────────────────────────────

// **********************************************************************************************************
// Check if Document link already exists in collegati collection
// this method is require to validate/check when we create Document link to prevent Duplicate error on saving
// **********************************************************************************************************
public boolean Documento.DocumentLinkAlreadyExists(
  Documento document // 
)
{
  if (!(DOCCOLLEGATI.loaded))
    this.loadCollectionFromDB(DOCCOLLEGATI, ...)
   
  boolean sameDocumentLinkAlreadyExists = false
  for each DOCCOLLEGATI doccollegati in DOCCOLLEGATI
  {
    if (doccollegati.IDDOCUMENTO == document.IDDOCUMENTO)
    {
      sameDocumentLinkAlreadyExists = true
      break 
    }
  }
  return sameDocumentLinkAlreadyExists
}


// ──────────────────────────────────

// ******************************************************************************************
// returns a collection of the documents distributed to the user passed as parameter
// it also calculates ToBeRead property of docDocumento so that it can be dispayed in a panel
// ******************************************************************************************
public static IDCollection Documento.getDocumentsToBeReadByUser(
  Utente utente // 
)
{
  // QUICK AND DIRTY METHOD DONE BY COPYING CODE FROM QUALIBUS C/S CODE IN TfrmDocMain.actDocDistribAMeExecute 
   
  date nullDate = #1899/12/30#
   
  IDCollection documentsDistributedToUser of Documento = new()
  select into collection (documentsDistributedToUser)
    set IDDOCUMENTO = Documenti.IDDOCUMENTO
    set IDCARTELLA = Documenti.IDCARTELLA
    set IDTIPODOCUMENTO = Documenti.IDTIPODOCUMENTO
    set STATO = Documenti.STATO
    set CODDOCUMENTO = Documenti.CODDOCUMENTO
    set DESCRDOCUMENTO = Documenti.DESCRDOCUMENTO
    set MODELLO = Documenti.MODELLO
    set NOTE = Documenti.NOTE
    set IDUTENTEINS = Documenti.IDUTENTEINS
    set DATAINSERIMENTO = Documenti.DATAINSERIMENTO
    set IDUTENTEOBS = Documenti.IDUTENTEOBS
    set DATAOBSOLETO = Documenti.DATAOBSOLETO
    set IDTEMPLATE = Documenti.IDTEMPLATE
    set TEMPLATE = Documenti.TEMPLATE
    set IDPARENTTEMPLATEREMOTE = Documenti.IDPARENTTEMPLATEREMOTE
  from 
    Documenti         // master table
    DOCTIPIDOCUMENTO  // joined with Documenti using key FK_DOC_DOCUMENTI02
    Revisions         // joined with Documenti using key FK_DOC_REVISIONI01
    Distributions     // joined with Revisions using key FK_DOC_DISTRIBUZIONE02
    Utenti            // manually joined, see where clauses
    DistribuzioneRecipients // joined with Distributions using key FK_DOC_LISTE_DISTRIBUZIONE01
    VUTENTIDIPENDENTI // manually joined, see where clauses
    VLASTDISTRIBUTION // manually joined, see where clauses
  where
//    not(exists(subquery))
//      select top 1 // 
//         IDFILE
//      from 
//         SW9DOCUMENTLOG // master table
//      where
//         SW9DOCUMENTLOG.IDFILE == nullValue(nullValue(Revisions.IDFILEALT, Revisions.IDDOCTAGS), Revisions.IDDOCFILE)
//       
//         SW9DOCUMENTLOG.IDUTENTE == utente.IDUTENTE
    Utenti.IDUTENTE == Distributions.IDUTENTEDISTRIBUTORE*
    VUTENTIDIPENDENTI.IDDIPENDENTE == DistribuzioneRecipients.IDDIPENDENTE
    VLASTDISTRIBUTION.IDLISTA == DistribuzioneRecipients.IDLISTA
    VUTENTIDIPENDENTI.IDUTENTE == utente.IDUTENTE
    nullValue(Revisions.DATAAPPROVAZIONE, nullDate) != nullDate
    Documenti.STATO == "A"
    nullValue(Revisions.DATACONSERVAZIONE, today()) == today()
    nullValue(Revisions.DATAVALIDITA, today()) >= today()
   
  IDCollection documentsToBeReadByUser of Documento = new()
  select into collection (documentsToBeReadByUser)
    set IDDOCUMENTO = Documenti.IDDOCUMENTO
    set IDCARTELLA = Documenti.IDCARTELLA
    set IDTIPODOCUMENTO = Documenti.IDTIPODOCUMENTO
    set STATO = Documenti.STATO
    set CODDOCUMENTO = Documenti.CODDOCUMENTO
    set DESCRDOCUMENTO = Documenti.DESCRDOCUMENTO
    set MODELLO = Documenti.MODELLO
    set NOTE = Documenti.NOTE
    set IDUTENTEINS = Documenti.IDUTENTEINS
    set DATAINSERIMENTO = Documenti.DATAINSERIMENTO
    set IDUTENTEOBS = Documenti.IDUTENTEOBS
    set DATAOBSOLETO = Documenti.DATAOBSOLETO
    set IDTEMPLATE = Documenti.IDTEMPLATE
    set TEMPLATE = Documenti.TEMPLATE
    set IDPARENTTEMPLATEREMOTE = Documenti.IDPARENTTEMPLATEREMOTE
  from 
    Documenti         // master table
    DOCTIPIDOCUMENTO  // joined with Documenti using key FK_DOC_DOCUMENTI02
    Revisions         // joined with Documenti using key FK_DOC_REVISIONI01
    Distributions     // joined with Revisions using key FK_DOC_DISTRIBUZIONE02
    Utenti            // manually joined, see where clauses
    DistribuzioneRecipients // joined with Distributions using key FK_DOC_LISTE_DISTRIBUZIONE01
    VUTENTIDIPENDENTI // manually joined, see where clauses
    VLASTDISTRIBUTION // manually joined, see where clauses
  where
    not(exists(subquery))
      select top 1 // 
         IDFILE
      from 
         SW9DOCUMENTLOG // master table
      where
         SW9DOCUMENTLOG.IDFILE == nullValue(nullValue(Revisions.IDFILEALT, Revisions.IDDOCTAGS), Revisions.IDDOCFILE)
//       
         SW9DOCUMENTLOG.IDUTENTE == utente.IDUTENTE
    Utenti.IDUTENTE == Distributions.IDUTENTEDISTRIBUTORE*
    VUTENTIDIPENDENTI.IDDIPENDENTE == DistribuzioneRecipients.IDDIPENDENTE
    VLASTDISTRIBUTION.IDLISTA == DistribuzioneRecipients.IDLISTA
    VUTENTIDIPENDENTI.IDUTENTE == utente.IDUTENTE
    nullValue(Revisions.DATAAPPROVAZIONE, nullDate) != nullDate
    Documenti.STATO == "A"
    nullValue(Revisions.DATACONSERVAZIONE, today()) == today()
    nullValue(Revisions.DATAVALIDITA, today()) >= today()
   
  for each Documento ddu in documentsDistributedToUser
  {
    boolean matchFound = false
    for each Documento dru in documentsToBeReadByUser
    {
      if (ddu.IDDOCUMENTO == dru.IDDOCUMENTO)
      {
         matchFound = true
         break 
      }
    }
    ddu.Read = !(matchFound)
    ddu.setOriginal()
//    if (ddu.??)
//    {
//      QappCore.DTTLogMessage(formatMessage("TO BE READ |1", ddu.DESCRDOCUMENTO, ...), 888, ...)
//       
//    }
//    else 
//    {
//      QappCore.DTTLogMessage(formatMessage("ALREADY READ |1", ddu.DESCRDOCUMENTO, ...), 777, ...)
//    }
  }
   
  return documentsDistributedToUser
}


// ──────────────────────────────────

// *******************************************************************************
// Extracts the DocDocumento's File to a local path (returned as inout parameter!)
// 
// in case returns an error code (from the value list downloadfileerrorlist)
// 
// this method is private and it is intended to be called by DownloadDocument
// *******************************************************************************
private int Documento.ExtractDocumentToFile(
  inout string Filepath                   // 
  optional boolean referBaseIdDocFile = 0 // 
)
{
  Filepath = ""
  int:downloadFileErrorTypes DownloadError = NoError
  string FileExtension = ""
  string FileBaseName = ""
  Revision Revision = null
   
  if (this == null)
  {
    return ErrorInvalidDocument
  }
  Revision = this.GetLastRevisione(...)
   
  if (Revision == null)
  {
    return ErrorNoAnyValidRevision
  }
  FileExtension = this.GetDonwloadableFileExtension(referBaseIdDocFile)
  FileBaseName = this.GetDownloadableFileBaseName()
   
  if (isNull(FileExtension))
  {
    return ErrorFileLessDocument
  }
   
  DocFile docfiles = new()
  docfiles.IDDOCFILE = this.GetDonwloadableFileIdDocFile(referBaseIdDocFile)
  try 
  {
    docfiles.loadFromDB(...)
  }
  catch 
  {
    // 
    return ErrorCorruptedBlob
  }
   
  string DownloadableFileName = ""
  DownloadableFileName = docfiles.GetPathOfDownloadableDOCFILE(FileExtension, FileBaseName, DownloadError)
  if (DownloadError = NoError)
  {
    // 
    Filepath = DownloadableFileName
     
  }
  return DownloadError
  // 
   
   
   
   
   
   
   
   
   
}


// ──────────────────────────────────

// ***************************************************************************************
// inserts a record in SW9_DOCUMENT_LOG to save the fact that the document has been opened
// ***************************************************************************************
public void Documento.WriteDocumentLog()
{
  int idDocFile = this.GetDonwloadableFileIdDocFile(...)
  // 
   
  // if no error occurs we mark the document as read by running insert query
  // on the database
  insert values into SW9DOCUMENTLOG (last value variable)
    set IDFILE = idDocFile
    set IDUTENTE = QappCore.Loggeduser.IDUTENTE
    set IDORIGINE = 0
    set PGMNAME = QappCore.QappWeb.Name
    set WINUSER = QappCore.Tbluserparameters.Winuser
    set DBUSER = QappCore.Loggeduser.Username
    set COMPUTERNAME = QappCore.Tbluserparameters.Computername
    set ACCESSDATE = now()
    set ACCESSOP = "-1"
}


// ──────────────────────────────────

// ****************************************************************************************************************************************************************************************************************
// returns the filepath that can be used to download the document last approved revision file with openDocument
// 
// in the inout parameter the error is stored, it is meant to be displayed in a User Interface
// 
// Note: currently only DocDocumento last revision is supported
// 
// likely it should be expanded so to handle:
// 1) Doc Coumnicazioni
// 2) A specific Doc Revisione
// 
// the method also opens the document if openDocument is true.
// (NOTE: true is default since originally this method was QDocs code)
// 
// NOTE(referBaseIdDocFile parameter):
// idDocFile, IdFileAlt and idFileTags all can co-exists in a revision in case of auto subsituted public file, if referBaseIdDocFile parameter is true we always return extension based on IdDocFile otherwise we
// give priority to extension of idFileAlt (if exists)
// 
// So it means when we want download a public file of a document, referBaseIdDocFile should passed as "False", while to download original/base main document we should referBaseIdDocFile as "True"
// ****************************************************************************************************************************************************************************************************************
public string Documento.GetDownloadbleFilePath(
  optional inout string errorMessage = "" // 
  optional boolean downloadDocument = -1  // 
  optional boolean referBaseIdDocFile = 0 // 
)
{
  string DownloadFilePath = ""
  int:downloadFileErrorTypes DownloadError = 0
  string ErrorMessage = ""
  //  
  // DownloadFilePath is an inout parameter and it is used to know where the file is on disk to download
  DownloadError = this.ExtractDocumentToFile(DownloadFilePath, referBaseIdDocFile)
   
  // 
  if (DownloadError == NoError)
  {
    errorMessage = ""
     
    // the document could be dowbloaded in case of NoError if the openDocument is true
    if (downloadDocument)
    {
      QappCore.OpenDocumentManager.downloadFile(DownloadFilePath)
    }
     
  }
  else 
  {
     
    // we clear the downloadPath in case of error
    DownloadFilePath = ""
    switch (DownloadError)
    {
      case ErrorFileLessDocument:
         ErrorMessage = "Documento cartaceo"
      break
      case ErrorCorruptedBlob:
         ErrorMessage = "Problema con il contenuto del file"
      break
      case ErrorInvalidDocument:
         ErrorMessage = "Il documento che si sta cercando di scaricare non è inizializzato correttamente"
      break
      case ErrorNoAnyValidRevision:
         ErrorMessage = "Il documento non ha revisioni approvate"
      break
      default:
         // this should never happen, if execution arrives here likely a new error type has been introduced
         // and it must be handled in this switch statement too
         ErrorMessage = "Errore sconosciuto"
      break
    }
  }
   
  // prepare the inout parameter
  errorMessage = if(ErrorMessage != "", ErrorMessage + ", impossibile scaricare.", "")
  return DownloadFilePath
}


// ──────────────────────────────────

// *********************************************************
// Returns true if stato attivo and expiration in the future
// *********************************************************
public boolean Documento.isValid()
{
  Revision dr = this.GetLastRevisione(...)
  if (!(dr))
  {
    return false
  }
   
  boolean validToday = nullValue(dr.DATAVALIDITA, today()) >= today()
   
  boolean isActive = STATO == Attivo
   
  boolean result = validToday and isActive
   
  return result
}


// ──────────────────────────────────

// *********************************************************
// Returns true if the document is visible to the given user
// *********************************************************
public boolean Documento.isVisibleTo(
  Utente user // 
)
{
  string:flagYN canRead = ""
  string:flagYN vPRIVPERS1NGTPRIVILEGI = ""
  select into variables (found variable)
    set canRead = LETTURA
  from 
    DOCPERMESSI // master table
  where
    IDCARTELLA == IDCARTELLA
    IDUTENTE == user.IDUTENTE
   
  boolean pers1inDocumenti = vPRIVPERS1NGTPRIVILEGI == Yes
  boolean canReadInFolder = canRead == Yes
   
  boolean visible = canReadInFolder or pers1inDocumenti
   
  return visible
}


// ──────────────────────────────────

// *************************************************************
// This procedure creates a DocDocumento with its first revision
// *************************************************************
public static Documento Documento.create(
  string filePath                       // 
  string descriptionOfDocumento         // 
  string codDocumento                   // 
  DOCTIPIDOCUMENTO documentType         // 
  int idCartella                        // 
  optional boolean keepOriginalFile = 0 // 
)
{
  // create docDocumento
  Documento document = new()
  document.init()
  document.IDTIPODOCUMENTO = documentType.IDTIPODOCUMENTO
  document.DESCRDOCUMENTO = descriptionOfDocumento
  document.IDCARTELLA = idCartella
  document.CODDOCUMENTO = codDocumento
  //  
  // Create the First Revision
  Revision dr = Revision.createRevision(filePath, 0, document, keepOriginalFile, ...)
  document.Revisions.add(dr)
   
  return document
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public Documento Documento.createACopy(
  optional Cartella cartella            // 
  optional boolean approve = 0          // 
  optional boolean keepOriginalFile = 0 // 
)
{
  // create docDocumento
  Documento document = new()
  document.init()
  if (cartella)
    document.IDCARTELLA = cartella.IDCARTELLA
  else 
    document.IDCARTELLA = IDCARTELLA
  document.IDTIPODOCUMENTO = IDTIPODOCUMENTO
  document.CODDOCUMENTO = CODDOCUMENTO
  document.DESCRDOCUMENTO = DESCRDOCUMENTO
  document.MODELLO = MODELLO
  document.NOTE = NOTE
   
  // download document as a file from document Object
  string errorMessage = ""
  string filePath = this.GetDownloadbleFilePath(errorMessage, false, ...)
   
  if (errorMessage != "")
  {
    QappCore.DTTLogMessage(errorMessage, ..., DTTError)
    return null
  }
   
  // Create the First Revision
  Revision dr = Revision.createRevision(filePath, 0, document, keepOriginalFile, approve)
  document.Revisions.add(dr)
   
  return document
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean Documento.isModello()
{
  return MODELLO == Yes
}


// ──────────────────────────────────



// ──────────────────────────────────

// ****************************************************************************************************************************
// it returns the property index of the property linked to the second image field of the class (e.g. ART_ANAGRAFICA.FOTO_SECOND
// ****************************************************************************************************************************
public int Documento.PROTECTEDgetSecondImagePropertyIndex()
{
  return null
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
private void Documento.SetDisplayIcon()
{
  // TODO
  // Logica per individuare l'estensione corretta
  string estensione = lower(EstensioneFile)
  //  
  if (CODDOCUMENTO + DESCRDOCUMENTO = "")
  {
    IconaDisplay = "{{icon-fa-file-word}}"
  }
  else 
  {
    if (isNull(EstensioneFile))
    {
      IconaDisplay = "{{icon-fa-file-word}}"
    }
    else 
    {
      // Scrivi un commento per questo blocco o premi backspace per eliminare questo commento
      switch (estensione)
      {
         case .pdf:
           IconaDisplay = "{{icon-fa-file-pdf}}"
         break
         case .doc:
           IconaDisplay = "{{icon-fa-file-word}}"
         break
         case .docx:
           IconaDisplay = "{{icon-fa-file-word}}"
         break
         case .xls:
           IconaDisplay = "{{icon-fa-file-excel}}"
         break
         case .xlsx:
           IconaDisplay = "{{icon-fa-file-excel}}"
         break
         case .rtf:
           IconaDisplay = "{{icon-fa-file-word}}"
         break
         default:
           IconaDisplay = "{{icon-fa-file}}"
         break
      }
      IconaDisplay = formatMessage("|1 |2", IconaDisplay, estensione, ...)
    }
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static Documento Documento.getFromDB(
  int mainId                                                             // 
  optional string:getFromDBLoadingModes loadingMode = "normalZeroLevels" // 
)
{
  Documento d = cast(MainModule.retrieve(Documenti, mainId, loadingMode))
  return d
}


// ──────────────────────────────────

// ***************************************
// Descrivi lo scopo di questa funzione...
// ***************************************
public int Documento.getMainID()
{
  return IDDOCUMENTO
}


// ──────────────────────────────────

// ***************************************
// Descrivi lo scopo di questa funzione...
// ***************************************
public int Documento.getTypeID()
{
  return IDTIPODOCUMENTO
}


// ──────────────────────────────────

// ***************************************
// Descrivi lo scopo di questa funzione...
// ***************************************
public int Documento.getKordApp()
{
  return Documenti
}


// ──────────────────────────────────

// ****************************************
// Describe the purpose of this function...
// ****************************************
public string Documento.getDescription()
{
  return DESCRDOCUMENTO
}


// ──────────────────────────────────

// ***************************************************************************************************************************************************************
// returns the count of the links that involve the document (either the doc is linked to another document - as source or destination of the link - or to a module)
// ***************************************************************************************************************************************************************
public int Documento.GetTotalLinksCount()
{
  int docCollegatiCount = 0
  int moduleLinksCount = 0
   
   
  int totalLinksCount = docCollegatiCount + moduleLinksCount
   
  string sql = formatMessage("SELECT count(*) as count FROM DOC_COLLEGATI DC INNER JOIN DOC_DOCUMENTI DD ON DD.ID_DOCUMENTO = DC.ID_DOCUMENTO where dc.id_documento = |1 UNION SELECT count(*)  FROM 
           DOC_COLLEGATI DC INNER JOIN DOC_DOCUMENTI DD ON DD.ID_DOCUMENTO = DC.ID_DOC where dc.id_documento = |1 UNION SELECT count(*) FROM ART_DOCUMENTI AD INNER JOIN ART_ANAGRAFICA AA ON AA.ID_ARTICOLO = AD.
           ID_ARTICOLO INNER JOIN ART_TIPI_ARTICOLO ATA ON ATA.ID_TIPO_ARTICOLO = AA.ID_TIPO_ARTICOLO INNER JOIN DOC_DOCUMENTI DOC ON DOC.ID_DOCUMENTO = AD.ID_DOCUMENTO WHERE ad.ID_DOCUMENTO = |1 UNION ALL 
           SELECT count(*) FROM GCF_DOCUMENTI GD INNER JOIN GCF_ANAGRAFICA GA ON GA.ID_CONTO = GD.ID_CONTO INNER JOIN GCF_TIPI_CONTO GTC ON GTC.ID_TIPI_CLIFOR = GA.ID_TIPO_CONTO INNER JOIN DOC_DOCUMENTI DOC ON 
           DOC.ID_DOCUMENTO = GD.ID_DOCUMENTO WHERE gd.id_documento = |1 UNION ALL SELECT count(*) FROM PRG_DOCUMENTI PD INNER JOIN PRG_PROGETTI PP ON PP.ID_PROGETTO= PD.ID_PROGETTO INNER JOIN PRG_TIPI_PROGETTO 
           PTP ON PTP.ID_TIPO_PROGETTO= PP.ID_TIPO_PROGETTO INNER JOIN DOC_DOCUMENTI DOC ON DOC.ID_DOCUMENTO = PD.ID_DOCUMENTO WHERE PD.ID_DOCUMENTO = |1 UNION ALL SELECT count(*) FROM EVA_DOCUMENTI ED INNER 
           JOIN EVA_TESTATA_EVENTO ETE ON ETE.ID_EVENTO= ED.ID_EVENTO INNER JOIN DOC_DOCUMENTI DOC ON DOC.ID_DOCUMENTO = ED.ID_DOCUMENTO WHERE ed.ID_DOCUMENTO = |1 UNION ALL SELECT count(*) FROM PER_DOCUMENTI P­
           D INNER JOIN PER_ANAGRAFICA PA ON PA.ID_DIPENDENTE = PD.ID_DIPENDENTE INNER JOIN V_UTENTI_DIPENDENTI VUD ON PA.ID_DIPENDENTE = VUD.ID_DIPENDENTE INNER JOIN PER_TIPI_ANAGR PTA ON PTA.ID_TIPO_ANAGR = P­
           A.ID_TIPO_ANAGR INNER JOIN DOC_DOCUMENTI DOC ON DOC.ID_DOCUMENTO = PD.ID_DOCUMENTO WHERE pd.ID_DOCUMENTO = |1 UNION ALL SELECT count(*) FROM CES_DOCUMENTI CD INNER JOIN CES_ANAGRAFICA CA ON CA.
           ID_CESPITE = CD.ID_CESPITE INNER JOIN CES_TIPI_CESPITE CTC ON CTC.ID_TIPI_INFRSTR = CA.ID_TIPO_CESPITE INNER JOIN DOC_DOCUMENTI DOC ON DOC.ID_DOCUMENTO = CD.ID_DOCUMENTO WHERE CD.ID_DOCUMENTO = |1 
           UNION ALL SELECT count(*) FROM CIS_DOCUMENTI CD INNER JOIN CIT_ANAGRAFICA CA ON CA.ID_CITTADINI= CD.ID_CITTADINI INNER JOIN CIT_TIPI_ANAGR CTA ON CTA.ID_TIPO_ANAGR = CA.ID_TIPO_ANAGR INNER JOIN 
           DOC_DOCUMENTI DOC ON DOC.ID_DOCUMENTO = CD.ID_DOCUMENTO WHERE CD.ID_DOCUMENTO = |1 UNION ALL SELECT count(*) FROM FUN_DOCUMENTI FD INNER JOIN MSQ_FUNZIONI MF ON MF.ID_FUNZIONE = FD.ID_FUNZIONE INNER 
           JOIN DOC_DOCUMENTI DOC ON DOC.ID_DOCUMENTO = FD.ID_DOCUMENTO WHERE fd.ID_DOCUMENTO = |1 UNION ALL SELECT COUNT(*) from CDATA_MODULE_VALUES CMV INNER JOIN CDATA_FIELDS CF ON CMV.ID_CDATA_FLD = CF.
           ID_CDATA_FLD AND CF.CDATA_TYPE = 18 AND CF.ATTIVO = 'Y' INNER JOIN CDATA_SECTIONS CS ON CF.ID_CDATA_SEC = CS.ID_CDATA_SEC and CS.ATTIVO = 'Y' INNER JOIN DOC_REVISIONI DR ON DR.ID_REVISIONE = CMV.VALU­
           E INNER JOIN DOC_DOCUMENTI DD ON DD.ID_DOCUMENTO = DR.ID_DOCUMENTO WHERE dd.ID_DOCUMENTO = |1", IDDOCUMENTO, ...)
   
  Recordset r = null
  r = QualibusDB.SQLQuery(sql)
   
  totalLinksCount = 0
  r.moveFirst()
  while (!(r.EOF()))
  {
    QappCore.DTTLogMessage(r.getFieldValue("count"), ..., DTTInfo)
    totalLinksCount = totalLinksCount + toInteger(r.getFieldValue("count"))
    r.moveNext()
  }
   
  return totalLinksCount
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public IDCollection Documento.GetDocCollegatiCollection(
  optional string:documentLinkDirectionTypes directionType = "S" // 
)
{
  IDCollection linkedDocuments of VDOCCOLLEGATI = new()
  select into collection (linkedDocuments)
  from 
    VDOCCOLLEGATI // master table
  where
    LINKDIRECTION = directionType
    IDMAINDOCUMENT = IDDOCUMENTO
   
  return linkedDocuments
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public IDCollection Documento.GetModuleDocumentLinksCollection()
{
  IDCollection linkedModuleDocuments of VMODULEDOCUMENTCOLLEGATI = new()
  select into collection (linkedModuleDocuments)
  from 
    VMODULEDOCUMENTCOLLEGATI // master table
  where
    IDDOCUMENTO = IDDOCUMENTO
   
  return linkedModuleDocuments
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean Documento.extensionSupportedByNuovoDaTemplate(
  inout string errorMessage // 
)
{
  boolean extensionSupported = true
  string extensionInLowerCase = lower(EstensioneFile)
  switch (extensionInLowerCase)
  {
    case ".doc":
    case ".xls":
    case ".ppt":
      extensionSupported = false
    break
  }
   
  errorMessage = ""
  if (!(extensionSupported))
  {
    errorMessage = formatMessage("I file con estensione '|1' non sono utilizzabili con "Nuovo da modello".", extensionInLowerCase, ...)
  }
   
  return extensionSupported
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static boolean Documento.documentExistsWithCode(
  string code // 
)
{
  boolean documentExistsWithGivenCode = false
  int vCount = 0
  select into variables (found variable)
    set vCount = count(IDDOCUMENTO)
  from 
    Documenti // master table
  where
    CODDOCUMENTO == code
   
  documentExistsWithGivenCode = vCount > 0
  return documentExistsWithGivenCode
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Documento.computeAdditionalProperties()
{
  InModifica = this.isRevisionInProgress()
  Distributed = this.isDocumentDistributed()
  DistributedToMe = this.isDistributedToLoggedInDipendente()
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean Documento.isDocumentDistributed()
{
   
  string distributed = ""
  select into variables (found variable)
    set distributed = VDOCREVISIONIDISTRIB.DISTRIBUITA
  from 
    Documenti            // master table
    Revisions            // joined with Documenti using key FK_DOC_REVISIONI01
    VDOCREVISIONIDISTRIB // manually joined, see where clauses
  where
    Documenti.IDDOCUMENTO == IDDOCUMENTO
    Revisions.IDREVISIONE == VDOCREVISIONIDISTRIB.IDREVISIONE
   
  boolean thisDocumentIsDistributed = distributed == Yes
   
   
  return thisDocumentIsDistributed
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean Documento.isRevisionInProgress()
{
  int countNotApprovedRevisions = 0
  select into variables (found variable)
    set countNotApprovedRevisions = count(IDREVISIONE)
  from 
    Revisions // master table
  where
    isNull(DATAAPPROVAZIONE)
    IDDOCUMENTO == this.IDDOCUMENTO
   
  boolean inProgress = countNotApprovedRevisions > 0
   
  return inProgress
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean Documento.isDistributedToLoggedInDipendente()
{
  boolean distributedToMe = false
   
  if (!(Revisions.loaded))
  {
    this.loadCollectionFromDB(Revisions, 0)
  }
  for each Revision r in Revisions
  {
    distributedToMe = r.IsRevDistributedToLoggedInDipendente()
    if (distributedToMe)
    {
      break 
    }
  }
   
  return distributedToMe
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static IDCollection Documento.searchDocumentsBasedOnPrivilege(
  Utente user                            // 
  string:documentSearchTypes searchTypes // 
  optional string searchString = ""      // 
)
{
  IDCollection searchResults of Documento = new()
  boolean searchModelloOnly = searchTypes == Template
  boolean searchAllDocuments = searchTypes == SearchAll
  QualibusDB.maxRows = 20
  select into collection (searchResults)
  from 
    Documento // master table
  where
    ((searchModelloOnly == true and MODELLO == Yes) or (searchAllDocuments == true)) and ((CODDOCUMENTO like "%" + searchString + "%") or (DESCRDOCUMENTO like "%" + searchString + "%") or (
       searchString == "%"))
   
   
  IDCollection filteredSearchResults of Documento = new()
  for each Documento d in searchResults
  {
    if (d.isValid())
    {
      if (user.hasSpecificPrivilege(Documenti, Pers1))
      {
         filteredSearchResults.add(d)
      }
      else if (d.isVisibleTo(user))
      {
         filteredSearchResults.add(d)
      }
    }
  }
   
  return filteredSearchResults
   
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean Documento.userCanDownloadObsoleteDocument(
  Utente utente                           // 
  optional inout string errorMessage = "" // 
)
{
  if (utente.hasSpecificPrivilegeForKordApp(Documenti, Pers2))
  {
    errorMessage = "il documento è OBSOLETO, non hai il privilegio per aprirlo"
    return false
  }
  else 
  {
    errorMessage = "Il documento che stai aprendo è OBSOLETO"
    return true
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean Documento.isDownloadable()
{
  boolean isDownloadable = false
   
  // if storedLastRevision is null we try to get it and if the retrieved last revisione is not null we "cache" it into StoredLastRevision
  if (StoredLastRevision == null)
  {
    Revision r = this.GetLastRevisione(...)
    if (r != null)
      StoredLastRevision = r
  }
   
  isDownloadable = StoredLastRevision != null
   
  return isDownloadable
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public int Documento.getFileSize(
  int idRevisione // 
)
{
   
  int FileSizeInBytes = 0
  select into variables (found variable)
    set FileSizeInBytes = DocFiles.FILESIZE
  from 
    Revisions           // master table
    VDOCREVISIONIVALIDE // manually joined, see where clauses
    DocFiles            // manually joined, see where clauses
  where
    Revisions.IDDOCUMENTO == IDDOCUMENTO
    VDOCREVISIONIVALIDE.IDDOCUMENTO == Revisions.IDDOCUMENTO
    VDOCREVISIONIVALIDE.IDFILE == DocFiles.IDDOCFILE
    Revisions.IDREVISIONE = idRevisione
   
  return FileSizeInBytes
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public IDCollection Documento.getApprovedRevisions()
{
  IDCollection approvedRevisions of Revision = new()
  this.loadDocumentRevisions(false, ...)
  for each Revision r in Revisions
  {
    if (!(isNull(r.DATAAPPROVAZIONE)))
    {
      approvedRevisions.addRef(r)
    }
  }
  return approvedRevisions
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Documento.loadDocumentRevisions(
  optional boolean forceReloadFromDB = 1 // 
  optional int childrenLevel = 9999      // 
)
{
  if (forceReloadFromDB)
  {
    Revisions.loaded = false
  }
  this.loadCollectionFromDB(Revisions, childrenLevel)
}


// ──────────────────────────────────



// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static DocumentoInserter DocumentoInserter.create(
  Cartella cartella // 
  string:documentInsertionTypes insertionType // 
  optional int idTipoDocumento = 0            // 
  optional string codDocumento = ""           // 
  optional string descrDocumento = ""         // 
  optional string filepath = ""               // 
)
{
  DocumentoInserter di = new()
  di.init()
  di.Cartella = cartella
  di.IDCARTELLA = cartella.IDCARTELLA
  di.InsertionType = insertionType
  if (idTipoDocumento != 0)
  {
    di.IDTIPODOCUMENTO = idTipoDocumento
    di.DOCTIPIDOCUMENTO = DOCTIPIDOCUMENTO.get(idTipoDocumento)
  }
  else 
  {
    di.DOCTIPIDOCUMENTO = DOCPARAMETRI.getDefaultTipoDocumento()
    di.IDTIPODOCUMENTO = di.DOCTIPIDOCUMENTO.IDTIPODOCUMENTO
  }
  di.populateCodiceWithDefaultValue()
   
  if (codDocumento != "")
    di.CODDOCUMENTO = codDocumento
  if (descrDocumento != "")
    di.DESCRDOCUMENTO = descrDocumento
  di.Filepath = filepath
  if (di.DESCRDOCUMENTO == "")
  {
    if (di.Filepath != "")
    {
      di.computeAddtionalProperties()
    }
  }
  return di
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void DocumentoInserter.setTipoDocumento(
  optional boolean forceTheLoading = 0 // 
)
{
  if (forceTheLoading)
  {
    if (DOCTIPIDOCUMENTO.loaded)
    {
      DOCTIPIDOCUMENTO.loaded = false
    }
  }
  DOCTIPIDOCUMENTO = DOCTIPIDOCUMENTO.get(IDTIPODOCUMENTO)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void DocumentoInserter.populateCodiceWithDefaultValue()
{
  DOCTIPIDOCUMENTO = DOCTIPIDOCUMENTO.get(IDTIPODOCUMENTO)
  if (CODDOCUMENTO == "")
  {
    if (DOCTIPIDOCUMENTO.CODICEPREFISSO != "")
    {
      CODDOCUMENTO = DOCTIPIDOCUMENTO.CODICEPREFISSO
    }
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static IDCollection DocumentoInserter.prepareDocumentoInserterCollection(
  Cartella cartella      // 
  IDArray filePathsArray // 
)
{
  IDCollection documentInserters of DocumentoInserter = new()
   
  // loop on array toe xtrac all the filepaths and create the collcetion of documentoInserter
  for (int i = 0; i < filePathsArray.length(); i = i + 1)
  {
    DocumentoInserter di = DocumentoInserter.create(cartella, FromFile, ..., filePathsArray.getValue(i))
    documentInserters.add(di)
  }
   
  // nce the collection has been prepared we clear the array so we mark that upload is finishde
   
  return documentInserters
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void DocumentoInserter.computeAddtionalProperties()
{
  Filename = SH.rightUpToDelimiter(Filepath, FH.getSeparator(), 1)
  string fileNameWithoutExtension = SH.leftUpToDelimiter(Filename, ".", 1)
  DESCRDOCUMENTO = fileNameWithoutExtension
}


// ──────────────────────────────────

// ************************************************************
// Event raised to the document during the validation procedure
// ************************************************************
event DocumentoInserter.OnValidate(
  int:validateReasons Reason // An integer specifying the reason the document is being validated. The most common values are 0 to indicate complete validation before saving, or 1 to indicate a quick validati...
  inout boolean Error        // A boolean output parameter. If set to True, specifies an error condition and stops the validation immediately.
  inout boolean Skip         // A boolean output parameter. If set to True, does not check the rules for automatic validation of the document (see notes).
)
{
  if (Reason == Complete)
  {
    // empty cartella error (likely this error wont be there)
    if (isNull(IDCARTELLA) or IDCARTELLA == 0)
    {
      Error = true
      this.setPropertyError("la cartella non è selezionata, qualcosa non va durante la selezione.", IDCARTELLA)
    }
    if (CODDOCUMENTO == "")
    {
      // mandatory code error
      if (DOCTIPIDOCUMENTO.CODICEOBBLIGATORIO == Yes)
      {
         Error = true
         this.setPropertyError("Il codice documento deve essere obbligatoriamente indicato.", CODDOCUMENTO)
      }
    }
    else 
    {
      // duplicate code error
      if (DOCTIPIDOCUMENTO.DUPLICAZIONECODICE == No)
      {
         boolean documentExistWithSameCode = Documento.documentExistsWithCode(CODDOCUMENTO)
         if (documentExistWithSameCode)
         {
           Error = true
           this.setPropertyError(formatMessage("Il tipo di documento non consente la duplicazione del codice! (|1)", CODDOCUMENTO, ...), CODDOCUMENTO)
         }
      }
    }
     
    // empty description error
    if (DESCRDOCUMENTO == "")
    {
      Error = true
      this.setPropertyError("La descrizione del documento deve essere indicata.", DESCRDOCUMENTO)
    }
    if (InsertionType == FromFile)
    {
      if (Filepath == "")
      {
         Error = true
         this.setPropertyError("È necessario selezionare un file da inserire.", Filepath)
      }
    }
  }
}


// ──────────────────────────────────

// ***********************************************************
// Event raised to the document when the transaction is closed
// ***********************************************************
event DocumentoInserter.OnEndTransaction()
{
  if (wasModified(IDTIPODOCUMENTO))
    this.populateCodiceWithDefaultValue()
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event DocumentoInserter.OnInit()
{
  Approve = false
  Filepath = ""
}


// ──────────────────────────────────

// *************************************************
// copy Revisions of documents from Modello revision
// *************************************************
public Revision Revision.CreateRevisionFromMedello(
  int IdCreator       // 
  int IdDocument      // 
  Revision ModelloRev // 
)
{
  Revision docrevisione = new()
  docrevisione.init()
  docrevisione.IDREVISIONE = Sequence.getNextSequence(DOCN_ID_REVISIONE, ...)
  docrevisione.IDDOCUMENTO = IdDocument
  docrevisione.NROREVISIONE = ModelloRev.NROREVISIONE
   
  docrevisione.ESTENSIONEFILE = ModelloRev.ESTENSIONEFILE
  docrevisione.IDUBICAZIONE = ModelloRev.IDUBICAZIONE
  docrevisione.IDUTENTEREDATTORE = IdCreator
  docrevisione.DATACREAZIONE = now()
  docrevisione.IDUTENTEAPPROVATORE = IdCreator
  docrevisione.DATAAPPROVAZIONE = now()
  docrevisione.NOTE = ModelloRev.NOTE
  docrevisione.IDUTENTEULTMOD = IdCreator
  docrevisione.DATAULTIMAMOD = now()
  docrevisione.IDTEMPLATE = ModelloRev.IDTEMPLATE
  docrevisione.TEMPLATE = ModelloRev.TEMPLATE
  docrevisione.IDPARENTTEMPLATEREMOTE = ModelloRev.IDPARENTTEMPLATEREMOTE
  docrevisione.MULTVER = ModelloRev.MULTVER
   
   
  // create docfile
  DocFile df = ModelloRev.CreateDocFile(DOC, ModelloRev.ESTENSIONEFILE)
  docrevisione.IDDOCFILE = df.IDDOCFILE
  docrevisione.DocFiles.add(df)
   
  if (!(isNull(ModelloRev.IDFILEALT)))
  {
    // create docfile for IDFileALT
    DocFile dfALT = ModelloRev.CreateDocFile(ALT, ModelloRev.ESTENSIONEFILEALT)
    docrevisione.IDFILEALT = dfALT.IDDOCFILE
    docrevisione.DocFilesALT.add(dfALT)
    docrevisione.ESTENSIONEFILEALT = ModelloRev.ESTENSIONEFILEALT
    docrevisione.DATAINSFILEALT = now()
    docrevisione.IDUTENTEFILEALT = IdCreator
  }
   
  if (!(isNull(ModelloRev.IDDOCTAGS)))
  {
    // create docfile for IDFileTAGS
    DocFile dfTAG = ModelloRev.CreateDocFile(TAG, ModelloRev.ESTENSIONEFILETAG)
    docrevisione.IDDOCTAGS = dfTAG.IDDOCFILE
    docrevisione.DocFilesTAG.add(dfTAG)
    docrevisione.ESTENSIONEFILETAG = ModelloRev.ESTENSIONEFILETAG
  }
   
  DevTools.ToBeReviewed("following fields to be reviewed")
  docrevisione.IDUTENTEVERIFICATORE = null
  docrevisione.DATAVERIFICA = null
  docrevisione.DATAVALIDITA = null
  docrevisione.DATACONSERVAZIONE = null
  docrevisione.IDUTENTEESTR = null
  docrevisione.COMPUTERNAME = null
  docrevisione.DATAESTRAZIONE = null
   
  return docrevisione
}


// ──────────────────────────────────

// ******************************************************************************************************
// obtain DocFile object from collection based on ID and type of ID i.e. IdDocFile,IdFileAlt or IdDocTags
// ******************************************************************************************************
private DocFile Revision.GetMatchingDocFileObject(
  int:iddocfiletype IdType // 
)
{
  DocFile docfileObject = null
  int IdFile = 0
  IDCollection DocFilesColl of DocFile = null
  switch (IdType)
  {
    case DOC:
      if (!(DocFiles.loaded))
         this.loadCollectionFromDB(DocFiles, ...)
      DocFilesColl = DocFiles
      IdFile = IDDOCFILE
    break
    case ALT:
      if (!(DocFilesALT.loaded))
         this.loadCollectionFromDB(DocFilesALT, ...)
      DocFilesColl = DocFilesALT
      IdFile = IDFILEALT
    break
    case TAG:
      if (!(DocFilesTAG.loaded))
         this.loadCollectionFromDB(DocFilesTAG, ...)
      DocFilesColl = DocFilesTAG
      IdFile = IDDOCTAGS
    break
  }
  for each DocFile df in DocFilesColl
  {
    if (df.IDDOCFILE == IdFile)
    {
      docfileObject = df
      break 
    }
  }
  return docfileObject
}


// ──────────────────────────────────

// **********************************************************************
// create doc file object from modello based on the type (DOC,ALT or TAG)
// **********************************************************************
public DocFile Revision.CreateDocFile(
  int:iddocfiletype Tipo // 
  string FileExtension   // 
)
{
  DocFile ModelloDocFile = new()
  ModelloDocFile = this.GetMatchingDocFileObject(Tipo)
  DocFile df = new()
  df = df.CreateDocFileFromModello(ModelloDocFile, FileExtension)
   
  return df
}


// ──────────────────────────────────

// ***************************************************************************************************
// This procedure creates a DocRevisione using a path to instance the DocFile (that contains the Blob)
// ***************************************************************************************************
public static Revision Revision.createRevision(
  string path                           // 
  int numberOfRevision                  // 
  Documento docDocumento                // 
  optional boolean keepOriginalFile = 0 // 
  optional boolean approve = 1          // 
)
{
  // Create the DocRevisione
  Revision revision = new()
  revision.init()
  revision.NROREVISIONE = numberOfRevision
  revision.IDDOCUMENTO = docDocumento.IDDOCUMENTO
  revision.computeEstensioneFile(path)
   
  // approval fields are set on Init event, so here we clear them if approval of document is not required
  if (!(approve))
  {
    revision.IDUTENTEAPPROVATORE = null
    revision.DATAAPPROVAZIONE = null
  }
   
  // 2. Create the DocFile from Path
  DocFile df = DocFile.createFromFile(path, keepOriginalFile)
  revision.setDocFile(df)
   
  return revision
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Revision.setDocFile(
  DocFile docFile // 
)
{
  DocFile = docFile
  IDDOCFILE = DocFile.IDDOCFILE
   
  // actually the Doc Revision saves all the DocFiles into the collection "DocFile". i add the doc File just created into the collection to keep it working
  DocFiles.add(DocFile)
}


// ──────────────────────────────────

// ******************************************************************************************
// this procedure computes the ESTENSIONEFILE property of DOCREVISIONE using StringTokenizer.
// ******************************************************************************************
public void Revision.computeEstensioneFile(
  string filePath // 
)
{
  string extension = ""
  string filePathWithExtension = filePath
  StringTokenizer stokeniz = new()
  stokeniz.setString(filePathWithExtension, ".", ...)
  extension = stokeniz.nextToken()
  while (stokeniz.hasNextToken())
  {
    extension = stokeniz.nextToken()
  }
   
  extension = "." + extension
  ESTENSIONEFILE = extension
}


// ──────────────────────────────────



// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Revision.loadRevisionDistributions(
  optional boolean forceReloadFromDB = 1 // 
  optional int childrenLevel = 9999      // 
)
{
  if (forceReloadFromDB)
  {
    Distributions.loaded = false
  }
  this.loadCollectionFromDB(Distributions, childrenLevel)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Revision.computeAdditionalProperties()
{
  if (!(Distributions.loaded))
  {
    this.loadRevisionDistributions(...)
  }
   
  // set all additional fields
  this.computeStatus()
  this.SetDisplayIcon()
   
  Distribuita = Distributions.count() > 0
  DistributedToMe = this.IsRevDistributedToLoggedInDipendente()
  NumberOfDistribution = this.getNumberOfDistribution()
  PublicFile = if(isNull(ESTENSIONEFILEALT), false, true)
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
private void Revision.SetDisplayIcon()
{
  // TODO
  // Logica per individuare l'estensione corretta
  string estensione = lower(ESTENSIONEFILE)
   
  //  
  if (isNull(ESTENSIONEFILE))
  {
    IconaDisplay = "{{icon-fa-file-word}}"
  }
  else 
  {
    // Scrivi un commento per questo blocco o premi backspace per eliminare questo commento
    switch (estensione)
    {
      case .pdf:
         IconaDisplay = "{{icon-fa-file-pdf}}"
      break
      case .doc:
         IconaDisplay = "{{icon-fa-file-word}}"
      break
      case .docx:
         IconaDisplay = "{{icon-fa-file-word}}"
      break
      case .xls:
         IconaDisplay = "{{icon-fa-file-excel}}"
      break
      case .xlsx:
         IconaDisplay = "{{icon-fa-file-excel}}"
      break
      case .rtf:
         IconaDisplay = "{{icon-fa-file-word}}"
      break
      default:
         IconaDisplay = "{{icon-fa-file}}"
      break
    }
    IconaDisplay = formatMessage("|1 |2", IconaDisplay, estensione, ...)
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean Revision.IsRevDistributedToLoggedInDipendente()
{
  boolean thisRevisionIsDistributedToLoggedinDipendente = false
   
  Personale p = QappCore.Loggeduser.getLinkedPersonale()
  if (p)
  {
    int vCount = 0
    select into variables (found variable)
      set vCount = count(DistribuzioneRecipients.IDLISTA)
    from 
      Distributions           // master table
      DistribuzioneRecipients // joined with Distributions using key FK_DOC_LISTE_DISTRIBUZIONE01
    where
      Distributions.IDREVISIONE == IDREVISIONE
      DistribuzioneRecipients.IDDIPENDENTE == p.IDDIPENDENTE
     
    thisRevisionIsDistributedToLoggedinDipendente = (vCount > 0)
  }
   
  return thisRevisionIsDistributedToLoggedinDipendente
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public int Revision.getNumberOfDistribution()
{
  int NoOfDistributions = 0
  select into variables (found variable)
    set NoOfDistributions = NRODISTRIB
  from 
    VDOCREVISIONIDISTRIB // master table
  where
    IDREVISIONE == IDREVISIONE
  return NoOfDistributions
}


// ──────────────────────────────────

// ***************************************************************
// static method to retrieve Revision Object for given idRevisione
// ***************************************************************
public static Revision Revision.get(
  int idRevisione // 
)
{
  Revision revisione = new()
  revisione.IDREVISIONE = idRevisione
  try 
  {
    revisione.loadFromDB(...)
  }
  catch 
  {
    revisione = null
    QappCore.DTTLogMessage(formatMessage("Unable to load revision Object for IdRevisione: |1", idRevisione, ...), ..., DTTError)
  }
  return revisione
}


// ──────────────────────────────────

// **************************************************************************************
// Event raised to the document during the initial steps of the document saving procedure
// **************************************************************************************
event throws exception Revision.BeforeSave(
  inout boolean Skip         // A boolean output parameter. If set to True, tells the framework not to perform operations on the database, because they will be managed independently.
  inout boolean Cancel       // A boolean output parameter. If set to True, tells the framework to cancel the save procedure due to errors that occurred. Since the entire document is saved in the same transa...
  int:beforeSavePhases Phase // An integer from 0 to 3 that specifies the progress status of the save procedure as detailed at the beginning of the article.
)
{
  // save all Docfiles collections
   
  if (DocFiles.isModified())
  {
    DocFiles.saveToDB(...)
  }
  if (DocFilesALT.isModified())
  {
    DocFilesALT.saveToDB(...)
  }
  if (DocFilesTAG.isModified())
  {
    DocFilesTAG.saveToDB(...)
  }
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event Revision.OnInit()
{
  // get the ID of Revisione from SW9_SEQUENCES
  IDREVISIONE = Sequence.getNextSequence(DOCN_ID_REVISIONE, ...)
   
  int idLoggedUser = QappCore.Loggeduser.IDUTENTE
  IDUTENTEREDATTORE = idLoggedUser
  DATACREAZIONE = now()
  DATAAPPROVAZIONE = now()
  IDUBICAZIONE = 1
  IDUTENTEAPPROVATORE = idLoggedUser
  IDUTENTEULTMOD = idLoggedUser
  NOTE = "-"
  DATAULTIMAMOD = now()
   
  MULTVER = No
   
}


// ──────────────────────────────────

// ************************************************************
// Event raised by the document after loading from the database
// ************************************************************
event Revision.AfterLoad(
  boolean AlreadyLoaded     // A boolean parameter that specifies whether the document was previously loaded.
  IDArray LoadedCollections // IDArray type parameter that contains the collections loaded by the framework. If the value of the Already Loaded parameter is False, the Loaded Collections parameter is NULL.
)
{
  if (getTag("skipAfterLoad") == true)
  {
    return 
  }
   
  this.computeAdditionalProperties()
  this.setOriginal()
}


// ──────────────────────────────────

// *********************************************************************************************************************
// Event raised to the document when a tree view wants to determine which icon to display next to the corresponding node
// *********************************************************************************************************************
event Revision.OnGetIcon(
  inout string Icon // String input/output parameter representing the icon file to be attached to the node corresponding to the document. The icon must be located in the images subdirectory of the web applic...
  int IconType      // The IconType parameter is 0 if the node is closed and 1 if the node is open.
  IDCollection CallerCollection of IDDocument // The collection containing the document. It may be different from ParentCollection if the document is contained by reference.
  int CallerForm    // The form containing the tree for which the event was raised. Use the Me function of the Form object to make comparisons. It can be ZERO to not specify a form in particular.
  int CallerFrame   // The tree for which the event is raised. Use the Me function of the Tree object to make comparisons. It can be ZERO to not specify a particular visual object.
)
{
  switch (ESTENSIONEFILE)
  {
    case ".pdf":
      Icon = "pdf.png"
       
    break
    case ".odt":
      Icon = "odt.png"
       
    break
    case ".doc":
    break
    default:
      Icon = "file.png"
    break
  }
}


// ──────────────────────────────────

// ************************************************************************
// Create doc file object using modello doc file object passed as parameter
// ************************************************************************
public DocFile DocFile.CreateDocFileFromModello(
  DocFile ModelloDocFile // 
  string FileExtension   // 
)
{
  DocFile df = new()
  df.init()
  df.FILESIZE = ModelloDocFile.FILESIZE
  df.ISCOMPRESSED = ModelloDocFile.ISCOMPRESSED
  df.IDTEMPLATE = ModelloDocFile.IDTEMPLATE
  df.TEMPLATE = ModelloDocFile.TEMPLATE
  df.IDPARENTTEMPLATEREMOTE = ModelloDocFile.IDPARENTTEMPLATEREMOTE
  df.SetDocumentField(ModelloDocFile, FileExtension)
   
  return df
}


// ──────────────────────────────────

// ******************************************************
// Extract file from BLOB field (DOC_FILES.DOCUMENT)
// 
// the parameter must be in the ".pdf" form and not "pdf"
// ******************************************************
private string DocFile.ExtractFileFromBLOB(
  string FileExtensionWithDotAtTheBeginning // 
)
{
  boolean stringStartsWithDot = left(FileExtensionWithDotAtTheBeginning, 1) == "."
  if (!(stringStartsWithDot))
  {
    QappCore.DTTLogMessage("ExtractFileFromBLOB expects the parameter value starts with '.'", ..., DTTError)
    return null
  }
   
  string BlobFile = ""
  string FileName = ""
  if (ISCOMPRESSED == Yes)
    FileName = toString(IDDOCFILE) + ".zip"
  else 
    FileName = toString(IDDOCFILE) + FileExtensionWithDotAtTheBeginning
  try 
  {
    BlobFile = saveBlobFile(DOCUMENT, QappCore.tempPath, FileName)
    if (BlobFile != "")
    {
      QappCore.addTempFile(QappCore.tempPath + FH.getSeparator() + FileName)
    }
  }
  catch 
  {
    throw 0, "Unable to extract file from BLOB"
  }
   
  return BlobFile
}


// ──────────────────────────────────

// ******************************************************************************************************************************************
// Unzip file from passed zipped file and add in to temp folder so file will be deleted on end of session
// 
// the method returns, as IDZip.unzip does, the path the the folder where the file has been extracted (NOTE: folder path only, not full path)
// ******************************************************************************************************************************************
private string DocFile.UnZipTheFile(
  string ZippedFile // 
)
{
  string UnzippedFileFolderPath = ""
  try 
  {
    string destinationPath = QappCore.tempPath + FH.getSeparator() + "UnZipped"
    UnzippedFileFolderPath = IDZip.unzip(ZippedFile, destinationPath, ...)
    QappCore.addTempFile(UnzippedFileFolderPath)
  }
  catch 
  {
    throw 0, "Unable to unzip the file " + ZippedFile
  }
  return UnzippedFileFolderPath
}


// ──────────────────────────────────

// *****************************************************************************************************************************
// Zip the file from soruce and make at destination, add zipped file in to temp folder so file will be deleted on end of session
// *****************************************************************************************************************************
private string DocFile.ZipTheFile(
  string Source      // 
  string Destination // 
)
{
  // this method has been extracted since it is called from 2 places, so it is purely a way to avoid a (even if minimal) code duplication
   
  string ZippedFile = ""
  ZippedFile = IDZip.zip(Source, Destination, ...)
  QappCore.addTempFile(ZippedFile)
  return ZippedFile
}


// ──────────────────────────────────

// ***********************************************************************************
// This method will update DOCUMENT field with New File by making a copy from modello.
// Following step are are done to achieve
// 1. Extract zipped document from modello.
// 2. Unzip file extracted file.
// 3. Rename unzipped file to newID.ext
// 4. Zip renamed file with newID.zip
// 5. Load blob data in Document with newID.zip
// ***********************************************************************************
public void DocFile.SetDocumentField(
  DocFile ModellodocFile // 
  string FileExtension   // 
)
{
  string unzippedFileFolderPath = ""
  string unzippedFileWithPath = ""
  string renamedUnzippedFile = ""
  string zippedFile = ""
  string separator = FH.getSeparator()
  //  
  // since sometimes fileextension is intended with . (".doc" instead than "doc"), we now make sure that it is ".pdf" since it is how it should be
  this.cleanFileExtension(FileExtension)
  string fileExtensionWithDotAtTheBeginning = "." + FileExtension
   
   
   
  // extract file from BLOB field (DOCUMENT)
  string ExtractedFile = ModellodocFile.ExtractFileFromBLOB(fileExtensionWithDotAtTheBeginning)
  if (ISCOMPRESSED == Yes)
  {
     
    // if file is compressed unzip the Extracted file (unzippedFileFolderPath is the folder path only!)
    unzippedFileFolderPath = this.UnZipTheFile(ExtractedFile)
     
    // extracted file should be IDDOCFILE.EXT
    unzippedFileWithPath = unzippedFileFolderPath + FH.getSeparator() + toString(ModellodocFile.IDDOCFILE) + fileExtensionWithDotAtTheBeginning
  }
  else 
    unzippedFileWithPath = ExtractedFile
   
  // rename modello document file to NEWID.exe
  renamedUnzippedFile = QappCore.tempPath + FH.getSeparator() + toString(IDDOCFILE) + fileExtensionWithDotAtTheBeginning
   
  this.renameFile(unzippedFileWithPath, renamedUnzippedFile)
   
  string zippedFolder = QappCore.tempPath + separator + "Zipped"
  QappCore.makeDirectory(zippedFolder)
  //  
  // Zip the renamed file
  zippedFile = zippedFolder + separator + toString(IDDOCFILE) + ".zip"
  zippedFile = this.ZipTheFile(renamedUnzippedFile, zippedFile)
   
  // load file in to DOCUMENT BLOB field
  DOCUMENT = loadBlobFile(zippedFile)
   
}


// ──────────────────────────────────

// ****************************************************************************************************************************************************************************************************************
// a docFile object is created using as blob the passed file (as filePath), to keep the passed file in the original path the second parameter must be True, otherwise the passed file becomes a temporary file
// 
// NOTE: for most cases the temporary file is a good solution because the blob will then be saved in DB and we do not need to refer to the original file, anyway in some cases it makes sense to keep the original
// file: for those cases true can be passed to keepOriginalFile
// ****************************************************************************************************************************************************************************************************************
public static DocFile DocFile.createFromFile(
  string filePath                       // 
  optional boolean keepOriginalFile = 0 // 
)
{
  string fileExtension = SH.rightUpToDelimiter(filePath, ".", ...)
   
  string actualFilePath = filePath
  if (keepOriginalFile)
  {
    string newFilePath = QappCore.tempPath + FH.getSeparator() + docIDToGuid(newDocID()) + "." + fileExtension
    QappCore.copyFile(filePath, newFilePath)
    actualFilePath = newFilePath
  }
   
   
  string RenamedUnzippedFile = ""
  string ZippedFile = ""
   
   
  DocFile df = new()
  df.init()
   
  int fileSize = 0
  try 
  {
    fileSize = fileLength(actualFilePath)
  }
  catch 
  {
    fileSize = 0
  }
  df.FILESIZE = fileSize
   
  df.ISCOMPRESSED = Yes
   
   
  // rename modello document file to NEWID.extension (e.g.: 5536.png)
   
  string separator = FH.getSeparator()
  RenamedUnzippedFile = QappCore.tempPath + separator + toString(df.IDDOCFILE) + "." + fileExtension
   
   
  // since renaming won't work if the the destination path contains a valid file, so we forcefully clear the destination path
   
  df.renameFile(actualFilePath, RenamedUnzippedFile)
   
   
  // Zip the renamed file (e.g.: 5536.png zipped to 5536.zip)
   
  string zippedFolder = QappCore.tempPath + separator + "Zipped"
  QappCore.makeDirectory(zippedFolder)
  ZippedFile = zippedFolder + separator + toString(df.IDDOCFILE) + ".zip"
  ZippedFile = df.ZipTheFile(RenamedUnzippedFile, ZippedFile)
   
  // load file in to DOCUMENT BLOB field
  df.DOCUMENT = loadBlobFile(ZippedFile)
   
  return df
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string DocFile.GetPathOfDownloadableDOCFILE(
  string FileExtension                           // 
  string FileBaseName                            // 
  inout int:downloadFileErrorTypes DownloadError // 
)
{
  // since sometimes fileextension is intended with . (".doc" instead than "doc", we remove the . and add it explicitly below)
  this.cleanFileExtension(FileExtension)
   
  // creo un filename GUID.zip
  string fileTemp = docIDToGuid(newDocID()) + ".zip"
  string zippedblob = saveBlobFile(DOCUMENT, QappCore.tempPath, fileTemp)
  string UnzippedFileName = ""
  string resultingFilePath = ""
  DownloadError = NoError
  // 
  QappCore.addTempFile(QappCore.tempPath + FH.getSeparator() + fileTemp)
  //  
   
  try 
  {
    // this variable is only used to call the unzip function, it is not used anywhere else
    string UnzippedFilePath = ""
    //  
    // unzip file, this can file in the following cases:
    // 1) when he doc is not zipped (in case DOC_FILES.IS_COMPRESSD is N, IS_COMPRESSED is not
    // handled currently)
    // 2) corruped file (likely because of filesystem failure or some mess made by DB admin)
     
    // non static method call: i need an object
     
    UnzippedFilePath = IDZip.unzip(zippedblob, QappCore.tempPath, ...)
    //  
    // we know that the unzipped file is IDDOCFILE.file extension
    // eg: a docx is likely stored in 123.zip containing 123.docx
    UnzippedFileName = QappCore.tempPath + FH.getSeparator() + toString(IDDOCFILE) + "." + FileExtension
    if (!(fileExists(UnzippedFileName)))
    {
      // if not found the only possible known cause is that the file was zipped with an uppercase file extension
      UnzippedFileName = QappCore.tempPath + FH.getSeparator() + toString(IDDOCFILE) + "." + upper(FileExtension)
    }
     
    string renamedFileName = Tools.RemoveBadFileNameChars(FileBaseName)
    resultingFilePath = QappCore.tempPath + FH.getSeparator() + renamedFileName + "." + FileExtension
     
    DevTools.RefactoringOpportunity("instead of inde rename file use EX.renameFile in 2023.3")
    if (fileExists(resultingFilePath))
    {
      QappCore.deleteFile(resultingFilePath)
    }
     
    this.renameFile(UnzippedFileName, resultingFilePath)
    QappCore.addTempFile(resultingFilePath)
     
  }
  catch 
  {
    DownloadError = ErrorCorruptedBlob
    return ""
  }
   
   
  return resultingFilePath
}


// ──────────────────────────────────

// ********************************************************************
// helper method that performs ".docx" -> "docx", ".tar.gz" -> "tar.gz"
// ********************************************************************
private void DocFile.cleanFileExtension(
  inout string fileExtension // 
)
{
  int fileExtensionLength = length(fileExtension)
   
  boolean startswithDot = left(fileExtension, 1) == "."
   
  if (startswithDot)
  {
    fileExtension = right(fileExtension, fileExtensionLength - 1)
  }
}


// ──────────────────────────────────

// *************************************************************
// renames the file adding it to the application temporary files
// *************************************************************
private void DocFile.renameFile(
  string OldFileName // 
  string NewFileName // 
)
{
  // no need to rename if both parameters match (this condition is hit in unit tests and could cause unwanted deletion of files)
  if (OldFileName == NewFileName)
    return 
   
  // this code is called twice so a private class method has been extracted
   
  // we delete destination file since renameFile will not work if a file exists at destination
  QappCore.deleteFile(NewFileName)
   
  QappCore.renameFile(OldFileName, NewFileName)
  QappCore.addTempFile(NewFileName)
   
   
}


// ──────────────────────────────────

// *********************************************************
// DOCUMENT blob field is updated with passed file
// It follows the following steps to update the blob field
// 1. it makes a copy of passed file (Based on parameter)
// 2. rename it with idDocFile.extension
// 3. Zip it
// 4. Finally zipped file is uploaded to DOCUMENT blob field
// 
// *********************************************************
public void DocFile.updateFromFile(
  string filePath                       // 
  optional boolean keepOriginalFile = 0 // 
)
{
  string fileExtension = SH.rightUpToDelimiter(filePath, ".", ...)
   
  string actualFilePath = filePath
  if (keepOriginalFile)
  {
    string newFilePath = QappCore.tempPath + FH.getSeparator() + docIDToGuid(newDocID()) + "." + fileExtension
    QappCore.copyFile(filePath, newFilePath)
    actualFilePath = newFilePath
  }
   
   
  string RenamedUnzippedFile = ""
  string ZippedFile = ""
   
   
   
  int fileSize = 0
  try 
  {
    fileSize = fileLength(actualFilePath)
  }
  catch 
  {
    fileSize = 0
  }
  FILESIZE = fileSize
  ISCOMPRESSED = Yes
   
   
  // rename modello document file to NEWID.extension (e.g.: 5536.png)
   
  string separator = FH.getSeparator()
  RenamedUnzippedFile = QappCore.tempPath + separator + toString(IDDOCFILE) + "." + fileExtension
   
  // since renaming won't work if the the destination path contains a valid file, so we forcefully clear the destination path
   
  this.renameFile(actualFilePath, RenamedUnzippedFile)
   
  string zippedFolder = QappCore.tempPath + separator + "Zipped"
  QappCore.makeDirectory(zippedFolder)
   
  // Zip the renamed file (e.g.: 5536.png zipped to 5536.zip)
  ZippedFile = zippedFolder + separator + toString(IDDOCFILE) + ".zip"
   
  ZippedFile = this.ZipTheFile(RenamedUnzippedFile, ZippedFile)
   
  // load file in to DOCUMENT BLOB field
  DOCUMENT = loadBlobFile(ZippedFile)
   
}


// ──────────────────────────────────

// *****************************************************
// static method to retrieve DocFile Object for given ID
// *****************************************************
public static DocFile DocFile.get(
  int ID // 
)
{
  DocFile df = new()
  df.IDDOCFILE = ID
  try 
  {
    df.loadFromDB(...)
  }
  catch 
  {
    df = null
    QappCore.DTTLogMessage(formatMessage("Unable to load DocFile Object for ID: |1", ID, ...), ..., DTTError)
  }
  return df
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event DocFile.OnInit()
{
  IDDOCFILE = Sequence.getNextSequence(DOCN_ID_FILE, ...)
  GUID = docIDToGuid(newDocID())
   
  IDTEMPLATE = null
  TEMPLATE = null
  IDPARENTTEMPLATEREMOTE = null
   
}


// ──────────────────────────────────

// *********************************************
// create doc verifica using modello Docverifica
// *********************************************
public DOCVERIFICHE DOCVERIFICHE.CreateVerificaFromModello(
  int IdUtente                 // 
  int IdRevision               // 
  DOCVERIFICHE ModelloVerifica // 
)
{
  DOCVERIFICHE docver = new()
  docver.init()
  docver.IDVERIFICA = Sequence.getNextSequence(DOCN_ID_VERIFICA, ...)
  docver.IDREVISIONE = IdRevision
  docver.IDUTENTEVERIFICATORE = IdUtente
  docver.DATAVERIFICA = now()
  docver.NOTE = ModelloVerifica.NOTE
  docver.IDEXPECTEDVERIFIER = IDEXPECTEDVERIFIER
  return docver
}


// ──────────────────────────────────

// ************************************************************
// Event raised to the document during the validation procedure
// ************************************************************
event DOCPARAMETRI.OnValidate(
  int:validateReasons Reason // An integer specifying the reason the document is being validated. The most common values are 0 to indicate complete validation before saving, or 1 to indicate a quick validati...
  inout boolean Error        // A boolean output parameter. If set to True, specifies an error condition and stops the validation immediately.
  inout boolean Skip         // A boolean output parameter. If set to True, does not check the rules for automatic validation of the document (see notes).
)
{
  if (deleted)
  {
    return 
  }
   
  if (DEFAULTTEXTREVISIONNOTEFIRSTREVISION == "")
  {
    Error = true
    this.setPropertyError("Impostare un testo di default note di revisione per "Prima revisione", DEFAULTTEXTREVISIONNOTEFIRSTREVISION)
  }
   
  if (DEFAULTTEXTREVISIONNOTEIMPORTDOC == "")
  {
    Error = true
    this.setPropertyError("Impostare un testo di default note di revisione per "Importazione documenti", DEFAULTTEXTREVISIONNOTEIMPORTDOC)
  }
   
  if (MAXALLOWEDSIZE == 0 or isNull(MAXALLOWEDSIZE))
  {
    Error = true
    this.setPropertyError("Impostare una dimensione massima consentita del documento", MAXALLOWEDSIZE)
  }
   
  if (AUTOLOGININTRANET == Yes)
  {
    if (IDUTENTEINTRANET == 0 or isNull(IDUTENTEINTRANET))
    {
      Error = true
      this.setPropertyError("Non è possibile attivare l'autologin se prima non si imposta un "Utente per Autologin"", AUTOLOGININTRANET)
    }
  }
}


// ──────────────────────────────────

// ***********************************************************
// Event raised to the document when the transaction is closed
// ***********************************************************
event DOCPARAMETRI.OnEndTransaction()
{
  this.validateOnEdit()
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public static int DOCPARAMETRI.GetIdWebUser()
{
  int vIDUTENTEINTRANETDOCPARAMETRI = 0
  select into variables (found variable)
    set vIDUTENTEINTRANETDOCPARAMETRI = IDUTENTEINTRANET
  from 
    DOCPARAMETRI // master table
  return vIDUTENTEINTRANETDOCPARAMETRI
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public static string DOCPARAMETRI.GetTitoloInternet()
{
  string vTITOLOINTRANETDOCPARAMETRI = ""
  select into variables (found variable)
    set vTITOLOINTRANETDOCPARAMETRI = TITOLOINTRANET
  from 
    DOCPARAMETRI // master table
  return vTITOLOINTRANETDOCPARAMETRI
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public static boolean DOCPARAMETRI.GetAutologinIntranet()
{
  string:flagYN vAUTOLOGININTRANETDOCPARAMETRI = ""
  select into variables (found variable)
    set vAUTOLOGININTRANETDOCPARAMETRI = AUTOLOGININTRANET
  from 
    DOCPARAMETRI // master table
  boolean boolAutologin = false
  if (vAUTOLOGININTRANETDOCPARAMETRI == Yes)
  {
    boolAutologin = true
  }
  return boolAutologin
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static int DOCPARAMETRI.getMaxAllowedSizeUploadFile()
{
  int vMAXALLOWEDSIZEDOCPARAMETRI = 0
  select into variables (found variable)
    set vMAXALLOWEDSIZEDOCPARAMETRI = MAXALLOWEDSIZE
  from 
    DOCPARAMETRI // master table
   
  int maxAllowedSizeInByte = vMAXALLOWEDSIZEDOCPARAMETRI * 1024 * 1024
  return maxAllowedSizeInByte
}


// ──────────────────────────────────

// ****************************************
// this method gives object of DocParametri
// ****************************************
public static DOCPARAMETRI DOCPARAMETRI.getInstance()
{
  IDCollection docParametriCollection of DOCPARAMETRI = new()
  select into collection (docParametriCollection)
  from 
    DOCPARAMETRI // master table
   
  if (docParametriCollection.count() > 1)
  {
    QappCore.DTTLogMessage("more than 1 record found in DocParametri", ..., DTTError)
  }
   
  docParametriCollection.moveFirst()
   
  return docParametriCollection.getAt()
}


// ──────────────────────────────────

// ***************************************************************************************
// get docTipoDocumento Object based on default id of TipoDocumento in doc parametri table
// ***************************************************************************************
public static DOCTIPIDOCUMENTO DOCPARAMETRI.getDefaultTipoDocumento()
{
  int defaultIdTipoDocumento = 0
  select into variables (found variable)
    set defaultIdTipoDocumento = IDTIPODOCDEFAULT
  from 
    DOCPARAMETRI // master table
   
  DOCTIPIDOCUMENTO doctipidocumento = new()
  if (defaultIdTipoDocumento > 0)
  {
    doctipidocumento = DOCTIPIDOCUMENTO.get(defaultIdTipoDocumento)
  }
  else 
  {
    doctipidocumento = null
  }
   
  return doctipidocumento
}


// ──────────────────────────────────



// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void DOCPARAMETRI.validateOnEdit()
{
  if (MAXALLOWEDSIZE == 0 or isNull(MAXALLOWEDSIZE))
  {
    MAXALLOWEDSIZE = 1
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void DOCPERMESSI.onChangeHandler(
  string changedProperty // 
)
{
  if (changedProperty == "LETTURA" and LETTURA == No)
  {
    this.setSecondaryBooleanProperties(No)
    ADMIN = No
  }
   
  if (changedProperty == "ADMIN" and ADMIN == Yes)
  {
    this.setSecondaryBooleanProperties(Yes)
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static DOCPERMESSI DOCPERMESSI.create(
  Utente utente     // 
  Cartella cartella // 
)
{
  DOCPERMESSI dp = new()
  dp.IDUTENTE = utente.IDUTENTE
  dp.IDCARTELLA = cartella.IDCARTELLA
  dp.IdReparto = utente.IDREPARTO
  dp.init()
   
  return dp
}


// ──────────────────────────────────

// ************************************************************************************
// centralize way to set all properties but "Admin" and "Lettura" in a single operation
// ************************************************************************************
private void DOCPERMESSI.setSecondaryBooleanProperties(
  string:flagYN value // 
)
{
  MODIFICA = value
  VERIFICA = value
  APPROVAZIONE = value
  DISTRIBUZIONE = value
  REVISIONE = value
  INSERIMENTO = value
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean DOCPERMESSI.allPropertiesAreUnchecked()
{
  boolean allAreUnchecked = LETTURA == No and MODIFICA == No and VERIFICA == No and APPROVAZIONE == No and DISTRIBUZIONE == No and ADMIN == No and REVISIONE == No and INSERIMENTO == No
   
  return allAreUnchecked
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void DOCPERMESSI.loadAdditionalProperties()
{
  if (IdReparto <= 0)
  {
    Utente u = Utente.get(IDUTENTE)
    IdReparto = u.IDREPARTO
    this.setOriginal()
  }
}


// ──────────────────────────────────

// ***********************************************************
// Event raised to the document when the transaction is closed
// ***********************************************************
event DOCPERMESSI.OnEndTransaction()
{
  if (wasModified(ADMIN))
  {
    this.onChangeHandler("ADMIN")
  }
  else if (wasModified(LETTURA))
  {
    this.onChangeHandler("LETTURA")
  }
}


// ──────────────────────────────────



// ──────────────────────────────────

// **************************************************************************************
// Event raised to the document during the initial steps of the document saving procedure
// **************************************************************************************
event throws exception DOCPERMESSI.BeforeSave(
  inout boolean Skip         // A boolean output parameter. If set to True, tells the framework not to perform operations on the database, because they will be managed independently.
  inout boolean Cancel       // A boolean output parameter. If set to True, tells the framework to cancel the save procedure due to errors that occurred. Since the entire document is saved in the same transa...
  int:beforeSavePhases Phase // An integer from 0 to 3 that specifies the progress status of the save procedure as detailed at the beginning of the article.
)
{
  if (this.allPropertiesAreUnchecked())
  {
    this.deleted = true
  }
}


// ──────────────────────────────────

// ************************************************************
// Event raised by the document after loading from the database
// ************************************************************
event DOCPERMESSI.AfterLoad(
  boolean AlreadyLoaded     // A boolean parameter that specifies whether the document was previously loaded.
  IDArray LoadedCollections // IDArray type parameter that contains the collections loaded by the framework. If the value of the Already Loaded parameter is False, the Loaded Collections parameter is NULL.
)
{
   
  this.loadAdditionalProperties()
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static DOCTIPIDOCUMENTO DOCTIPIDOCUMENTO.get(
  int idTipoDocumento // 
)
{
  DOCTIPIDOCUMENTO tipoDocumento = new()
  tipoDocumento.IDTIPODOCUMENTO = idTipoDocumento
  try 
  {
    tipoDocumento.loadFromDB(...)
  }
  catch 
  {
    tipoDocumento = null
    QappCore.DTTLogMessage(formatMessage("Unable to load tipo documento for idTipoDocumento: |1", idTipoDocumento, ...), ..., DTTError)
  }
  return tipoDocumento
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void DOCTIPIDOCUMENTO.validateOnEdit()
{
  // if i put to no the notification check i put to 0 only the corrisponding days and if i put check notification to yes i put to 1 only the corrisponding days
  if (wasModified(NOTIFYINADVANCE))
  {
    if (NOTIFYINADVANCE == No)
      NOTIFYADVANCEDAYS = 0
    else 
      NOTIFYADVANCEDAYS = 1
  }
  if (wasModified(NOTIFYDELAYS))
  {
    if (NOTIFYDELAYS == No)
      NOTIFYDELAYSDAYS = 0
    else 
      NOTIFYDELAYSDAYS = 1
  }
  if (wasModified(NOTIFYDELAYSCONTINUE))
  {
    if (NOTIFYDELAYSCONTINUE == No)
      NOTIFYDELAYSCONTINUEDAYS = 0
    else 
      NOTIFYDELAYSCONTINUEDAYS = 1
  }
   
  // if i modify the days value i put to 0 only if the corrisponding check is no
  if (wasModified(NOTIFYDELAYSDAYS))
  {
    if (NOTIFYDELAYS == No)
      NOTIFYDELAYSDAYS = 0
  }
  if (wasModified(NOTIFYADVANCEDAYS))
  {
    if (NOTIFYINADVANCE == No)
      NOTIFYADVANCEDAYS = 0
  }
  if (wasModified(NOTIFYDELAYSCONTINUEDAYS))
  {
    if (NOTIFYDELAYSCONTINUE == No)
      NOTIFYDELAYSCONTINUEDAYS = 0
  }
   
  // if verifica and approva is yes , nro verifiche putted to 1, if is no and nroverifiche is greater than 99 we set the max of nro verifiche to 99
  if (wasModified(NROVERIFICHE) or wasModified(VERIFICAEAPPROVA))
  {
    if (VERIFICAEAPPROVA == Yes)
    {
      NROVERIFICHE = 1
    }
    else 
    {
      if (NROVERIFICHE > 99)
      {
         NROVERIFICHE = 99
      }
    }
  }
   
   
  if (wasModified(MODIFICABILE))
  {
    if (MODIFICABILE == Yes)
    {
      CREAZIONEPDF = No
      AUTOSUBSTITUTE = No
    }
  }
   
  if (wasModified(CREAZIONEPDF) or wasModified(AUTOSUBSTITUTE))
  {
    if (CREAZIONEPDF == Yes or AUTOSUBSTITUTE == Yes)
    {
      MODIFICABILE = No
    }
  }
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event DOCTIPIDOCUMENTO.OnInit()
{
  IDTIPODOCUMENTO = Sequence.getNextSequence(DOCN_ID_TABELLE, ...)
  DESCRTIPODOCUMENTO = "Nuovo tipo documento"
  INTERNO = "N"
  MODELLO = "N"
  MODIFICABILE = "N"
  REVISIONI = "N"
  CODICEOBBLIGATORIO = "N"
  ATTIVO = "N"
  VERIFICAEAPPROVA = "N"
  DUPLICAZIONECODICE = "N"
  NROVERIFICHE = 1
  MESICONSERVAZIONE = 1
  CONFERMANOTIFICA = "N"
  CREAZIONEPDF = "N"
  AUTOSUBSTITUTE = "N"
  USEROLESFORMULTIVER = "N"
  VERIFICAAUTOMESSAGE = "N"
  NOTIFYRESPONSIBLE = "N"
  NOTIFYEXECUTOR = "N"
  NOTIFYINADVANCE = "N"
  NOTIFYONEXECUTION = "N"
  NOTIFYONCLOSE = "N"
  NOTIFYDELAYS = "N"
  NOTIFYINSUSER = "N"
  NOTIFYDELAYSCONTINUE = "N"
   
  NOTIFYDELAYSDAYS = 0
  NOTIFYADVANCEDAYS = 0
  NOTIFYDELAYSCONTINUEDAYS = 0
   
  DocUbicazioni du = DOCPARAMETRI.getDefaultUbicazione()
  if (du)
  {
    IDUBICAZIONE = du.IDUBICAZIONE
  }
   
  // as C/S
  IDTIPOSUPPORTO = 3
}


// ──────────────────────────────────

// ************************************************************
// Event raised to the document during the validation procedure
// ************************************************************
event DOCTIPIDOCUMENTO.OnValidate(
  int:validateReasons Reason // An integer specifying the reason the document is being validated. The most common values are 0 to indicate complete validation before saving, or 1 to indicate a quick validati...
  inout boolean Error        // A boolean output parameter. If set to True, specifies an error condition and stops the validation immediately.
  inout boolean Skip         // A boolean output parameter. If set to True, does not check the rules for automatic validation of the document (see notes).
)
{
  if (deleted)
  {
    return 
  }
   
  if (DESCRTIPODOCUMENTO == "")
  {
    Error = true
    this.setPropertyError("Il campo deve essere compilato. ", DESCRTIPODOCUMENTO)
  }
   
  boolean ReceiverSelected = NOTIFYRESPONSIBLE == Yes or NOTIFYEXECUTOR == Yes or NOTIFYINSUSER == Yes or (length(NOTIFYOTHERS) > 0)
  boolean NotificationTypeSelected = NOTIFYDELAYSCONTINUE == Yes or NOTIFYDELAYS == Yes or NOTIFYINADVANCE == Yes or NOTIFYONEXECUTION == Yes
  if (!(ReceiverSelected) and NotificationTypeSelected)
  {
    if (NOTIFYDELAYSCONTINUE == Yes)
    {
      Error = true
      this.setPropertyError(formatMessage("Seleziona almeno un destinatario. (Tipo documento: |1)", DESCRTIPODOCUMENTO, ...), NOTIFYDELAYSCONTINUE)
    }
    if (NOTIFYDELAYS == Yes)
    {
      Error = true
      this.setPropertyError(formatMessage("Seleziona almeno un destinatario. (Tipo documento: |1)", DESCRTIPODOCUMENTO, ...), NOTIFYDELAYS)
    }
    if (NOTIFYINADVANCE == Yes)
    {
      Error = true
      this.setPropertyError(formatMessage("Seleziona almeno un destinatario. (Tipo documento: |1)", DESCRTIPODOCUMENTO, ...), NOTIFYINADVANCE)
    }
    if (NOTIFYONEXECUTION == Yes)
    {
      Error = true
      this.setPropertyError(formatMessage("Seleziona almeno un destinatario. (Tipo documento: |1)", DESCRTIPODOCUMENTO, ...), NOTIFYONEXECUTION)
    }
  }
   
  if (!(NotificationTypeSelected) and ReceiverSelected)
  {
    if (NOTIFYRESPONSIBLE == Yes)
    {
      Error = true
      this.setPropertyError(formatMessage("Imposta un tipo di notifica. (Tipo documento: |1)", DESCRTIPODOCUMENTO, ...), NOTIFYRESPONSIBLE)
    }
    if (NOTIFYEXECUTOR == Yes)
    {
      Error = true
      this.setPropertyError(formatMessage("Imposta un tipo di notifica. (Tipo documento: |1)", DESCRTIPODOCUMENTO, ...), NOTIFYEXECUTOR)
    }
    if (NOTIFYINSUSER == Yes)
    {
      Error = true
      this.setPropertyError(formatMessage("Imposta un tipo di notifica. (Tipo documento: |1)", DESCRTIPODOCUMENTO, ...), NOTIFYINSUSER)
    }
    if (length(NOTIFYOTHERS) > 0)
    {
      Error = true
      this.setPropertyError(formatMessage("Imposta un tipo di notifica. (Tipo documento: |1)", DESCRTIPODOCUMENTO, ...), NOTIFYOTHERS)
    }
  }
   
}


// ──────────────────────────────────

// ***********************************************************
// Event raised to the document when the transaction is closed
// ***********************************************************
event DOCTIPIDOCUMENTO.OnEndTransaction()
{
  this.validateOnEdit()
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static ModuleDocument ModuleDocument.create(
  string filePath                // 
  int mainId                     // 
  int:kordapp kordapp            // 
  string Description             // 
  Utente creatorUser             // 
  optional int IDDocComGroup = 0 // 
  optional boolean keepOriginalFile = 0 // 
)
{
  ModuleDocument md = new()
  md.DocFile = DocFile.createFromFile(filePath, keepOriginalFile)
   
  md.init()
  md.COMPUTERNAME = null
  string extension = SH.rightUpToDelimiter(filePath, ".", ...)
  md.ESTENSIONEFILE = "." + extension
  md.computeIcon()
  md.IDORIGINE = kordapp
  md.DESCRIZIONE = Description
  md.IDUTENTECREATORE = creatorUser.IDUTENTE
  md.IDUTENTEULTMOD = creatorUser.IDUTENTE
  if (IDDocComGroup > 0)
  {
    md.IDDOCCOMMGROUP = IDDocComGroup
  }
  md.IDFILE = md.DocFile.IDDOCFILE
  md.IDDOCFILE = md.DocFile.IDDOCFILE
   
  switch (kordapp)
  {
    case Eventi:
      md.IDEVENTO = mainId
    break
    case Articoli:
      md.IDARTICOLO = mainId
    break
    case Funzioni:
      md.IDFUNZIONE = mainId
    break
    case Progetti:
      md.IDPROGETTO = mainId
    break
    case Personale:
      md.IDDIPENDENTE = mainId
    break
    case Privati:
      md.IDCITTADINI = mainId
    break
    case ClientiFornitori:
      md.IDCONTO = mainId
    break
    case Interventi:
      md.IDTESTATAOP = mainId
    break
    default:
      QappCore.DTTLogMessage("Kordapp type not handled", ..., DTTError)
    break
  }
   
   
   
  return md
}


// ──────────────────────────────────

// ***************************************
// Returns the path for the extracted File
// ***************************************
public string ModuleDocument.getFilePath(
  optional inout int:downloadFileErrorTypes downloadError = 0 // 
)
{
  DocFile = new()
  DocFile.IDDOCFILE = IDDOCFILE
  DocFile.loadFromDB(0)
   
  string extractedFilePath = DocFile.GetPathOfDownloadableDOCFILE(ESTENSIONEFILE, DESCRIZIONE, downloadError)
   
  if (downloadError != NoError)
  {
    QappCore.DTTLogMessage(formatMessage("Error found: |1", decode(downloadError, DownloadFileErrorTypes), ...), ..., DTTError)
  }
   
  return extractedFilePath
   
   
   
   
}


// ──────────────────────────────────

// ****************************************************
// mark checkout by updating fields related to checkout
// ****************************************************
private void ModuleDocument.markCheckout()
{
  IDUTENTEESTR = QappCore.Loggeduser.IDUTENTE
  DATAESTRAZIONE = now()
}


// ──────────────────────────────────

// ***************************************************
// mark checkin by clearing fields related to checkout
// ***************************************************
public void ModuleDocument.markCheckin()
{
  IDUTENTEESTR = null
  DATAESTRAZIONE = null
  COMPUTERNAME = null
  CHECKEDOUTFILE = null
}


// ──────────────────────────────────

// ******************************************
// method to checkin the given document file 
// ******************************************
public void ModuleDocument.checkin(
  string filePath // 
)
{
  DocFile.updateFromFile(filePath, true)
  string fileExtension = "." + SH.rightUpToDelimiter(filePath, ".", ...)
  ESTENSIONEFILE = fileExtension
  this.markCheckin()
}


// ──────────────────────────────────

// ******************************************************************
// "Estrai in modifica" method
// 
// idFormForOnlinEditor must be passed only when mode is onlineEditor
// ******************************************************************
public CheckOutAction ModuleDocument.checkout(
  optional string:estraiInModificaModes mode = "download"       // 
  optional string:executionModes executionMode = "unitTestMode" // use unitTestMode in unittests to avoid executing client side operations such as downlaod or open online document editor
  optional IDForm idFormForOnlineEditor                         // 
)
{
  CheckOutAction checkOutAction = null
   
  if (mode == online_editor)
  {
    if (idFormForOnlineEditor == null)
      QappCore.DTTLogMessage("It is mandatory to pass idFormForOnlineEditor in case mode==online_editor!", ..., DTTError)
  }
   
  IdFormForOnlineEditor = idFormForOnlineEditor
   
  boolean canBeCheckedOut = this.canBeCheckedoutBy(...)
  if (canBeCheckedOut)
  {
    this.markCheckout()
  }
  else 
  {
    QappCore.DTTLogMessage("user cannot checkout the document", ..., DTTWarning)
  }
   
  string requiredClassName = ""
  switch (mode)
  {
    case download:
      requiredClassName = DownloadAction.className(...)
    break
    case online_editor:
      requiredClassName = OnlineEditorOpenAction.className(...)
    break
    default:
      QappCore.DTTLogMessage("unsupported case :" + mode, ..., DTTError)
    break
  }
   
  checkOutAction = CheckOutAction.create(this, requiredClassName)
   
  boolean unitTestMode = executionMode == unitTestMode
  checkOutAction.perform(unitTestMode)
   
  return checkOutAction
}


// ──────────────────────────────────

// *********************************************************
// returns true if the passed user can checkout the document
// *********************************************************
public boolean ModuleDocument.canBeCheckedoutBy(
  optional Utente utente // 
)
{
  // use passed user ot logged user (in case no user is passed)
  Utente utenteToCheckAgainst = null
  if (utente != null)
  {
    utenteToCheckAgainst = utente
  }
  else 
  {
    utenteToCheckAgainst = QappCore.Loggeduser
  }
   
  // decide if the user can checkout: either doc is already checked out or not
  boolean documentIsAlreadyCheckedOutbyTheUser = IDUTENTEESTR == utenteToCheckAgainst.IDUTENTE
  boolean utenteCanCheckout = false
  if (documentIsAlreadyCheckedOutbyTheUser)
  {
    utenteCanCheckout = false
  }
  else 
  {
     
    boolean documentIsNotCheckedOut = IDUTENTEESTR == null
     
    if (documentIsNotCheckedOut)
    {
      utenteCanCheckout = true
    }
    else 
    {
      utenteCanCheckout = documentIsAlreadyCheckedOutbyTheUser
    }
  }
   
  return utenteCanCheckout
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean ModuleDocument.isCheckedOut()
{
  return IDUTENTEESTR > 0
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void ModuleDocument.undoCheckout()
{
  IDUTENTEESTR = null
  DATAESTRAZIONE = null
  COMPUTERNAME = null
  CHECKEDOUTFILE = null
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void ModuleDocument.computeIcon()
{
  // we return icon + " " + estensione because in this way iconHtml contains both image and text and it can be used directly in panels
  IconHtml = Tools.computeHtmlIcon(ESTENSIONEFILE)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean ModuleDocument.isCheckedOutForDownload()
{
  boolean isCheckedOutForDownload = CHECKEDOUTFILE == "D"
  return isCheckedOutForDownload
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void ModuleDocument.markAsCheckedOutForDownload()
{
  CHECKEDOUTFILE = "D"
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean ModuleDocument.isCheckoutWithDownloadPossible()
{
  boolean checkoutWithDownloadIsAvailable = false
   
  boolean documentIsCheckedOut = IDUTENTEESTR > 0
  boolean canCheckout = this.canBeCheckedoutBy(...)
  checkoutWithDownloadIsAvailable = !(documentIsCheckedOut) and canCheckout
  return checkoutWithDownloadIsAvailable
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean ModuleDocument.IsCheckoutWithOOPossible()
{
  boolean checkoutWithOOIsAvailable = false
   
  boolean documentIsCheckedOut = IDUTENTEESTR > 0
  boolean canCheckout = this.canBeCheckedoutBy(...)
  boolean docExtensionIsSupportedByOO = OnlyOfficeTools.extensionIsSupportedByEditor(ESTENSIONEFILE)
  checkoutWithOOIsAvailable = !(documentIsCheckedOut) and canCheckout and (docExtensionIsSupportedByOO)
   
  return checkoutWithOOIsAvailable
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean ModuleDocument.isOnlineEditorOpened()
{
  return OnlineEditorOpened
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void ModuleDocument.setOnlineEditorOpened(
  boolean value // 
)
{
  OnlineEditorOpened = value
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public void ModuleDocument.reload()
{
  this.loaded = false
  this.loadFromDB(...)
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event ModuleDocument.OnInit()
{
  DATACREAZIONE = now()
  COMPUTERNAME = ""
  CHECKEDOUTFILE = ""
}


// ──────────────────────────────────

// ************************************************************
// Event raised to the document during the validation procedure
// ************************************************************
event ModuleDocument.OnValidate(
  int:validateReasons Reason // An integer specifying the reason the document is being validated. The most common values are 0 to indicate complete validation before saving, or 1 to indicate a quick validati...
  inout boolean Error        // A boolean output parameter. If set to True, specifies an error condition and stops the validation immediately.
  inout boolean Skip         // A boolean output parameter. If set to True, does not check the rules for automatic validation of the document (see notes).
)
{
   
  switch (IDORIGINE)
  {
    case Eventi:
      if (isNull(IDEVENTO))
      {
         this.setPropertyError("ID_EVENTO must be set for this ID_ORIGINE", IDEVENTO)
      }
    break
  }
   
  if (DocFile == null)
  {
    this.setPropertyError("Il file non è valido, contattare un amministratore.", DESCRIZIONE)
    Error = true
  }
}


// ──────────────────────────────────

// **************************************************************************************
// Event raised to the document during the initial steps of the document saving procedure
// **************************************************************************************
event throws exception ModuleDocument.BeforeSave(
  inout boolean Skip         // A boolean output parameter. If set to True, tells the framework not to perform operations on the database, because they will be managed independently.
  inout boolean Cancel       // A boolean output parameter. If set to True, tells the framework to cancel the save procedure due to errors that occurred. Since the entire document is saved in the same transa...
  int:beforeSavePhases Phase // An integer from 0 to 3 that specifies the progress status of the save procedure as detailed at the beginning of the article.
)
{
  if (Phase == 0)
  {
    if (inserted or updated)
    {
      DATAULTIMAMOD = now()
    }
     
  }
   
  if (DocFile != null)
  {
    if (inserted or DocFile.updated)
    {
      DocFile.saveToDB(0, ...)
    }
    if (deleted)
    {
      DocFile.deleted = true
      DocFile.saveToDB(0, ...)
    }
  }
  else 
  {
    QappCore.DTTLogMessage(formatMessage("Doc file for the file: |1 is null.", DESCRIZIONE, ...), ..., DTTError)
    return 
  }
   
}


// ──────────────────────────────────

// ************************************************************
// Event raised by the document after loading from the database
// ************************************************************
event ModuleDocument.AfterLoad(
  boolean AlreadyLoaded     // A boolean parameter that specifies whether the document was previously loaded.
  IDArray LoadedCollections // IDArray type parameter that contains the collections loaded by the framework. If the value of the Already Loaded parameter is False, the Loaded Collections parameter is NULL.
)
{
   
  DevTools.ToBeReviewed("It would be better to load the BLOB only when we really need it, such on extract and open, it should be postponed")
  DocFile = DocFile.get(IDDOCFILE)
  this.computeIcon()
  this.setOriginal()
}


// ──────────────────────────────────

// ********************************************************************
// Event raised to the document before loading a collection of its type
// ********************************************************************
event ModuleDocument.BeforeLoadCollection(
  IDDocument Parent                     // The parent document that requested loading of the collection. It can be Null Object if the event is raised by the LoadCollectionByExample procedure.
  IDCollection Collection of IDDocument // The collection to be loaded.
  inout boolean Skip                    // A boolean output parameter. If set to True, standard loading will not be performed.
)
{
  if (!(MainModule.isMyInstance(Parent)))
  {
    return 
  }
  if (Collection.loaded)
  {
    return 
  }
  Skip = true
  MainModule parentMainModule = (MainModule)Parent
   
  int:kordapp kordApp = parentMainModule.getKordApp()
  int mainId = parentMainModule.getMainID()
   
  IDCollection moduleDocumets of ModuleDocument = new()
  select into collection (moduleDocumets)
  from 
    ModuleDocument // master table
  where
    IDORIGINE == kordApp
    (kordApp == ClientiFornitori and IDCONTO == mainId) or (kordApp == Eventi and IDEVENTO == mainId) or (kordApp == Personale and IDDIPENDENTE == mainId) or (kordApp == Articoli and
       IDARTICOLO == mainId) or (kordApp == AltreAnagrafiche and IDCESPITE == mainId) or (kordApp == Progetti and IDPROGETTO == mainId) or (kordApp == Privati and IDCITTADINI == mainId) or (
       kordApp == Interventi and IDTESTATAOP == mainId) or (kordApp == Funzioni and IDFUNZIONE == mainId)
   
  // IMPORTANT: we do Move because otherwise the collection does not "sense" it has the document
  Collection.addAll(moduleDocumets, ...)
  Collection.loaded = true
   
  // since the collection is just retrieved we mark as original or it will look modified
  Collection.setOriginal()
}


// ──────────────────────────────────

// ****************************************************************************
// Event raised to the document to determine the definition of a named property
// ****************************************************************************
event ModuleDocument.OnGetNamedPropertyDefinition(
  string PropertyName                     // The name of the named property whose definition is sought.
  IDPropertyDefinition PropertyDefinition // The object of the IDPropertyDefinition type that will be used to communicate the property definition to the caller.
)
{
  if (PropertyName == "UPDOWN_ICON")
  {
    PropertyDefinition.dataType = Character
  }
}


// ──────────────────────────────────

// ***********************************************************************
// Event raised to the document to determine the value of a named property
// ***********************************************************************
event ModuleDocument.OnGetNamedPropertyValue(
  string PropertyName        // The name of the named property whose value to retrieve.
  inout string PropertyValue // An output parameter containing the value of the named property.
)
{
  if (PropertyName == "UPDOWN_ICON")
  {
    if (this.isCheckedOut())
    {
      PropertyValue = "{{icon-fa-upload}}"
    }
    else 
    {
      PropertyValue = "{{icon-fa-download}}"
    }
     
    if (!(this.isCheckedOutForDownload()) and this.isCheckedOut())
    {
      PropertyValue = "{{icon-fa-pencil-square-o}}"
    }
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void CheckOutAction.perform(
  optional boolean unitTestMode = 0 // 
)
{
  QappCore.DTTLogMessage("ABSTRACT", ..., DTTError)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static CheckOutAction CheckOutAction.create(
  ModuleDocument parent // 
  string className      // 
)
{
  CheckOutAction result = null
   
  switch (className)
  {
    case DownloadAction.className(...):
      DownloadAction da = new()
      result = da
    break
    case OnlineEditorOpenAction.className(...):
      OnlineEditorOpenAction oeoa = new()
      result = oeoa
    break
    default:
      QappCore.DTTLogMessage("Unsupported: " + className, ..., DTTError)
      result = null
    break
  }
   
  if (result)
  {
    result.ModuleDocument = parent
  }
  return result
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void DownloadAction.perform(
  optional boolean unitTestMode = 0 // 
)
{
  ModuleDocument md = ModuleDocument
   
  int:downloadFileErrorTypes downloadError = 0
  string filePath = md.getFilePath(downloadError)
  if (downloadError == NoError)
  {
     
    // avoid doing the actual action i unit tests
    if (!(unitTestMode))
    {
      QappCore.OpenDocumentManager.downloadFile(filePath)
    }
    md.markAsCheckedOutForDownload()
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void OnlineEditorOpenAction.perform(
  optional boolean unitTestMode = 0 // 
)
{
  ModuleDocument md = ModuleDocument
  md.setOnlineEditorOpened(true)
   
  if (!(unitTestMode))
  {
    string fileWithPath = OnlyOfficeDocumentOpener.open(md, md.IdFormForOnlineEditor, edit)
    md.CHECKEDOUTFILE = fileWithPath
  }
  else 
  {
    md.CHECKEDOUTFILE = "UNIT TEST FILE PATH"
  }
  md.computeIcon()
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static SW9DOCUMENTLOG SW9DOCUMENTLOG.create(
  int idFile // 
)
{
  SW9DOCUMENTLOG sw9documentlog = new()
  sw9documentlog.init()
  sw9documentlog.IDFILE = idFile
  sw9documentlog.IDUTENTE = QappCore.Loggeduser.IDUTENTE
  sw9documentlog.IDORIGINE = 0
  sw9documentlog.PGMNAME = QappCore.QappWeb.Name
  sw9documentlog.WINUSER = QappCore.Tbluserparameters.Winuser
  sw9documentlog.DBUSER = QappCore.Loggeduser.Username
  sw9documentlog.COMPUTERNAME = QappCore.Tbluserparameters.Computername
  sw9documentlog.ACCESSDATE = now()
  sw9documentlog.ACCESSOP = "-1"
  return sw9documentlog
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static ListaDiDistribuzione ListaDiDistribuzione.getFromDB(
  int idListaDistrib // 
)
{
  ListaDiDistribuzione ldd = new()
  ldd.IDLISTADISTRIB = idListaDistrib
  try 
  {
    ldd.loadFromDB(...)
  }
  catch 
  {
    QappCore.DTTLogMessage("Lista di distribuzione not found", ..., DTTError)
    ldd = null
  }
   
  return ldd
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public IDCollection ListaDiDistribuzione.getRigheLista()
{
  Righe.clear()
  for each row (readonly)
  {
    select
      vID = IDRIGALISTADISTR
      IDDipendente = IDDIPENDENTE
      IDContatto = IDCONTATTO
    from 
      DOCLISTEDISTRIBRIGHE // master table
    where
      IDLISTADISTRIB == IDLISTADISTRIB
     
    if (IDDipendente > 0)
    {
      RigaListaDistribuzionePersonale rldp = new()
      rldp.IDRIGALISTADISTR = vID
      rldp.loadFromDB(0)
      rldp.loadAdditionalProperties()
      Righe.add(rldp)
    }
    else if (IDContatto > 0)
    {
      RigaListaDistribuzioneContatto rldc = new()
      rldc.IDRIGALISTADISTR = vID
      rldc.loadFromDB(0)
      rldc.loadAdditionalProperties()
      Righe.add(rldc)
    }
  }
  return Righe
}


// ──────────────────────────────────

// *********************************************************************************************
// this method populates the passed collections with the personale and contatto specific records
// *********************************************************************************************
public void ListaDiDistribuzione.computeSpecificLists()
{
  if (!(RigaListaDistribuzionePersonale.loaded))
  {
    this.loadCollectionFromDB(RigaListaDistribuzionePersonale, ...)
  }
  if (!(RigaListaDistribuzioneContatto.loaded))
  {
    this.loadCollectionFromDB(RigaListaDistribuzioneContatto, ...)
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static ListaDiDistribuzione ListaDiDistribuzione.create(
  string description // 
)
{
  ListaDiDistribuzione ldd = new()
  ldd.init()
  ldd.DESCRLISTADISTRIB = description
  return ldd
}


// ──────────────────────────────────

// ********************************************************************
// this method is not tested but removeInactivePersonaleFromAllRighe is
// ********************************************************************
public void ListaDiDistribuzione.removeInactivePersonaleFromAllListeDistribuzione()
{
  IDCollection coll of ListaDiDistribuzione = parentCollection()
   
  for each ListaDiDistribuzione ldd in coll
  {
    ldd.removeInactivePersonaleFromAllRighe()
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void ListaDiDistribuzione.removeInactivePersonaleFromAllRighe()
{
  for each RigaListaDistribuzionePersonale rldp in RigaListaDistribuzionePersonale
  {
    Personale p = rldp.getPersonale()
    if (!(p))
    {
      rldp.loadAdditionalProperties()
      p = rldp.getPersonale()
    }
    if (p.STATO != Attivo)
    {
      rldp.deleted = true
    }
  }
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event ListaDiDistribuzione.OnInit()
{
  IDLISTADISTRIB = Sequence.getNextSequence(TABN_ID_TABELLE_VARIE, ...)
}


// ──────────────────────────────────

// ************************************************************
// Event raised by the document after loading from the database
// ************************************************************
event ListaDiDistribuzione.AfterLoad(
  boolean AlreadyLoaded     // A boolean parameter that specifies whether the document was previously loaded.
  IDArray LoadedCollections // IDArray type parameter that contains the collections loaded by the framework. If the value of the Already Loaded parameter is False, the Loaded Collections parameter is NULL.
)
{
  this.computeSpecificLists()
  this.setOriginal()
}


// ──────────────────────────────────

// **************************************************************************************
// Event raised to the document during the initial steps of the document saving procedure
// **************************************************************************************
event throws exception ListaDiDistribuzione.BeforeSave(
  inout boolean Skip         // A boolean output parameter. If set to True, tells the framework not to perform operations on the database, because they will be managed independently.
  inout boolean Cancel       // A boolean output parameter. If set to True, tells the framework to cancel the save procedure due to errors that occurred. Since the entire document is saved in the same transa...
  int:beforeSavePhases Phase // An integer from 0 to 3 that specifies the progress status of the save procedure as detailed at the beginning of the article.
)
{
  if (this.deleted == true)
  {
    for each RigaListaDistribuzionePersonale rldp in RigaListaDistribuzionePersonale
    {
      rldp.deleted = true
    }
     
    for each RigaListaDistribuzioneContatto rldc in RigaListaDistribuzioneContatto
    {
      rldc.deleted = true
    }
  }
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event RigaListaDistribuzione.OnInit()
{
  IDRIGALISTADISTR = Sequence.getNextSequence(TABN_ID_TABELLE_VARIE, ...)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string RigaListaDistribuzione.getRecipientDescription()
{
  return null
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string RigaListaDistribuzione.getRecipientCompanyDescription()
{
  return null
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string RigaListaDistribuzione.getEmailAddress()
{
  return null
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void RigaListaDistribuzione.loadAdditionalProperties()
{
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string RigaListaDistribuzionePersonale.getRecipientDescription()
{
  string description = ""
  if (Personale)
    description = formatMessage("|1 |2", Personale.NOME, Personale.COGNOME, ...)
   
  return description
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string RigaListaDistribuzionePersonale.getRecipientCompanyDescription()
{
  string companyDescription = ""
  select into variables (found variable)
    set companyDescription = DESCRDITTA1
  from 
    TABPARAMETRI1 // master table
  return companyDescription
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string RigaListaDistribuzionePersonale.getEmailAddress()
{
  string recipientEmailAddress = null
  if (Personale)
    recipientEmailAddress = Personale.EMAIL
   
  return recipientEmailAddress
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void RigaListaDistribuzionePersonale.loadAdditionalProperties()
{
  Personale.IDDIPENDENTE = IDDIPENDENTE
  try 
  {
    Personale.loadFromDB(0)
    RecipientDescription = this.getRecipientDescription()
    CompanyDescription = this.getRecipientCompanyDescription()
    Email = this.getEmailAddress()
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static RigaListaDistribuzionePersonale RigaListaDistribuzionePersonale.create(
  int idListaDistrib // 
  int idDipendente   // 
)
{
  RigaListaDistribuzionePersonale rld = new()
  rld.init()
  rld.IDLISTADISTRIB = idListaDistrib
  rld.NROCOPIE = 0
  rld.TIPODISTRIBUZIONE = MessaggioInterno
   
  rld.IDDIPENDENTE = idDipendente
  rld.loadAdditionalProperties()
   
  return rld
   
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public Personale RigaListaDistribuzionePersonale.getPersonale()
{
  return Personale
}


// ──────────────────────────────────

// ************************************************************
// Event raised by the document after loading from the database
// ************************************************************
event RigaListaDistribuzionePersonale.AfterLoad(
  boolean AlreadyLoaded     // A boolean parameter that specifies whether the document was previously loaded.
  IDArray LoadedCollections // IDArray type parameter that contains the collections loaded by the framework. If the value of the Already Loaded parameter is False, the Loaded Collections parameter is NULL.
)
{
  this.loadAdditionalProperties()
  this.setOriginal()
}


// ──────────────────────────────────

// ************************************************************
// Event raised by the document after loading from the database
// ************************************************************
event RigaListaDistribuzioneContatto.AfterLoad(
  boolean AlreadyLoaded     // A boolean parameter that specifies whether the document was previously loaded.
  IDArray LoadedCollections // IDArray type parameter that contains the collections loaded by the framework. If the value of the Already Loaded parameter is False, the Loaded Collections parameter is NULL.
)
{
  this.loadAdditionalProperties()
  this.setOriginal()
}


// ──────────────────────────────────

// ************************************************************
// Event raised to the document during the validation procedure
// ************************************************************
event RigaListaDistribuzioneContatto.OnValidate(
  int:validateReasons Reason // An integer specifying the reason the document is being validated. The most common values are 0 to indicate complete validation before saving, or 1 to indicate a quick validati...
  inout boolean Error        // A boolean output parameter. If set to True, specifies an error condition and stops the validation immediately.
  inout boolean Skip         // A boolean output parameter. If set to True, does not check the rules for automatic validation of the document (see notes).
)
{
  if (!(deleted))
  {
    if (nullValue(Email, "") == "")
    {
      this.setPropertyError("Per ogni soggetto esterno va impostato un indirizzo email", Email)
      Error = true
      this.refreshUserInterface()
    }
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string RigaListaDistribuzioneContatto.getRecipientDescription()
{
  string description = ""
  if (Contatto)
  {
    description = formatMessage("|1 |2", Contatto.NOME, Contatto.COGNOME, ...)
  }
  return description
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string RigaListaDistribuzioneContatto.getRecipientCompanyDescription()
{
  string ragioneSociale = ""
  if (Contatto)
  {
    select into variables (found variable)
      set ragioneSociale = RAGIONESOCIALE
    from 
      ClientiFornitori // master table
    where
      IDCONTO = IDCONTO
  }
   
  return ragioneSociale
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string RigaListaDistribuzioneContatto.getEmailAddress()
{
  string contattoEmail = null
  if (Contatto)
    contattoEmail = Contatto.EMAIL
  return contattoEmail
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static RigaListaDistribuzioneContatto RigaListaDistribuzioneContatto.create(
  int idListaDistrib // 
  int idContatto     // 
  int idConto        // 
)
{
  RigaListaDistribuzioneContatto rld = new()
  rld.init()
  rld.IDLISTADISTRIB = idListaDistrib
  rld.NROCOPIE = 0
  rld.TIPODISTRIBUZIONE = E-Mail
   
  rld.IDCONTATTO = idContatto
  rld.IDCONTO = idConto
  rld.loadAdditionalProperties()
   
  return rld
   
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void RigaListaDistribuzioneContatto.loadAdditionalProperties()
{
  Contatto.IDCONTATTO = IDCONTATTO
  try 
  {
    Contatto.loadFromDB(0)
    RecipientDescription = this.getRecipientDescription()
    CompanyDescription = this.getRecipientCompanyDescription()
    Email = this.getEmailAddress()
    TipoContoDescription = this.getTipoContoDescription()
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string RigaListaDistribuzioneContatto.getTipoContoDescription()
{
  string tipoContoDescription = ""
  if (Contatto)
  {
    select into variables (found variable)
      set tipoContoDescription = GCFTIPICONTO.DESCRTIPOCONTO
    from 
      ClientiFornitori // master table
      GCFTIPICONTO     // joined with Clienti Fornitori using key FK_GCF_ANAGRAFICA_ID_TIPO_CONTO
    where
      ClientiFornitori.IDCONTO = IDCONTO
  }
   
  return tipoContoDescription
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static DOCCOMMGROUP DOCCOMMGROUP.getByDescription(
  string desciption // 
)
{
  DOCCOMMGROUP doccommgroup = new()
  doccommgroup.DESCRGRUPPO = desciption
  try 
  {
    doccommgroup.loadFromDB(...)
  }
  catch 
  {
    doccommgroup = null
    QappCore.DTTLogMessage(formatMessage("Unable to find doc comm group for description: |1", desciption, ...), ...)
  }
  return doccommgroup
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static DOCCOMMGROUP DOCCOMMGROUP.create(
  string description         // 
  optional int sequence = 1  // 
  optional IDArray kordApps  // pass null if you want to set Addin or array of kordApps to set relevant field
  optional boolean addin = 0 // pass true to set Addin = Yes
)
{
  DOCCOMMGROUP doccommgroup = new()
  doccommgroup.init()
  doccommgroup.SEQUENZA = sequence
  doccommgroup.DESCRGRUPPO = description
  if (kordApps)
  {
    for (int i = 0; i < kordApps.length(); i = i + 1)
    {
      int:kordapp kordApp = kordApps.getValue(i)
      switch (kordApp)
      {
         case ClientiFornitori:
           doccommgroup.CLIFOR = Yes
         break
         case Personale:
           doccommgroup.PERSONALE = Yes
         break
         case Progetti:
           doccommgroup.PROGETTI = Yes
         break
         case Privati:
           doccommgroup.PRIVATI = Yes
         break
         case Eventi:
           doccommgroup.EVENTI = Yes
         break
         case Funzioni:
           doccommgroup.FUNZIONI = Yes
         break
         case AltreAnagrafiche:
           doccommgroup.INFSTR = Yes
         break
         case Articoli:
           doccommgroup.ARTICOLI = Yes
         break
         case Interventi:
           doccommgroup.INTERVENTI = Yes
         break
      }
    }
  }
  if (addin)
  {
    doccommgroup.ADDIN = Yes
  }
   
  return doccommgroup
   
}


// ──────────────────────────────────

// ********************************************************
// filter doc comm group based on kordapp and addin boolean
// ********************************************************
public Recordset DOCCOMMGROUP.filterGroupsForLookup(
  int:kordapp kordApp        // 
  optional boolean addin = 0 // 
)
{
  Recordset filteredGroupsBasedOnKordApp = new()
  select into recordset (filteredGroupsBasedOnKordApp)
    IDDOCCOMMGROUP as IDDOCCOMMGRO
    DESCRGRUPPO as DESCRGRUPPO
  from 
    DOCCOMMGROUPS // master table
  where
    (kordApp == ClientiFornitori and CLIFOR == Yes) or (kordApp == Personale and PERSONALE == Yes) or (kordApp == Progetti and PROGETTI == Yes) or (kordApp == Privati and PRIVATI == Yes) or (
       kordApp == Eventi and EVENTI == Yes) or (kordApp == Funzioni and FUNZIONI == Yes) or (kordApp == Funzioni and FUNZIONI == Yes) or (kordApp == Funzioni and FUNZIONI == Yes) or (kordApp ==
       AltreAnagrafiche and INFSTR == Yes) or (kordApp == Articoli and ARTICOLI == Yes) or (kordApp == Interventi and INTERVENTI == Yes) or (addin == true and ADDIN == Yes)
   
  return filteredGroupsBasedOnKordApp
}


// ──────────────────────────────────

// **************************************************************************
// Event called to customize the Smart Lookup procedure at the document level
// **************************************************************************
event DOCCOMMGROUP.OnGetSmartLookup(
  Recordset RecordSet       // The recordset to be filled if the queries run during the Smart Lookup event are customized.
  inout int Level           // The Smart Lookup procedure performs multiple attempts, increasingly widening the search field. This integer parameter specifies the number of attempts made.
  inout boolean NullValue   // A boolean output parameter. If set to True, then NULL (no results found) will be returned to the panel.
  inout boolean Skip        // A boolean output parameter. If set to True, then the standard queries for the current level will not be executed.
  inout boolean Cancel      // A boolean output parameter. If set to True, the Smart Lookup procedure will be stopped.
  IDDocument CallerDocument // The document for which the smart lookup procedure is launched. It can be null.
)
{
  if (CallerDocument)
  {
    if (ModuleDocument.isMyInstance(CallerDocument))
    {
      ModuleDocument md = (ModuleDocument)CallerDocument
      int:kordapp kordApp = md.IDORIGINE
      Recordset filteredRecordSetBasedOnKordApp = this.filterGroupsForLookup(kordApp, ...)
      RecordSet.copyFrom(filteredRecordSetBasedOnKordApp)
    }
  }
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event DocumentUploadHelper.OnInit()
{
  Description = "Documento inserito da Web"
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static DocumentUploadHelper DocumentUploadHelper.create(
  optional string documentDescription = "" // 
  optional boolean enableDescription = 0   // 
  optional IDArray supportedExtensions     // 
)
{
  DocumentUploadHelper d = new()
  d.init()
  if (documentDescription != "")
  {
    d.Description = documentDescription
  }
  d.EnableDescription = enableDescription
   
  if (supportedExtensions)
  {
    d.setSupportedExtensions(supportedExtensions)
  }
  return d
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void DocumentUploadHelper.computeBlob()
{
  string newFileNameWithExtension = docIDToGuid(newDocID()) + FileExtension
  string filepath = saveBlobFile(BlobData, QappCore.tempPath, newFileNameWithExtension)
  ResultingFilePath = filepath
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void DocumentUploadHelper.setExtension(
  string extension // 
)
{
  FileExtension = "." + extension
}


// ──────────────────────────────────

// ***********************************************************************************************************************
// the passed array is cleaned ans set in the internal array, [.png,docx,.jpg] is cleaned to [png,docx,jpg] before setting
// ***********************************************************************************************************************
public void DocumentUploadHelper.setSupportedExtensions(
  IDArray supportedExtensions // 
)
{
  SupportedExtensions = new()
  for (int i = 0; i < supportedExtensions.length(); i = i + 1)
  {
    string currentExtension = supportedExtensions.getValue(i)
    string cleanCurrentExtension = replace(currentExtension, ".", "")
    SupportedExtensions.addValue(cleanCurrentExtension)
  }
}


// ──────────────────────────────────

// *******************************************************************************************
// returns the supported extensions as a string ready to be passed as FileType in a blob panel
// [jpg,png] -> "*.jpg;*.png"
// *******************************************************************************************
public string DocumentUploadHelper.supportedExtensionsAsFileTypesString()
{
  string result = ""
  for (int i = 0; i < SupportedExtensions.length(); i = i + 1)
  {
    string currentExtension = SupportedExtensions.getValue(i)
    string currentExtensionaAsFileTypeString = formatMessage("*.|1", currentExtension, ...)
     
    result = SH.Concat(result, currentExtensionaAsFileTypeString, ";")
  }
   
  return result
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void DocumentUploadHelper.initializeDocTipiFileSupportExtensions()
{
  SupportedExtensions = DocTipiFile.getArrayOfSupportedExtensions()
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static DocumentEngine DocumentEngine.create(
  Documento modelloDocumento // 
  MainModule mainModule      // 
)
{
  if (!(modelloDocumento.isModello()))
    QappCore.DTTLogMessage("the passed Documento is not a modello", ..., DTTError)
   
  DocumentEngine de = new()
  de.MainModule = mainModule
  de.ModelloDocumento = modelloDocumento
  de.init()
  return de
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public ModuleDocument DocumentEngine.createDocumentFromModello()
{
  ModuleDocument md = null
   
  string errorMessage = ""
  string createdDocumentFilePath = ModelloDocumento.GetDownloadbleFilePath(errorMessage, false, ...)
   
  if (errorMessage != "")
  {
    QappCore.DTTLogMessage(errorMessage, ..., DTTError)
    return md
  }
  string documentWithReplacedValues = this.replaceTagsInDocument(createdDocumentFilePath)
   
  md = ModuleDocument.create(documentWithReplacedValues, MainModule.getMainID(), MainModule.getKordApp(), ModelloDocumento.DESCRDOCUMENTO, QappCore.Loggeduser, ..., false)
  return md
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void DocumentEngine.prepareReplacementsMap()
{
  ReplacementsMap = new()
   
  // Retrieve fixed fields
   
  this.initializeFixedReplacementsCollection()
   
   
  // Retrieve module specific fields (including: module header data (such as Ragione Sociale or Descr titolo), custom data, custom data of riferimenti
   
  IDCollection replacementsCollection of MapElement = MainModule.getAllTextReplacements()
  replacementsCollection.addAll(FixedReplacementsCollection, ...)
   
  for each MapElement me in replacementsCollection
  {
    string transformedKey = this.transformKeysForReplacement(me.Key)
    ReplacementsMap.setValue(transformedKey, me.Value)
  }
}


// ──────────────────────────────────

// ***************************************************
// encapsulates the way we define replacement tags
// now it is <TAG>, in future it could become [TAG]...
// ***************************************************
private string DocumentEngine.transformKeysForReplacement(
  string key // 
)
{
  return "&lt;" + key + "&gt;"
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public IDMap DocumentEngine.getReplamentsMap()
{
  return ReplacementsMap
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void DocumentEngine.initializeFixedReplacementsCollection()
{
  FixedReplacementsCollection = new()
   
  string dateFormatString = UIParameters.getDateFormat()
  FixedReplacementsCollection.add(MapElement.create("DATA", format(today(), dateFormatString, ...)))
  FixedReplacementsCollection.add(MapElement.create("DATA_ESTESA", format(today(), "dd mmm yyyy", ...)))
   
  string datestring = format(today(), "dd/mm/yyyy", ...)
  string timestring = format(now(), "hh:nn", ...)
   
  string dateTimeString = formatMessage("|1 - |2", datestring, timestring, ...)
   
  FixedReplacementsCollection.add(MapElement.create("DATA_ORA", dateTimeString))
   
  FixedReplacementsCollection.add(MapElement.create("ORA", format(now(), "hh:nn:ss", ...)))
  FixedReplacementsCollection.add(MapElement.create("AUTORE", QappCore.Loggeduser.DESCRUTENTE))
  FixedReplacementsCollection.add(MapElement.create("DESCR_REPARTO", QappCore.Loggeduser.getDescrReparto()))
   
  TabParametri tp = TabParametri.getInstance()
  FixedReplacementsCollection.add(MapElement.create("DESCR_DITTA1", tp.DESCRDITTA1))
  FixedReplacementsCollection.add(MapElement.create("DESCR_DITTA2", tp.DESCRDITTA2))
  FixedReplacementsCollection.add(MapElement.create("DESCR_DITTA3", tp.DESCRDITTA3))
  FixedReplacementsCollection.add(MapElement.create("EMAIL_DITTA", tp.EMAIL))
   
  Personale p = QappCore.Loggeduser.getLinkedPersonale()
  FixedReplacementsCollection.add(MapElement.create("EMAIL_USER", p.EMAIL))
   
  // get next sequence of module document and update tag NOME_FILE
  int nextSequenceOfModuleDocument = Sequence.getNextSequence(DOCN_ID_FILE, false)
  FixedReplacementsCollection.add(MapElement.create("NOME_FILE", toString(nextSequenceOfModuleDocument)))
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private string DocumentEngine.replaceTagsInDocument(
  string documentFilePath // 
)
{
  DOCX docx = DOCX.create(documentFilePath)
  docx.loadStructure()
   
//  // code for debugging
//  IDArray ida = ReplacementsMap.getKeys()
//  for (int i = 0; i < ida.length(); i = i + 1)
//  {
//    string key = ida.getValue(i)
//    string value = ReplacementsMap.getValue(key)
//    QappCore.DTTLogMessage(formatMessage("key: |1 = Value: |2", key, value, ...), 1515151, ...)
//  }
  docx.replaceValues(ReplacementsMap)
   
  docx.compress()
  return docx.FileHandler.Path
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event DocumentEngine.OnInit()
{
  this.prepareReplacementsMap()
}


// ──────────────────────────────────

// **************************************************************************************
// Event raised to the document during the initial steps of the document saving procedure
// **************************************************************************************
event throws exception DocSpreadsheet.BeforeSave(
  inout boolean Skip         // A boolean output parameter. If set to True, tells the framework not to perform operations on the database, because they will be managed independently.
  inout boolean Cancel       // A boolean output parameter. If set to True, tells the framework to cancel the save procedure due to errors that occurred. Since the entire document is saved in the same transa...
  int:beforeSavePhases Phase // An integer from 0 to 3 that specifies the progress status of the save procedure as detailed at the beginning of the article.
)
{
  if (Phase == PreSave)
  {
    DATAULTIMAMOD = now()
     
    DocFile.saveToDB(...)
  }
}


// ──────────────────────────────────

// ************************************************************
// Event raised by the document after loading from the database
// ************************************************************
event DocSpreadsheet.AfterLoad(
  boolean AlreadyLoaded     // A boolean parameter that specifies whether the document was previously loaded.
  IDArray LoadedCollections // IDArray type parameter that contains the collections loaded by the framework. If the value of the Already Loaded parameter is False, the Loaded Collections parameter is NULL.
)
{
  this.loadDocFile()
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void DocSpreadsheet.loadDocFile()
{
  DocFile.IDDOCFILE = IDDOCFILE
  DocFile.loadFromDB(0)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static DocCollegato DocCollegato.create(
  int:kordapp kordapp          // 
  int mainId                   // 
  int idDocumento              // 
  optional int idRevisione = 0 // 
  optional string note = ""    // 
)
{
  DocCollegato dcb = new()
  dcb.init()
  dcb.MainId = mainId
  dcb.IdDocumento = idDocumento
  dcb.IdRevisione = idRevisione
  dcb.Kordapp = kordapp
  dcb.Note = note
  IDDocument doc = dcb.getInstance(mainId, idDocumento, kordapp)
  dcb.setOwnedObject(doc)
  dcb.initializePropertiesDependingOnOwnedObject()
  if (!(doc.inserted))
  {
    dcb.setOriginal()
  }
   
   
  return dcb
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public IDDocument DocCollegato.getOwnedObject()
{
  return OwnedObject
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void DocCollegato.setOwnedObject(
  IDDocument ownedObject // 
)
{
  OwnedObject = ownedObject
//  this.initializePropertiesDependingOnOwnedObject()
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static IDCollection DocCollegato.loadCollectionForMainModule(
  MainModule mainModule // 
)
{
  IDCollection retrievedCollection of DocCollegato = new()
  int mainId = mainModule.getMainID()
  Recordset r = this.getDocCollegatiRecordSetForMainModule(mainModule)
   
  r.moveFirst()
  while (!(r.EOF()))
  {
    DocCollegato dc = DocCollegato.create(mainModule.getKordApp(), mainId, toInteger(r.getFieldValue("ID_DOCUMENTO")), ...)
    if (dc)
    {
      retrievedCollection.add(dc)
    }
    else 
    {
      QappCore.DTTLogMessage("Not found: it should not happen", ..., DTTError)
    }
    r.moveNext()
  }
  return retrievedCollection
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static Recordset DocCollegato.getDocCollegatiRecordSetForMainModule(
  MainModule mainModule // 
)
{
   
  string parametricQuery = "SELECT ID_DOCUMENTO FROM |1 WHERE |2=|3"
   
  // we need a method with switch
  string moduleSpecificDbTableName = this.getDocCollegatoClassInfo(mainModule.getKordApp(), tableName)
   
  string moculeSpecificDbMainId = this.getDocCollegatoClassInfo(mainModule.getKordApp(), mainIdFieldName)
   
  int mainId = mainModule.getMainID()
   
  string replacedQuery = formatMessage(parametricQuery, moduleSpecificDbTableName, moculeSpecificDbMainId, mainId, ...)
   
  Recordset docCollegatiRecordSet = QualibusDB.SQLQuery(replacedQuery)
   
  return docCollegatiRecordSet
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void DocCollegato.handleEndTransaction(
  boolean noteWasModified // 
)
{
  IDDocument doc = this.getOwnedObject()
  if (doc)
  {
    if (noteWasModified)
    {
      IdDocumentTools.setPropertyValueByDbCode(doc, "NOTE", Note)
    }
  }
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static IDDocument DocCollegato.getInstance(
  int mainId          // 
  int idDocumento     // 
  int:kordapp kordApp // 
)
{
   
  IDDocument doc = this.createNewSpecificInstanceOfDocCollegato(kordApp)
   
  string mainIdDbCodeForDocCollegato = this.getDocCollegatoClassInfo(kordApp, mainIdFieldName)
  IdDocumentTools.setPropertyValueByDbCode(doc, mainIdDbCodeForDocCollegato, toString(mainId))
  IdDocumentTools.setPropertyValueByDbCode(doc, "ID_DOCUMENTO", toString(idDocumento))
   
  try 
  {
    doc.loadFromDB(...)
  }
  catch 
  {
    doc.init()
  }
  return doc
}


// ──────────────────────────────────

// ***********************************************************************************************************************************************************************
// given a kordApp we return the requested info on the classs (either className or main id field at the moment of writing) of the corresponding doc collegati actual class
// ***********************************************************************************************************************************************************************
private static string DocCollegato.getDocCollegatoClassInfo(
  int:kordapp kordApp                        // 
  string:docCollegatoClassInfoTypes infoType // 
)
{
  IDMap kordClassDocCollegatiMapping = new()
  switch (infoType)
  {
    case className:
      kordClassDocCollegatiMapping.setValue(Articoli, ARTDOCUMENTI.className(...))
      kordClassDocCollegatiMapping.setValue(AltreAnagrafiche, CESDOCUMENTI.className(...))
      kordClassDocCollegatiMapping.setValue(Privati, CISDOCUMENTI.className(...))
      kordClassDocCollegatiMapping.setValue(Documenti, DOCCOLLEGATI.className(...))
      kordClassDocCollegatiMapping.setValue(Eventi, EventiDocCollegato.className(...))
      kordClassDocCollegatiMapping.setValue(Funzioni, FUNDOCUMENTI.className(...))
      kordClassDocCollegatiMapping.setValue(ClientiFornitori, GCFDOCUMENTI.className(...))
      kordClassDocCollegatiMapping.setValue(Interventi, MANDOCUMENTI.className(...))
      kordClassDocCollegatiMapping.setValue(ModelliEventi, ModelloEventoDocumentoCollegato.className(...))
      kordClassDocCollegatiMapping.setValue(Personale, PERDOCUMENTI.className(...))
      kordClassDocCollegatiMapping.setValue(Progetti, PRGDOCUMENTI.className(...))
    break
    case mainIdFieldName:
      kordClassDocCollegatiMapping.setValue(Articoli, "ID_ARTICOLO")
      kordClassDocCollegatiMapping.setValue(AltreAnagrafiche, "ID_CESPITE")
      kordClassDocCollegatiMapping.setValue(Privati, "ID_CITTADINI")
      kordClassDocCollegatiMapping.setValue(Documenti, "ID_DOC")
      kordClassDocCollegatiMapping.setValue(Eventi, "ID_EVENTO")
      kordClassDocCollegatiMapping.setValue(Funzioni, "ID_FUNZIONE")
      kordClassDocCollegatiMapping.setValue(ClientiFornitori, "ID_CONTO")
      kordClassDocCollegatiMapping.setValue(Interventi, "ID_TESTATA_OP")
      kordClassDocCollegatiMapping.setValue(ModelliEventi, "ID_TEMPLATE_EVENTO")
      kordClassDocCollegatiMapping.setValue(Personale, "ID_DIPENDENTE")
      kordClassDocCollegatiMapping.setValue(Progetti, "ID_PROGETTO")
    break
    case tableName:
      kordClassDocCollegatiMapping.setValue(Articoli, "ART_DOCUMENTI")
      kordClassDocCollegatiMapping.setValue(AltreAnagrafiche, "CES_DOCUMENTI")
      kordClassDocCollegatiMapping.setValue(Privati, "CIS_DOCUMENTI")
      kordClassDocCollegatiMapping.setValue(Documenti, "DOC_COLLEGATI")
      kordClassDocCollegatiMapping.setValue(Eventi, "EVA_DOCUMENTI")
      kordClassDocCollegatiMapping.setValue(Funzioni, "FUN_DOCUMENTI")
      kordClassDocCollegatiMapping.setValue(ClientiFornitori, "GCF_DOCUMENTI")
      kordClassDocCollegatiMapping.setValue(Interventi, "MAN_DOCUMENTI")
      kordClassDocCollegatiMapping.setValue(ModelliEventi, "EVA_TEMPL_DOCUMENTI")
      kordClassDocCollegatiMapping.setValue(Personale, "PER_DOCUMENTI")
      kordClassDocCollegatiMapping.setValue(Progetti, "PRG_DOCUMENTI")
    break
  }
   
  string retrievedValue = kordClassDocCollegatiMapping.getValue(kordApp)
   
  if (retrievedValue == "")
  {
    QappCore.DTTLogMessage(formatMessage("doc Collegato info "|1" not found for kordapp "|2"", decode(infoType, DocCollegatoClassInfoTypes), decode(kordApp, Kordapp), ...), ..., DTTError)
  }
   
  return retrievedValue
}


// ──────────────────────────────────

// *********************************************************************************
// given a kordApp of a docCollegato class, we return a new() instance of that class
// 
// *********************************************************************************
private static IDDocument DocCollegato.createNewSpecificInstanceOfDocCollegato(
  int:kordapp kordApp // 
)
{
  string className = this.getDocCollegatoClassInfo(kordApp, className)
   
  IDDocument doc = null
  if (className != "")
  {
    doc = QappCore.createFormFromLibrary("", className)
  }
  else 
  {
    QappCore.DTTLogMessage(formatMessage("Classname not identified for kordApp "|1"", decode(kordApp, Kordapp), ...), ..., DTTError)
  }
  return doc
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void DocCollegato.initializePropertiesDependingOnOwnedObject()
{
  Note = IdDocumentTools.getPropertyValueByDbCode(OwnedObject, "NOTE")
  IdRevisione = toInteger(IdDocumentTools.getPropertyValueByDbCode(OwnedObject, "ID_REVISIONE"))
   
  Documento d = Documento.getFromDB(IdDocumento, ...)
  if (d)
  {
    this.setAddtionalProperties(d)
     
  }
   
  // properties not depending on owned object
  LockedRevisione = IdRevisione > 0
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void DocCollegato.setAddtionalProperties(
  Documento documento // 
)
{
  CodDocumento = documento.CODDOCUMENTO
  DescrDocumento = documento.DESCRDOCUMENTO
  TipoFile = Tools.computeHtmlIcon(documento.EstensioneFile)
  TipoDocumento = documento.IDTIPODOCUMENTO
  StatoDocumento = documento.STATO
  DataApprovazione = documento.DataApprovazione
  InModifica = documento.InModifica
  if (IdRevisione > 0)
  {
    Revision r = Revision.get(IdRevisione)
    NroRevisioneToBeDisplayed = r.NROREVISIONE
  }
  else 
    NroRevisioneToBeDisplayed = documento.LastNroRevsion
   
  Dimensione = documento.getFileSize(if(IdRevisione > 0, IdRevisione, documento.LastRevision.IDREVISIONE))
  DimensioneKb = round(Dimensione / 1000,0, 2)
  DimensioneMb = round(DimensioneKb / 1000,0, 2)
  DimensioneString = if(DimensioneKb < 1000, formatMessage("|1 KB", DimensioneKb, ...), formatMessage("|1 MB", DimensioneMb, ...))
   
  NoteDiRevisione = documento.LastRevision.NOTE
  DocUbicazioni du = DocUbicazioni.get(documento.LastRevision.IDUBICAZIONE)
  Ubicazione = du.DESCRUBICAZIONE
  Cartella cartella = Cartella.get(documento.IDCARTELLA)
  CartellaDescription = cartella.getFullPathDescription()
   
  Documento = documento
}


// ──────────────────────────────────



// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void DocCollegato.blockRevision(
  Revision revision // 
)
{
  LockedRevisione = true
  NroRevisioneToBeDisplayed = revision.NROREVISIONE
  IDDocument doc = this.getOwnedObject()
  IdDocumentTools.setPropertyValueByDbCode(doc, "ID_REVISIONE", toString(revision.IDREVISIONE))
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void DocCollegato.unblockRevision()
{
  LockedRevisione = false
  NroRevisioneToBeDisplayed = Documento.LastRevision.NROREVISIONE
  IDDocument doc = this.getOwnedObject()
  IdDocumentTools.setPropertyValueByDbCode(doc, "ID_REVISIONE", null)
}


// ──────────────────────────────────

// ***********************************************************
// Event raised to the document when the transaction is closed
// ***********************************************************
event DocCollegato.OnEndTransaction()
{
  boolean noteWasModified = wasModified(Note)
  this.handleEndTransaction(noteWasModified)
}


// ──────────────────────────────────



// ──────────────────────────────────

// *************************************************************
// Event raised to the document at the end of the save procedure
// *************************************************************
event throws exception DocCollegato.AfterSave(
  inout boolean Cancel // Boolean output parameter that makes it possible to cancel the entire save. It is equivalent to the Cancel parameter of the BeforeSave event.
)
{
  IDDocument doc = this.getOwnedObject()
  int Phase = 0
  if (Phase == PreSave)
  {
    if (updated)
    {
      doc.saveToDB(...)
    }
    if (deleted)
    {
      doc.deleted = true
      doc.saveToDB(...)
    }
  }
}


// ──────────────────────────────────

// ***********************************************************************
// get altre anagrafiche doc Collegato for given idCespite and IdDocumento
// ***********************************************************************
public static CESDOCUMENTI CESDOCUMENTI.get(
  int idCespite   // 
  int idDocumento // 
)
{
  CESDOCUMENTI cd = new()
  cd.IDCESPITE = idCespite
  cd.IDDOCUMENTO = idDocumento
  try 
  {
    cd.loadFromDB(...)
  }
  catch 
  {
    cd = null
    QappCore.DTTLogMessage(formatMessage("Unable to load Clifor Document for idCespite: |1, IdDocumento: |2", idCespite, idDocumento, ...), ..., DTTError)
  }
  return cd
}


// ──────────────────────────────────

********************************************************* opens the document in browser and writes the document log *********************************************************

// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static CISDOCUMENTI CISDOCUMENTI.create(
  int idCittadini // 
  int idDocumento // 
)
{
  CISDOCUMENTI cd = new()
  cd.init()
  cd.IDCITTADINI = idCittadini
  cd.IDDOCUMENTO = idDocumento
   
  return cd
}


// ──────────────────────────────────

// ***********************************************************
// this method get or create and finally return correct object
// ***********************************************************
public static CISDOCUMENTI CISDOCUMENTI.getInstance(
  int idCittadini // 
  int idDocumento // 
)
{
  CISDOCUMENTI cd = this.get(idCittadini, idDocumento)
  if (cd == null)
  {
    cd = this.create(idCittadini, idDocumento)
  }
  return cd
}


// ──────────────────────────────────

// *******************************************************************
// get Documento doc Collegato for given idDoc(mainId) and IdDocumento
// *******************************************************************
public static DOCCOLLEGATI DOCCOLLEGATI.get(
  int idDoc       // 
  int idDocumento // 
)
{
  DOCCOLLEGATI dc = new()
  dc.IDDOC = idDoc
  dc.IDDOCUMENTO = idDocumento
  try 
  {
    dc.loadFromDB(...)
  }
  catch 
  {
    dc = null
    QappCore.DTTLogMessage(formatMessage("Unable to load Clifor Document for idDoc: |1, IdDocumento: |2", idDoc, idDocumento, ...), ..., DTTError)
  }
  return dc
}


// ──────────────────────────────────

// ***********************************************************
// get Evento doc Collegato for given idEvento and IdDocumento
// ***********************************************************
public static EventiDocCollegato EventiDocCollegato.get(
  int idEvento    // 
  int idDocumento // 
)
{
  EventiDocCollegato cd = new()
  cd.IDEVENTO = idEvento
  cd.IDDOCUMENTO = idDocumento
  try 
  {
    cd.loadFromDB(...)
  }
  catch 
  {
    cd = null
    QappCore.DTTLogMessage(formatMessage("Unable to load Clifor Document for idEvento: |1, IdDocumento: |2", idEvento, idDocumento, ...), ..., DTTError)
  }
  return cd
}


// ──────────────────────────────────

// ***************************************************************
// get Funzioni doc Collegato for given idFunzioni and IdDocumento
// ***************************************************************
public static FUNDOCUMENTI FUNDOCUMENTI.get(
  int idFunzione  // 
  int idDocumento // 
)
{
  FUNDOCUMENTI fd = new()
  fd.IDFUNZIONE = idFunzione
  fd.IDDOCUMENTO = idDocumento
  try 
  {
    fd.loadFromDB(...)
  }
  catch 
  {
    fd = null
    QappCore.DTTLogMessage(formatMessage("Unable to load Clifor Document for idFunzione: |1, IdDocumento: |2", idFunzione, idDocumento, ...), ..., DTTError)
  }
  return fd
}


// ──────────────────────────────────

// **********************************************************
// get clifor doc Collegato for given idConto and IdDocumento
// **********************************************************
public static GCFDOCUMENTI GCFDOCUMENTI.get(
  int idConto     // 
  int idDocumento // 
)
{
  GCFDOCUMENTI gd = new()
  gd.IDCONTO = idConto
  gd.IDDOCUMENTO = idDocumento
  try 
  {
    gd.loadFromDB(...)
  }
  catch 
  {
    gd = null
    QappCore.DTTLogMessage(formatMessage("Unable to load Clifor Document for idConto: |1, IdDocumento: |2", idConto, idDocumento, ...), ..., DTTError)
  }
  return gd
}


// ──────────────────────────────────

// ******************************************************************
// get Intervento doc Collegato for given idTestataOp and IdDocumento
// ******************************************************************
public static MANDOCUMENTI MANDOCUMENTI.get(
  int idTestataOp // 
  int idDocumento // 
)
{
  MANDOCUMENTI cd = new()
  cd.IDTESTATAOP = idTestataOp
  cd.IDDOCUMENTO = idDocumento
  try 
  {
    cd.loadFromDB(...)
  }
  catch 
  {
    cd = null
    QappCore.DTTLogMessage(formatMessage("Unable to load Clifor Document for idTestataOp: |1, IdDocumento: |2", idTestataOp, idDocumento, ...), ..., DTTError)
  }
  return cd
}


// ──────────────────────────────────

// ***************************************************************************
// get Modello Evento doc Collegato for given idTemplateEvento and IdDocumento
// ***************************************************************************
public static ModelloEventoDocumentoCollegato ModelloEventoDocumentoCollegato.get(
  int idTemplateEvento // 
  int idDocumento      // 
)
{
  ModelloEventoDocumentoCollegato docCollegato = new()
  docCollegato.IDTEMPLATEEVENTO = idTemplateEvento
  docCollegato.IDDOCUMENTO = idDocumento
  try 
  {
    docCollegato.loadFromDB(...)
  }
  catch 
  {
    docCollegato = null
    QappCore.DTTLogMessage(formatMessage("Unable to load Clifor Document for idTemplateEvento: |1, IdDocumento: |2", idTemplateEvento, idDocumento, ...), ..., DTTError)
  }
  return docCollegato
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event ModelloEventoDocumentoCollegato.OnInit()
{
  QualibusDB.maxRows = 1
   
  int currentId = 0
  select into variables (found variable)
    set currentId = IDEVATEMPLDOCUMENTI
  from 
    ModelloEventoDocumentoCollegati // master table
  order by
    IDEVATEMPLDOCUMENTI
   
  IDEVATEMPLDOCUMENTI = currentId + 1
}


// ──────────────────────────────────

// ******************************************************************
// get Personale doc Collegato for given idDipendente and IdDocumento
// ******************************************************************
public static PERDOCUMENTI PERDOCUMENTI.get(
  int idDipendente // 
  int idDocumento  // 
)
{
  PERDOCUMENTI pd = new()
  pd.IDDIPENDENTE = idDipendente
  pd.IDDOCUMENTO = idDocumento
  try 
  {
    pd.loadFromDB(...)
  }
  catch 
  {
    pd = null
    QappCore.DTTLogMessage(formatMessage("Unable to load Clifor Document for idDipendente: |1, IdDocumento: |2", idDipendente, idDocumento, ...), ..., DTTError)
  }
  return pd
}


// ──────────────────────────────────



// ──────────────────────────────────

// ************************************************************
// Event raised by the document after loading from the database
// ************************************************************
event VDOCCOLLEGATI.AfterLoad(
  boolean AlreadyLoaded     // A boolean parameter that specifies whether the document was previously loaded.
  IDArray LoadedCollections // IDArray type parameter that contains the collections loaded by the framework. If the value of the Already Loaded parameter is False, the Loaded Collections parameter is NULL.
)
{
  if (!(AlreadyLoaded))
  {
    this.computeDisplayIcon()
    this.setOriginal()
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void VDOCCOLLEGATI.computeDisplayIcon()
{
  string estensione = lower(ESTENSIONEFILE)
   
  if (CODDOCUMENTO + DESCRDOCUMENTO == "")
  {
    DisplayIcon = empty
  }
  else 
  {
    switch (estensione)
    {
      case .pdf:
         DisplayIcon = .pdf
      break
      case .doc:
         DisplayIcon = .doc
      break
      case .docx:
         DisplayIcon = .docx
      break
      case .rtf:
         DisplayIcon = .rtf
      break
      case .xls:
         DisplayIcon = .xls
      break
      case .xlsx:
         DisplayIcon = .xlsx
      break
      default:
         DisplayIcon = empty
      break
    }
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public Documento VDOCCOLLEGATI.getLinkedDocumentASPETTA()
{
  int idDocumento = IDLINKEDDOCUMENT
  Documento d = Documento.getFromDB(idDocumento, ...)
   
  return d
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void OpenDocumentManager.downloadFile(
  string docFile // 
)
{
  QappCore.openDocument(docFile, true, "save")
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static DocTipiFile DocTipiFile.create(
  optional string fileTypeDescription = "Nuovo tipo file" // 
  optional string extension = "???"                       // 
)
{
  DocTipiFile dtf = new()
  dtf.init()
  dtf.DESCRTIPOFILE = fileTypeDescription
  dtf.ESTENSIONE = extension
  return dtf
}


// ──────────────────────────────────

// **********************************************************************
// we do not allow old office extension (ppt,xls and Doc are not allowed)
// 
// **********************************************************************
public boolean DocTipiFile.isValidExtension(
  inout string errorMessage // 
)
{
  errorMessage = ""
  boolean validExtension = true
  string lowerCaseExtention = lower(ESTENSIONE)
  switch (lowerCaseExtention)
  {
    case "doc":
    case "ppt":
    case "xls":
    case ".doc":
    case ".ppt":
    case ".xls":
      validExtension = false
      errorMessage = formatMessage("I file con estensione '|1' non sono utilizzabili.", ESTENSIONE, ...)
    break
  }
  return validExtension
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static IDArray DocTipiFile.getArrayOfSupportedExtensions()
{
  IDCollection collectionOfSupportedExtensions of DocTipiFile = new()
  select into collection (collectionOfSupportedExtensions)
    ESTENSIONE as ESTENSIONE
  from 
    DocTipiFile // master table
   
  IDArray supportedExtensions = new()
  for each DocTipiFile dtf in collectionOfSupportedExtensions
  {
    string errorMessage = ""
    boolean isValidExtension = dtf.isValidExtension(errorMessage)
    if (isValidExtension)
    {
      supportedExtensions.addValue(dtf.ESTENSIONE)
    }
  }
   
  return supportedExtensions
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean DocTipiFile.extensionAlreadyExists()
{
  boolean extensionAlreadyExists = false
   
  IDCollection parentCollection of DocTipiFile = parentCollection()
   
  for each DocTipiFile dtf in parentCollection
  {
    if (dtf.IDTIPOFILE != IDTIPOFILE)
    {
      if (dtf.ESTENSIONE == ESTENSIONE)
      {
         extensionAlreadyExists = true
         break 
      }
    }
  }
   
  return extensionAlreadyExists
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean DocTipiFile.anyInvalidExtensionIsDefinedInParentCollection()
{
  boolean anyInvalidExtensionIsdDefinedInPArentCollection = false
   
  IDCollection parentCollection of DocTipiFile = parentCollection()
  for each DocTipiFile dtf in parentCollection
  {
    string errorMessage = ""
    if (!(dtf.isValidExtension(errorMessage)))
    {
      anyInvalidExtensionIsdDefinedInPArentCollection = true
      QappCore.DTTLogMessage(dtf.ESTENSIONE, 0000000000, DTTWarning)
      break 
    }
  }
   
  return anyInvalidExtensionIsdDefinedInPArentCollection
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void DocTipiFile.validateESTENSIONE(
  inout boolean Error // 
)
{
  if ((nullValue(ESTENSIONE, "") == "" or (ESTENSIONE == "???") or (ESTENSIONE == "")))
  {
    Error = true
    this.setPropertyError("Il campo deve essere definito.", ESTENSIONE)
  }
  string errorMessage = ""
  if (!(this.isValidExtension(errorMessage)))
  {
    Error = true
    this.setPropertyError(errorMessage, ESTENSIONE)
  }
  boolean extensionAlreadyExists = this.extensionAlreadyExists()
  if (extensionAlreadyExists)
  {
    Error = true
    this.setPropertyError(formatMessage("Valore: '|1' già specificato", ESTENSIONE, ...), ESTENSIONE)
  }
   
//  // THIS CODE COMMENTED BUT WE KEEP ONE LINE JUST TO REMEMBER THIS EXTRA (TESTED) METHOD EXISTS:
//  boolean anyInvalidExtensionIsDefinedInParentCollection = this.anyInvalidExtensionIsDefinedInParentCollection()
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event DocTipiFile.OnInit()
{
  IDTIPOFILE = Sequence.getNextSequence(DOCN_ID_TABELLE, ...)
  DESCRTIPOFILE = "Nuovo tipo file"
  ESTENSIONE = "???"
  CHANGEAPPTITLEOBSOLETE = Yes
  ISCOMPRESSED = Yes
  SEQ = 1
}


// ──────────────────────────────────

// ************************************************************
// Event raised to the document during the validation procedure
// ************************************************************
event DocTipiFile.OnValidate(
  int:validateReasons Reason // An integer specifying the reason the document is being validated. The most common values are 0 to indicate complete validation before saving, or 1 to indicate a quick validati...
  inout boolean Error        // A boolean output parameter. If set to True, specifies an error condition and stops the validation immediately.
  inout boolean Skip         // A boolean output parameter. If set to True, does not check the rules for automatic validation of the document (see notes).
)
{
  if (deleted)
    return 
   
  this.validateESTENSIONE(Error)
   
  if ((nullValue(DESCRTIPOFILE, "") == "" or (DESCRTIPOFILE == "")))
  {
    Error = true
    this.setPropertyError("Il campo deve essere definito.", DESCRTIPOFILE)
  }
  if (isNull(SEQ) or toString(SEQ) == "")
  {
    Error = true
    this.setPropertyError("Il campo deve essere definito.", SEQ)
  }
   
   
}


// ──────────────────────────────────

// ************************************************************
// Event raised by the document after loading from the database
// ************************************************************
event DocTipiFile.AfterLoad(
  boolean AlreadyLoaded     // A boolean parameter that specifies whether the document was previously loaded.
  IDArray LoadedCollections // IDArray type parameter that contains the collections loaded by the framework. If the value of the Already Loaded parameter is False, the Loaded Collections parameter is NULL.
)
{
  // we check for valid extension here and change the status to update for that object so that inde framework will automatically handle for validations
  string errMsg = ""
  if (!(this.isValidExtension(errMsg)))
  {
    this.updated = true
  }
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event Distribution.OnInit()
{
  int nextId = Sequence.getNextSequence(DOCN_ID_DISTRIBUZIONE, ...)
  IDDISTRIBUZIONE = nextId
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event DistribuzioneRecipients.OnInit()
{
  int nextId = Sequence.getNextSequence(DOCN_ID_LISTA_DISTRIBUZIONE, ...)
  IDLISTA = nextId
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event DocUbicazioni.OnInit()
{
  IDUBICAZIONE = Sequence.getNextSequence(DOCN_ID_TABELLE, ...)
  IDTEMPLATE = null
  TEMPLATE = null
  IDPARENTTEMPLATEREMOTE = null
}


// ──────────────────────────────────

// ************************************************************
// Event raised to the document during the validation procedure
// ************************************************************
event DocUbicazioni.OnValidate(
  int:validateReasons Reason // An integer specifying the reason the document is being validated. The most common values are 0 to indicate complete validation before saving, or 1 to indicate a quick validati...
  inout boolean Error        // A boolean output parameter. If set to True, specifies an error condition and stops the validation immediately.
  inout boolean Skip         // A boolean output parameter. If set to True, does not check the rules for automatic validation of the document (see notes).
)
{
  if (!(deleted))
  {
    if (DESCRUBICAZIONE == null or DESCRUBICAZIONE == "")
    {
      Error = true
      this.setPropertyError("Il campo deve essere definito.", DESCRUBICAZIONE)
    }
  }
}


// ──────────────────────────────────

// ***********************************************************************
// get altre anagrafiche doc Collegato for given idCespite and IdDocumento
// ***********************************************************************
public static DocUbicazioni DocUbicazioni.get(
  int id // 
)
{
  DocUbicazioni du = new()
  du.IDUBICAZIONE = id
  try 
  {
    du.loadFromDB(...)
  }
  catch 
  {
    du = null
    QappCore.DTTLogMessage(formatMessage("Unable to load DocUbicazioni for idUbicazioni: |1", id, ...), ..., DTTError)
  }
  return du
}


// ──────────────────────────────────

********************************************************* opens the document in browser and writes the document log *********************************************************

// ──────────────────────────────────

// ************************************************************
// Event raised to the document during the validation procedure
// ************************************************************
event DOCTIPISUPPORTO.OnValidate(
  int:validateReasons Reason // An integer specifying the reason the document is being validated. The most common values are 0 to indicate complete validation before saving, or 1 to indicate a quick validati...
  inout boolean Error        // A boolean output parameter. If set to True, specifies an error condition and stops the validation immediately.
  inout boolean Skip         // A boolean output parameter. If set to True, does not check the rules for automatic validation of the document (see notes).
)
{
  if (!(deleted))
  {
    if (DESCRTIPOSUPPORTO == "")
    {
      Error = true
      this.setPropertyError("Il campo deve essere definito.", DESCRTIPOSUPPORTO)
    }
  }
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event DOCTIPITAG.OnInit()
{
  IDTIPOTAG = Sequence.getNextSequence(DOCN_ID_TABELLE, ...)
  DESCRTAG = ""
  GRUPPO = ""
  IDTEMPLATE = null
  TEMPLATE = null
  IDPARENTTEMPLATEREMOTE = null
}


// ──────────────────────────────────

// ************************************************************
// Event raised to the document during the validation procedure
// ************************************************************
event DOCTIPITAG.OnValidate(
  int:validateReasons Reason // An integer specifying the reason the document is being validated. The most common values are 0 to indicate complete validation before saving, or 1 to indicate a quick validati...
  inout boolean Error        // A boolean output parameter. If set to True, specifies an error condition and stops the validation immediately.
  inout boolean Skip         // A boolean output parameter. If set to True, does not check the rules for automatic validation of the document (see notes).
)
{
  if (!(deleted))
  {
    if (DESCRTAG == "")
    {
      Error = true
      this.setPropertyError("Il campo deve essere valorizzato ", DESCRTAG)
    }
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static NtfExtraEmailAttachment NtfExtraEmailAttachment.Create(
  NtfExtraEmail email // 
  DocFile docFileBlob // 
)
{
  if (!(email))
  {
    QappCore.DTTLogMessage("email cannot be null", ..., DTTError)
    return null
  }
  if (!(docFileBlob))
  {
    QappCore.DTTLogMessage("docFileBlob cannot be null", ..., DTTError)
    return null
  }
   
   
  NtfExtraEmailAttachment ea = new()
  ea.init()
  ea.IDEXTRAEMAIL = email.IDEXTRAEMAIL
  ea.IDDOCFILE = docFileBlob.IDDOCFILE
   
  return ea
}


// ──────────────────────────────────

// ********************************************
// creates an email
// cc recipients can be added after creating it
// ********************************************
public static NtfExtraEmail NtfExtraEmail.create(
  string toRecipient                 // 
  string subject                     // 
  string plainTxtBody                // 
  optional DocFile docFileAttachment // 
  optional string fromAddress = ""   // 
)
{
  NtfExtraEmail e = new()
  e.init()
  e.RECIPIENTTO = toRecipient
  e.EMAILSUBJECT = subject
  e.EMAILBODY = plainTxtBody
  if (fromAddress != "")
  {
    e.FROMADDRESS = fromAddress
  }
  if (docFileAttachment)
  {
    NtfExtraEmailAttachment ea = NtfExtraEmailAttachment.Create(e, docFileAttachment)
    ea.IDEXTRAEMAIL = e.IDEXTRAEMAIL
    e.NtfExtraEmailAttachment = ea
  }
   
  return e
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void NtfExtraEmail.addCCRecipient(
  string emailAddress // pass either an emailaddress or a semicolon separated list of email addresses
)
{
  boolean validEmailFound = false
  string notValidEmailAddress = ""
  IDArray emailArray = SH.tokenizeToArray(emailAddress, ";", ...)
   
  for (int idx = 0; idx < emailArray.length(); idx = idx + 1)
  {
    string currentEmailAddress = emailArray.getValue(idx)
    boolean currentEmailValid = EmailTools.CheckEmailIsValid(currentEmailAddress)
    if (currentEmailValid)
    {
      validEmailFound = true
      CcRecipients.addValue(currentEmailAddress)
    }
    else 
    {
      validEmailFound = false
      notValidEmailAddress = currentEmailAddress
      break 
    }
  }
   
  if (!(validEmailFound))
    QappCore.DTTLogMessage(formatMessage("'|1' is not a valid email address", notValidEmailAddress, ...), ..., DTTWarning)
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void NtfExtraEmail.prepareCCRecipients()
{
  for (int i = 0; i < CcRecipients.length(); i = i + 1)
  {
    RECIPIENTCC = SH.Concat(RECIPIENTCC, CcRecipients.getValue(i), ";")
  }
}


// ──────────────────────────────────

// ************************************************************
// send saves the mail to the DB so that Mailsender can send it
// fromAddress can be optionally passed
// 
// ************************************************************
public void NtfExtraEmail.send(
  optional string fromAddress = "" // 
)
{
  if (fromAddress != "")
  {
    if (EmailTools.CheckEmailIsValid(fromAddress))
      FROMADDRESS = fromAddress
  }
   
  this.warnForEmptyFromAddress()
  this.prepareCCRecipients()
  this.saveToDB(...)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void NtfExtraEmail.warnForEmptyFromAddress()
{
  if (FROMADDRESS == "")
  {
    QappCore.DTTLogMessage("not setting fromAddress will cause MailSender use the fromAddress defined in MailSender settings", ..., DTTWarning)
  }
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event NtfExtraEmail.OnInit()
{
  EMAILSTATUS = ToBeSent
}


// ──────────────────────────────────

// **************************************************************************************
// Event raised to the document during the initial steps of the document saving procedure
// **************************************************************************************
event throws exception NtfExtraEmail.BeforeSave(
  inout boolean Skip         // A boolean output parameter. If set to True, tells the framework not to perform operations on the database, because they will be managed independently.
  inout boolean Cancel       // A boolean output parameter. If set to True, tells the framework to cancel the save procedure due to errors that occurred. Since the entire document is saved in the same transa...
  int:beforeSavePhases Phase // An integer from 0 to 3 that specifies the progress status of the save procedure as detailed at the beginning of the article.
)
{
  if (Phase == PreSave)
  {
    if (deleted)
    {
      if (NtfExtraEmailAttachment)
      {
         NtfExtraEmailAttachment.deleted = true
         NtfExtraEmailAttachment.saveToDB(...)
      }
    }
  }
}


// ──────────────────────────────────

// *************************************************************
// Event raised to the document at the end of the save procedure
// *************************************************************
event throws exception NtfExtraEmail.AfterSave(
  inout boolean Cancel // Boolean output parameter that makes it possible to cancel the entire save. It is equivalent to the Cancel parameter of the BeforeSave event.
)
{
  if (deleted)
    return 
   
  if (NtfExtraEmailAttachment)
  {
    NtfExtraEmailAttachment.IDEXTRAEMAIL = IDEXTRAEMAIL
    NtfExtraEmailAttachment.saveToDB(0, ...)
  }
}


// ──────────────────────────────────

// ***********************************************************************
// Returns plain text description from the (possibly) RTF one
// 
// To avoid useless converting, the content is stored after the first call
// ***********************************************************************
public string Evento.getDescrizioneTXT()
{
   
  // return immediately if TXT already computed...
  if (DescrizioneTXT != "")
    return DescrizioneTXT
   
   
  // ...otherwise convert, store in memory and return
  string DescrizioneText = SH.RtfToText(DESCREVENTO)
  this.setOriginalValue(toPropertyIndex(DescrizioneTXT), DescrizioneText)
  DescrizioneTXT = DescrizioneText
  this.updated = false
  return DescrizioneTXT
}


// ──────────────────────────────────

// *************************************************************************************************************
// create a new Link document for given document and Idrevision and add in to collection of eventi doc collegati
// *************************************************************************************************************
public void Evento.AddDocCollegati(
  Documento Document // 
  int IdRevision     // 
)
{
  DocCollegato dc = DocCollegato.create(Eventi, IDEVENTO, Document.IDDOCUMENTO, IdRevision, Document.NOTE)
  DocCollegati.add(dc)
}


// ──────────────────────────────────

// **********************************************************************************************************
// Check if Document link already exists in collegati collection
// this method is require to validate/check when we create Document link to prevent Duplicate error on saving
// **********************************************************************************************************
public boolean Evento.DocumentLinkAlreadyExists(
  Documento document // 
)
{
  boolean sameDocumentLinkAlreadyExists = false
   
  for each DocCollegato dc in DocCollegati
  {
    if (dc.IdDocumento == document.IDDOCUMENTO)
    {
      sameDocumentLinkAlreadyExists = true
      break 
    }
  }
   
  return sameDocumentLinkAlreadyExists
}


// ──────────────────────────────────

// **************************************************************
// returns the WKFStep to which the workflow evento currently is.
// 
// It returns null in case of error or in case of closed evento
// **************************************************************
public WkfStep Evento.getCurrentWorkflowStep()
{
  if (WORKFLOWTYPE != Workflow)
  {
    string errorDetail = formatMessage("ID_EVENTO: |1", IDEVENTO, ...)
    if (true)
      throw 0, "You cannot get the Step on a non Workflow evento - " + errorDetail
    return null
  }
   
  if (this.isLocked())
  {
    return null
  }
   
  Disposizione nonClosedDisposizione = this.getNonClosedWorkflowDisposizione()
   
  WkfStep ws = new()
  if (nonClosedDisposizione)
  {
    int idStep = nonClosedDisposizione.IDSTEP
     
    ws.IDSTEP = idStep
    try 
    {
      ws.loadFromDB(...)
    }
    catch 
    {
      ws = null
    }
  }
  else 
  {
    ws = null
  }
   
  return ws
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Evento.close(
  Utente utenteChiusura // 
  WkfFlow closureFlow   // 
)
{
  this.setEventoAsClosed(utenteChiusura, closureFlow.IDGRAVITA)
}


// ──────────────────────────────────

// ********************************************************************************************************************************************************************************************************
// returns the "Next Disposizione", this has a special meaning depending on workflow behavior:
// - NORMAL: the non closed disposizione with the lowest NRO_RIGA (note: for normal we could have the odd situation in which a disposizione with a lower date has an higher NRO_RIGA, anyway NRO_RIGA wins)
// - WORKFLOW: the only non closed disposizione
// 
// IMPORTANT: if the evento is closed or all disposizioni are closed NULL is returned
// ********************************************************************************************************************************************************************************************************
public Disposizione Evento.getNextDisposizione()
{
  Disposizione nextDisposizione = null
   
  if (!(Disposizioni.loaded))
  {
    this.loadCollectionFromDB(Disposizioni, ...)
  }
   
  if (WORKFLOWTYPE == Workflow)
  {
    nextDisposizione = this.getNonClosedWorkflowDisposizione()
  }
  else 
  {
    Disposizioni.resetSortCriteria()
    Disposizioni.addSortCriteria(toPropertyIndex(Disposizione.NRORIGA))
    Disposizioni.doSort()
    for each Disposizione d in Disposizioni
    {
      if (d.ESITO == null)
      {
         nextDisposizione = d
         break 
      }
    }
  }
   
  return nextDisposizione
}


// ──────────────────────────────────

// *****************************************************************************************
// returns the color used in calendar to paint impegni.
// 
// The optional parameter allows to choose wether to have in return the color as an integer:
// true - in DeveloperExpress format (makes sense for Qapps that updated the Qualibus DB)
// false - in inde format (makes sense for Inde forms that display the color)
// *****************************************************************************************
public int Evento.getDxColor(
  optional boolean returnDxColor = 0 // 
)
{
  int computedColor = 0
  ClasseEvento ce = getLinkedDocument(false, ClasseEvento.className(...), ...)
   
  switch (ce.OPZIONICOLORE)
  {
    case Classe:
      computedColor = ce.COLORE
    break
    case Ambito:
      AmbitoEvento ae = getLinkedDocument(false, AmbitoEvento.className(...), ...)
      computedColor = ae.COLORE
    break
  }
   
  if (!(returnDxColor))
  {
    computedColor = Misc.dxColorToInde(computedColor)
  }
   
  return computedColor
}


// ──────────────────────────────────

// *******************************************************************
// retrieves the contatoreEvento that is needed for the current Evento
// *******************************************************************
public ContatoreEvento Evento.getContatore()
{
  ClasseEvento ce = this.getClasseEvento()
   
  int eventoYear = year(DATAEVENTO)
   
  ContatoreEvento conttoreForCurrentEvento = ContatoreEvento.retrieve(ce, IDAMBITO, IDTIPOLOGIA, eventoYear)
  return conttoreForCurrentEvento
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public ClasseEvento Evento.getClasseEvento()
{
  if (!(ClasseEvento))
  {
    ClasseEvento = new()
    ClasseEvento.IDCLASSE = IDCLASSE
    ClasseEvento.loadFromDB(0)
  }
   
  return ClasseEvento
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Evento.computeStandardImportMetadata()
{
  base.addImportMetadato(titolo, toPropertyIndex(DESCRTITOLO), true, ...)
  base.addImportMetadato(data_evento, toPropertyIndex(DATAEVENTO), true, ...)
  base.addImportMetadato(stato_evento, toPropertyIndex(IDSTATOEVENTO), true, null, null, IDMap.fromEnum(EventiStati))
  base.addImportMetadato(descrizione, toPropertyIndex(DESCREVENTOTXT), true, ...)
  base.addImportMetadato(id_utente_inserimento, toPropertyIndex(IDUTENTEINS), true, ...)
  base.addImportMetadato(id_dipendente_avvio, toPropertyIndex(IDDIPAVVIO), true, ...)
  base.addImportMetadato(id_dipendente_default_esecutore_disposizione, toPropertyIndex(IDDIPAVVIO), true, ...)
  base.addImportMetadato(id_utente_responsabile, toPropertyIndex(IDUTENTERESP), true, ...)
  base.addImportMetadato(id_modello_evento, toPropertyIndex(IDTEMPLATEEVENTO), true, ...)
  base.addImportMetadato(id_ambito, toPropertyIndex(IDAMBITO), false, ...)
  base.addImportMetadato(data_inizio_evento, toPropertyIndex(DATAEVENTO), false, ...)
  base.addImportMetadato(id_tipologia_evento, toPropertyIndex(IDTIPOLOGIA), false, ...)
  base.addImportMetadato(nro_evento, toPropertyIndex(NROEVENTO), false, ...)
  base.addImportMetadato(costo_fisso, toPropertyIndex(COSTOFISSO), true, ...)
  base.addImportMetadato(costo_totale, toPropertyIndex(COSTOTOTALE), true, ...)
  base.addImportMetadato(id_classe_evento, toPropertyIndex(IDCLASSE), false, ...)
   
}


// ──────────────────────────────────

// *************************************************************************************************************************************
// get mandatory reference types which are defined in modello evento 
// 
// (returns a collection of refTypes)
// note: initially we ignore Numero >1 (means 2, user must enter 2 riferimenti, will behave as 1), we use 0=non mandatory, 1+= mandatory
// *************************************************************************************************************************************
public IDCollection Evento.getMandatoryRefTypes()
{
  IDCollection mandatoryReferenceTypes of ReferenceType = new()
   
  if (ModelloEvento == null)
  {
    this.loadModelloEvento()
  }
   
  // if collection of reference types defined in modello evento(EVA_TMPL_RFERENCES), if not loaded we load it
  if (!(ModelloEvento.ModelloEventoRiferimenti.loaded))
  {
    ModelloEvento.loadModelloEventoRiferimenti(...)
  }
   
  // loop modello evento references to find mandatoary reference types
  for each ModelloEventoRiferimento mer in ModelloEvento.ModelloEventoRiferimenti
  {
    if (mer.MANDATORYROWS > 0)
    {
      ReferenceType rt = ReferenceType.get(mer.IDTipoRiferimento)
      if (rt)
      {
         mandatoryReferenceTypes.add(rt)
      }
    }
  }
  return mandatoryReferenceTypes
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static Evento Evento.create(
  int idUser                                    // 
  int idDipendenteAvvio                         // 
  int idDipendenteOfDefaultDisposizioneExecutor // 
  int idModelloEvento                           // 
  optional date startDate = #1899/12/30#        // 
  optional int idAmbito = 0                     // 
  optional int idTipologia = 0                  // 
  optional int idUtenteResponsabile = 0         // 
)
{
  EventoCreationInput eci = new()
  eci.IDUSER = idUser
  eci.IdDipendenteAvvio = idDipendenteAvvio
  eci.DefaultIDExecutorOfDisposizione = idDipendenteOfDefaultDisposizioneExecutor
  eci.IDModelloEvento = idModelloEvento
  eci.IdAmbito = idAmbito
  eci.IdTipologia = idTipologia
  eci.IdUtenteResponsabile = idUtenteResponsabile
  if (startDate != toDate(1899, 12, 30))
  {
    eci.StartDate = startDate
  }
  else 
  {
    eci.StartDate = today()
  }
   
  Evento e = new()
   
  // trick to "pass" an object to the OnInit event
  e.setObjectTag("eci", eci)
   
  e.init()
   
   
  return e
   
}


// ──────────────────────────────────

// ****************************************************************************************************************************************************************************************************************
// this procedure generate a Riferimento object using the Template Evento reference example (if it exists), so we check first the riferimenti designed in the Template, considering only the ones of a certain
// reference Type
// We should pass the RdataInfoMap to populate correctly the Values X of Riferimento using the right indexes
// ****************************************************************************************************************************************************************************************************************
public Riferimento Evento.createDefaultReference(
  int IDReferenceType // 
  IDMap rDataInfoMap  // needed to compute the proper VALUEX of resulting Riferimento
)
{
  int idForAllInheritedFromRiferimentoTemplate = 0
  Riferimento templateReference = new()
  Riferimento defaultReference = new()
   
  DevTools.RefactoringOpportunity("Retrieve the template references. This should be done into the evento creation procedure, not here. Move the code below inside a procedure in the evento creation method. Now 
        i put the code here to make it work for the q2023")
  IDCollection templateReferences of Riferimento = new()
  select into collection (templateReferences)
  from 
    Riferimento // master table
  where
    ISMODELLO == Yes
    IDEvento == IDTEMPLATEEVENTO
    IDTipoRiferimento == IDReferenceType
  if (templateReferences.count() > 0)
  {
    templateReferences.moveFirst()
    DevTools.RefactoringOpportunity("Probably there could be cases of more references defined in the modello with the same type, with also custom Data. Actually, as it was said in the DM of 25/09/2023, we 
          support only a mandatory reference for every manadatory referece types defined in Modello Evento")
    templateReference = (Riferimento)templateReferences.getAt()
    idForAllInheritedFromRiferimentoTemplate = templateReference.IDForAll
  }
   
  defaultReference.IDEvento = IDEVENTO
  defaultReference.IDTipoRiferimento = IDReferenceType
  if (idForAllInheritedFromRiferimentoTemplate > 0)
  {
    defaultReference.IDForAll = idForAllInheritedFromRiferimentoTemplate
    DevTools.RefactoringOpportunity("As it was designed to force the inde UI to work in our case, we are setting the tag 'descr' to modify the field DESTINATION of the Riferimenti's panel. For the use that we 
          are doing of References, we can create a new class Reference UI that extends the Riferimenti class to have the fields that we want to show in the UI")
    int:referenceTypeKordModules kordapp = 0
    select into variables (found variable)
      set kordapp = SourceKordapp
    from 
      TipiRiferimento // master table
    where
      IDTipoRiferimento == IDReferenceType
    MainModule mm = MainModule.retrieve(kordapp, idForAllInheritedFromRiferimentoTemplate, ...)
     
    // set ui tag Descr to show the descrption in the field DESCRIPTION
    defaultReference.setTag("descr", mm.getDescription())
     
    // set the Module Object tag to be able to retrieve the module in the the Crea Evento step, so computing the custom Data
    defaultReference.setObjectTag("module", mm)
     
    // read the dati pers of Template riferimento
    templateReference.loadRDatiPersonalizzati()
     
    // copy data into Runtime populated properties of Riferimento "defaultReference"
    IDArray rdataInfoKeys = rDataInfoMap.getKeys()
    int keysLength = rdataInfoKeys.length()
    for (int keyIndex = 0; keyIndex < keysLength; keyIndex = keyIndex + 1)
    {
      string propertyCode = rdataInfoKeys.getValue(keyIndex)
      if (propertyCode != "")
      {
         RiferimentoDatoPersonalizzatoInfo storedRDataInfo = (RiferimentoDatoPersonalizzatoInfo)rDataInfoMap.getObject(propertyCode)
         string valueRDatoPersTemplate = templateReference.getRDatoPersonalizzato(storedRDataInfo)
         int propertyIndex = defaultReference.getPropertyIndex(propertyCode, true, true, true, true)
          
         // set the corrisponding property to see it in the UI
         defaultReference.setProperty(propertyIndex, valueRDatoPersTemplate)
      }
    }
  }
  return defaultReference
}


// ──────────────────────────────────

// **********************************************************************************************************************************************************
// this method sets the value of the 2 properties that control the visibility logic of the responsible widget in an evento panel, those are 2 boolean flags: 
// responsibleVisible: tells whether in the UI the responsible flag should be made visible or not
// responsibleReadonly: same but for readonly
// 
// 2 examples:
// when user is anonymous and the responsible is set in modello as normal user we hide the responsible
// when user is normal, without pers1 in eventi, a responsible is set: we show responsible and make it redaonly
// 
// NOTE: this method centralizes the UI logic making it testable and reusable
// **********************************************************************************************************************************************************
public void Evento.computeResponsabileWidgetProperties()
{
  boolean responsibleVisible = false
  boolean responsibleReadonly = false
   
  if (QappCore.Loggeduser = null)
  {
    QappCore.DTTLogMessage("It is not possible to call computeReponsabileWIdgetProperties if the loggerUser is null", ..., DTTError)
  }
   
  boolean anonymousSessionInProgress = Utente.isAnonymousUtenteLoggedIn()
   
  boolean isPers1InEventi = QappCore.Loggeduser.hasSpecificPrivilege(Eventi, Pers1)
  boolean utenteResponsibleIsSetInModello = (ModelloEvento.IDUTENTERESP > 0) and (ModelloEvento.ISFUNCTION == No)
  boolean responsabileOfTypeFunzione = (IdFunzioneResponsible > 0)
   
  if (responsabileOfTypeFunzione)
  {
    Funzione f = Funzione.getFromDB(IdFunzioneResponsible, quickLoad)
    boolean funzioneHasOnlyOneActiveMember = f.getActiveMemmbersCount() == 1
     
    responsibleVisible = true
    responsibleReadonly = false
     
    // handle the special case of funzione with only one active member:
    if (anonymousSessionInProgress)
    {
       
      // anonymous in case of funzione
      if (funzioneHasOnlyOneActiveMember)
      {
         responsibleVisible = false
         responsibleReadonly = false
      }
    }
    else 
    {
       
      // not anymous in case of funzione
      if (funzioneHasOnlyOneActiveMember)
      {
         responsibleVisible = true
         responsibleReadonly = true
      }
    }
  }
  else 
  {
     
    // case of NON FUNZIONE RESPONSABILE
    responsibleVisible = true
    responsibleReadonly = false
     
    // handle the special case of funzione with only one active member:
    if (anonymousSessionInProgress)
    {
      QappCore.DTTLogMessage("This case is even not possible because anonyous cannot be set in URLToken if the modello has no responsible", ..., DTTInfo)
      responsibleVisible = false
    }
    else 
    {
      // not anymous in case of normal responsabile of utente type
      boolean allowChangeResponsible = (isPers1InEventi or !(utenteResponsibleIsSetInModello))
      responsibleVisible = true
      responsibleReadonly = !(allowChangeResponsible)
    }
  }
   
  ResponsibleWidgetVisible = responsibleVisible
  ResponsibleWidgetReadonly = responsibleReadonly
}


// ──────────────────────────────────

// ***************************************************************************************************************************************
// this procedure is needed in case of loadFromMap needs more data or it require module specific procedures to intanciate the final object
// ***************************************************************************************************************************************
public static QualibusDocument Evento.prepareEventoForQsyncro(
  IDMap data                              // 
  IDCollection metadata of ImportMetadato // 
  QualibusDocument emptyObject            // 
  string dateFormat                       // 
)
{
  // extract the useful data from Data to generate the evento properly
  int idUtente = 0
  int idDipendenteAvvio = 0
  int idDipendenteDefaultDisposizioneExec = 0
  int idModelloEvento = 0
  date dataInizio = #1899/12/30#
  int idAmbito = 0
  int idTipologia = 0
  decimal costoFisso = 0
  decimal costoTotale = 0
  string nrEvento = ""
  for each ImportMetadato im in metadata
  {
    switch (im.Caption)
    {
      case id_utente_inserimento:
         idUtente = toInteger(data.getValue(im.Caption))
      break
      case id_dipendente_avvio:
         idDipendenteAvvio = toInteger(data.getValue(im.Caption))
      break
      case id_dipendente_default_esecutore_disposizione:
         idDipendenteDefaultDisposizioneExec = toInteger(data.getValue(im.Caption))
      break
      case id_modello_evento:
         idModelloEvento = toInteger(data.getValue(im.Caption))
      break
      case data_inizio_evento:
         dataInizio = data.getValue(im.Caption)
      break
      case id_ambito:
         idAmbito = toInteger(data.getValue(im.Caption))
      break
      case id_tipologia_evento:
         idTipologia = toInteger(data.getValue(im.Caption))
      break
      case costo_fisso:
         costoFisso = toFloat(data.getValue(im.Caption))
      break
      case costo_totale:
         costoTotale = toFloat(data.getValue(im.Caption))
      break
      case nro_evento:
         nrEvento = toString(data.getValue(im.Caption))
      break
    }
  }
   
  int idEvento = 0
  select into variables (found variable)
    set idEvento = IDEVENTO
  from 
    Eventi // master table
  where
    NROEVENTO == nrEvento
   
  if (idEvento > 0)
  {
    QualibusDocument qd = emptyObject
    qd.loadFromMap(data, dateFormat)
    return qd
  }
  else 
  {
    // CREATE evento with the proper method
    Evento newEvento = Evento.create(idUtente, idDipendenteAvvio, idDipendenteDefaultDisposizioneExec, idModelloEvento, dataInizio, idAmbito, idTipologia, ...)
    newEvento.COSTOFISSO = costoFisso
    newEvento.COSTOTOTALE = costoTotale
    newEvento.NROEVENTO = nrEvento
     
    for each Disposizione d in newEvento.Disposizioni
    {
      d.IDRESPATTIVITA = toInteger(data.getValue(id_dipendente_default_esecutore_disposizione))
    }
     
    // set custom data
    newEvento.loadDatiPers()
    for each DatoPersonalizzato MainModuleDatiPersonalizzatiBase in newEvento.DatiPersonalizzati
    {
      MainModuleDatoPersonalizzatoInfo cdataFieldInfo = cast(MainModuleDatiPersonalizzatiBase.getCustomDataInfo())
      string computedcaption = replace(trim(lower(cdataFieldInfo.Caption)), " ", "_")
      if (data.containsKey(computedcaption))
      {
         string dpValue = data.getValue(computedcaption)
         newEvento.setDatoPersonalizzato(dpValue, cdataFieldInfo)
      }
    }
     
    // in case close evento
    boolean shouldBeClosed = toInteger(data.getValue(stato_evento)) == Chiuso
    if (shouldBeClosed)
    {
      WkfFlow wkfflow = new()
      wkfflow.IDGRAVITA = 322
      Utente u = new()
      u.IDUTENTE = toInteger(data.getValue(id_utente_inserimento))
      newEvento.close(u, wkfflow)
    }
     
    return cast(newEvento)
  }
   
}


// ──────────────────────────────────

// **********************************************************************************************************************************************************************************************
// This method at this moment is place holder to implement the visiblity of Disposizioni tab based on Classe Evento, URL Token and logged in user (Note: Loggedin user can be anonymous user too)
// 
// It will be used in ECFW qualibus appllication 
// **********************************************************************************************************************************************************************************************
public boolean Evento.canShowDisposizioniTabInECFW(
  optional UrlToken urlToken // 
)
{
  boolean isAnonymousUser = QappCore.Loggeduser.isAnonymous()
  boolean hideDisposizioni = false
   
  if (urlToken)
    hideDisposizioni = urlToken.NascondiDisposizioni
   
  ClasseEvento ce = this.getClasseEvento()
  boolean showDisposizioniSelectedInClasseEvento = ce.SHOWDISPOSIZIONI == Yes
  boolean isWorkflowEvento = WORKFLOWTYPE == Workflow
  boolean showDisposizioniTab = showDisposizioniSelectedInClasseEvento and !(hideDisposizioni) and !(isAnonymousUser) and isWorkflowEvento
   
  return showDisposizioniTab
}


// ──────────────────────────────────

// **************************************************************************
// loads in memory the modello evento and assigns it to the matching property
// **************************************************************************
private void Evento.loadModelloEvento()
{
  ModelloEvento = ModelloEvento.getFromDB(IDTEMPLATEEVENTO, ...)
}


// ──────────────────────────────────

// *******************************************************************************
// this is a special method usable in unit tests to test a specific private method
// NOTE: this is odd but it is convenient to test old code not TDD generated
// 
// the method param must match with one of the switch cases below
// parametersArray could make sense in some cases but it is optional
// *******************************************************************************
public string Evento.privateMethodsTester(
  string method                    // 
  optional IDArray parametersArray // optional Array that might be used to pass more data to simulate specific conditions
)
{
  string result = ""
  switch (method)
  {
    case "populateMsgBody":
      result = this.PopulateMsgBody()
    break
    default:
      QappCore.DTTLogMessage(formatMessage("method |1 not supported", method, ...), ..., DTTError)
    break
  }
  return result
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public ModelloEvento Evento.getModelloEvento()
{
  if (ModelloEvento == null)
    this.loadModelloEvento()
   
  return ModelloEvento
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Evento.setEventoAsClosed(
  Utente utenteChiusura // 
  int IdGravita         // 
)
{
  if (IdGravita > 0)
  {
    IDUTENTECHIUS = utenteChiusura.IDUTENTE
    date time todayWithNoTime = today()
    DATACHIUSURA = todayWithNoTime
    IDGRAVITA = IdGravita
    IDSTATOEVENTO = 9
  }
  else 
  {
    QappCore.DTTLogMessage("Id Gravita (so esito) is not selected.", ...)
  }
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Evento.handleOnEndTransactionForIdUtenteResp()
{
  ModelloEvento me = this.getModelloEvento()
  if (me.WORKFLOWTYPE == Workflow)
  {
    WkfStep firstStep = me.getFirstStep()
     
    if (firstStep.EXECUTORTYPE == Responsabile)
    {
      Disposizioni.moveFirst()
      Disposizione d = (Disposizione)Disposizioni.getAt()
      Utente u = Utente.get(IDUTENTERESP)
      Personale p = Personale.getPersonaleLinkedToUtente(u)
      d.IDRESPATTIVITA = p.IDDIPENDENTE
    }
  }
  else 
  {
    QappCore.DTTLogMessage("Nothing to do for normale.", ..., DTTInfo)
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean Evento.isNormaleType()
{
  if (WORKFLOWTYPE == Normal)
  {
    return true
  }
   
  return false
}


// ──────────────────────────────────



// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Evento.rollbackToSelectedDisposizione(
  Disposizione selectedDisposizione // 
  string:executionModes mode        // 
)
{
  if (!(selectedDisposizione))
  {
    QappCore.DTTLogMessage("Selected disposizione is null.", ..., DTTError)
    return 
  }
   
  if (!(Disposizioni.loaded))
  {
    this.loadCollectionFromDB(Disposizioni, ...)
  }
   
  Disposizione d = new()
  Disposizioni.resetSortCriteria()
  Disposizioni.addSortCriteria(d.NRORIGA * -1)
  Disposizioni.doSort()
   
  int nroRigaDisposizione = selectedDisposizione.NRORIGA
   
  for each Disposizione eventoDisposizione in Disposizioni
  {
    boolean shouldBeSaved = false
    if (eventoDisposizione.NRORIGA > nroRigaDisposizione)
    {
      eventoDisposizione.deleted = true
      shouldBeSaved = true
    }
     
    if (eventoDisposizione.IDATTIVITA == selectedDisposizione.IDATTIVITA)
    {
      eventoDisposizione.reOpen()
      shouldBeSaved = true
    }
     
    shouldBeSaved = shouldBeSaved and mode == normalMode
     
    if (shouldBeSaved)
    {
      eventoDisposizione.saveToDB(...)
    }
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public Disposizione Evento.createFromWKFDisposizioneDraft(
  WkfDisposizioneDraft nd         // 
  Disposizione SourceDisposizione // 
)
{
   
  WkfStep nextStep = new()
  nextStep.IDSTEP = nd.IDSTEP
  nextStep.loadFromDB(...)
   
  Disposizione d = new()
  d.init()
  d.IDEVENTO = SourceDisposizione.IDEVENTO
  d.IDTIPOATTIVITA = nd.IDTIPOATTIVITA
  d.IDSTEP = nd.IDSTEP
  d.DATAPREVISTA = nd.ExecutionDate
  d.ORAPREVISTA = nd.STARTTIME
  d.ORARIOFINE = nd.ENDTIME
  d.ORE = (nd.ENDTIME - nd.STARTTIME) * 24
  d.DESCRATTIVITA = nextStep.STEPDESCRIPTION + Tools.getNewLineChar() + nd.DESCRIPTION
  d.ISFUNCTION = nd.ISFUNCTION
  d.IDRESPATTIVITA = nd.IDEXECUTORDIPENDENTE
  d.IDUTENTEINS = QappCore.Loggeduser.IDUTENTE
  d.IDUTENTEULTMOD = QappCore.Loggeduser.IDUTENTE
  d.DATAINS = now()
  d.DATAULTIMAMOD = now()
  d.computeOraInizio()
  d.computeOraFine()
  d.updateOre()
   
  d.copyNotificationSettingsFromStep(nextStep)
   
  this.CreateChecklistFromModelloStep(d, nextStep)
  if (d.Checklist.ChecklistInstanceItemCollection.count() > 0)
    d.STATOCKL = "A"
  else 
    d.STATOCKL = "N"
   
  this.CreateNTFfromModelloStep(d, nextStep)
   
  Disposizioni.add(d)
  return d
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public int Evento.GetNextNroRigaForDisposizione()
{
  int maxNrRiga = 0
  if (Disposizioni && Disposizioni.count() > 0)
  {
    for each Disposizione d in Disposizioni
    {
      if (d.NRORIGA > maxNrRiga)
         maxNrRiga = d.NRORIGA
    }
  }
   
  return maxNrRiga + 1
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Evento.createDocCollegatiFromModello()
{
  for each DocCollegato dc in ModelloEvento.DocCollegati
  {
    DocCollegato dc1 = DocCollegato.create(Eventi, IDEVENTO, dc.IdDocumento, dc.IdRevisione, dc.Note)
    DocCollegati.add(dc1)
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public Disposizione Evento.GetDisposizioneWithNroRiga(
  int nroRiga // 
)
{
  if (nroRiga <= 0)
    return null
   
  if (Disposizioni.count() == 0)
    return null
   
  for each Disposizione d in Disposizioni
  {
    if (d.NRORIGA == nroRiga)
    {
      return d
    }
  }
   
  return null
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string Evento.getCaption(
  string additionalDescription // 
)
{
  string caption = ""
   
  if (additionalDescription != "")
  {
    caption = "Eventi " + "{{icon-fa-chevron-right}}" + " " + additionalDescription + " " + "{{icon-fa-chevron-right}}"
  }
  else 
  {
    caption = "Eventi " + "{{icon-fa-chevron-right}}"
  }
   
  return caption
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean Evento.isLocked()
{
  return IDSTATOEVENTO == Chiuso
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean Evento.isClosed()
{
  return IDSTATOEVENTO == Chiuso
}


// ──────────────────────────────────

// ***************************************
// returns True if the evento supports SCM
// ***************************************
public boolean Evento.supportsSingleClassMode()
{
  ClasseEvento ce = this.getClasseEvento()
  boolean supportsSingleClassMode = false
  if (ce != null)
    supportsSingleClassMode = ce.INDEPENDANTMODULE == Yes
   
  return supportsSingleClassMode
}


// ──────────────────────────────────

// ***************************************************************************
// Visibility of the panel-level "Add Disposizione" command for Normal eventi.
// 
// Selection-independent: shown even when no row is selected (e.g. on an empty
// Normal evento). For Workflow eventi  — see canAddMultipleDisposizione.
// ***************************************************************************
public boolean Evento.canAddDisposizioneFromPanel(
  Utente user // 
)
{
  if (this.isLocked())
    return false
   
  if (!(user.hasSpecificPrivilegeForKordApp(Eventi, Update)))
    return false
   
  return this.isNormaleType()
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static Evento Evento.duplicateEvento(
  Evento sourceEvento                   // 
  date dataEvento                       // 
  boolean shouldDuplicateReferences     // 
  boolean shouldDuplicateCdata          // 
  boolean shouldDuplicateChecklistNotes // 
)
{
  Evento duplicatedEvento = Evento.create(sourceEvento.IDUTENTERESP, sourceEvento.IDDIPAVVIO, sourceEvento.IDDIPAVVIO, sourceEvento.IDTEMPLATEEVENTO, dataEvento, sourceEvento.IDAMBITO, sourceEvento.IDTIPOLOGIA
           , sourceEvento.IDUTENTERESP)
   
  duplicatedEvento.DESCRTITOLO = sourceEvento.DESCRTITOLO + formatMessage(" (copia dell'Evento |1)", sourceEvento.NROEVENTO, ...)
  duplicatedEvento.DESCREVENTO = sourceEvento.DESCREVENTO
  duplicatedEvento.COSTOFISSO = sourceEvento.COSTOFISSO
  duplicatedEvento.COSTOTOTALE = sourceEvento.COSTOTOTALE
  duplicatedEvento.IDDIPAVVIO = sourceEvento.IDDIPAVVIO
  duplicatedEvento.IDUTENTEINS = sourceEvento.IDUTENTEINS
  duplicatedEvento.IDUTENTERESP = sourceEvento.IDUTENTERESP
   
  duplicatedEvento.copyMainModuleDataFromSource(sourceEvento, shouldDuplicateCdata, shouldDuplicateReferences)
   
  duplicatedEvento.copyAdditionalCollectionsFromSource(sourceEvento, shouldDuplicateChecklistNotes)
   
  return duplicatedEvento
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Evento.copyAdditionalCollectionsFromSource(
  Evento sourceEvento                   // 
  boolean shouldDuplicateChecklistNotes // 
)
{
  EventiCosti.clear()
  Disposizioni.clear()
   
  // costi
  if (!(sourceEvento.EventiCosti.loaded))
  {
    sourceEvento.loadCollectionFromDB(sourceEvento.EventiCosti, ...)
  }
   
  for each EventiCosto sourceCosto in sourceEvento.EventiCosti
  {
    EventiCosto ec = EventiCosto.create(this, ...)
    ec.ORE = sourceCosto.ORE
    ec.PREZZO = sourceCosto.PREZZO
    ec.Importo = sourceCosto.Importo
    ec.NOTE = sourceCosto.NOTE
    ec.IDTIPOCOSTO = sourceCosto.IDTIPOCOSTO
     
    EventiCosti.add(ec)
  }
   
   
  // diposizioni
   
  if (!(sourceEvento.Disposizioni.loaded))
  {
    sourceEvento.loadCollectionFromDB(sourceEvento.Disposizioni, ...)
  }
   
  date sourceEventoDate = sourceEvento.DATAEVENTO
  date targetEventoDate = DATAEVENTO
   
  // if the evento is workflow we duplicate ONLY the first disposizioni, if it is normale we duplicate all of disposizioni
   
  if (this.isWorkflowType())
  {
    sourceEvento.Disposizioni.moveFirst()
    Disposizione sourceFirstDisp = (Disposizione)sourceEvento.Disposizioni.getAt()
    date firstDisposizioneDataPrevista = sourceFirstDisp.computeDataPrevistaForEventoDuplication(sourceEventoDate, targetEventoDate)
    Disposizione duplicatedDisp = sourceFirstDisp.duplicateDisposizione(QappCore.Loggeduser, this, firstDisposizioneDataPrevista, shouldDuplicateChecklistNotes)
     
    Disposizioni.add(duplicatedDisp)
  }
  else 
  {
    for each Disposizione sourceDisp in sourceEvento.Disposizioni
    {
      date disposizioneDataPrevista = sourceDisp.computeDataPrevistaForEventoDuplication(sourceEventoDate, targetEventoDate)
      Disposizione newDisp = sourceDisp.duplicateDisposizione(QappCore.Loggeduser, this, disposizioneDataPrevista, shouldDuplicateChecklistNotes)
      Disposizioni.add(newDisp)
    }
  }
   
}


// ──────────────────────────────────

// *******************************************************************************
// Inserts a new disposizione before the given anchor in the evento's Disposizioni
// collection, shifting NRORIGA of the anchor and every subsequent disposizione
// up by 1. The new disposizione takes the anchor's previous NRORIGA.
// 
// Returns the newly created disposizione (added to evento.Disposizioni, not yet
// persisted). The caller is responsible for triggering save when ready.
// 
// Insert is only meaningful for Normal eventi — Workflow eventi organize
// disposizioni by step rather than by free-form NRORIGA. The visibility layer
// (Disposizione.computeCommandVisibility) enforces this; this method assumes
//  the caller respects it.
// 
//  Returns null if anchor is invalid or doesn't belong to this evento.
// *******************************************************************************
public Disposizione Evento.insertDisposizioneBefore(
  Disposizione anchor               // 
  TipoDisposizione tipoDisposizione // 
)
{
  if (anchor == null or anchor.IDATTIVITA <= 0)
  {
    return null
  }
   
  if (anchor.IDEVENTO != IDEVENTO)
  {
    QappCore.DTTLogMessage("anchor does not belong to this evento", ..., DTTError)
    return null
  }
   
  if (!(this.isNormaleType()))
  {
    QappCore.DTTLogMessage("insertDisposizioneBefore called on non-Normal evento", ..., DTTError)
    return null
  }
   
  if (!(Disposizioni.loaded))
  {
    this.loadCollectionFromDB(Disposizioni, ...)
  }
   
  int targetNroRiga = anchor.NRORIGA
   
  for each Disposizione d in Disposizioni
  {
    if (d.NRORIGA >= targetNroRiga)
    {
      d.NRORIGA = d.NRORIGA + 1
    }
  }
   
  // Create the new row at the freed-up position, inheriting context from anchor
  Disposizione insertedDisposizione = Disposizione.create(this)
  insertedDisposizione.NRORIGA = targetNroRiga
  insertedDisposizione.IDTIPOATTIVITA = tipoDisposizione.IDTIPOATTIVITA
  insertedDisposizione.DESCRATTIVITA = tipoDisposizione.DESCRDEFAULT
  Disposizioni.add(insertedDisposizione)
   
  // // Re-sort so the panel renders in NRORIGA order. NRORIGA defines display
  this.sortDisposizioniByNroRiga()
  return insertedDisposizione
}


// ──────────────────────────────────

// *********************************************************************************
//  Removes a disposizione from the evento and compacts NRORIGA so the remaining
//  disposizioni keep a gap-free sequence (1..N).
// 
//  Marks the disposizione as deleted (InDe defers the actual collection removal
//  to the next save) and decrements NRORIGA on every following disposizione by 1,
//  then re-sorts the collection by NRORIGA so the panel renders in the correct
//  order.
// 
//  Returns silently if toRemove is null, unsaved, or doesn't belong to this evento.
//  Caller is responsible for triggering save and refreshing the panel binding.
// *********************************************************************************
public void Evento.removeDisposizione(
  Disposizione disposizioneToBeRemoved // 
)
{
  if (disposizioneToBeRemoved == null or disposizioneToBeRemoved.IDATTIVITA <= 0)
  {
    return 
  }
   
  if (disposizioneToBeRemoved.IDEVENTO != IDEVENTO)
  {
    QappCore.DTTLogMessage("disposizioneToBeRemoved does not belong to this evento", ..., DTTError)
    return 
  }
   
   
  if (!(Disposizioni.loaded))
  {
    this.loadCollectionFromDB(Disposizioni, ...)
  }
   
  int removedNroRiga = disposizioneToBeRemoved.NRORIGA
   
  disposizioneToBeRemoved.deleted = true
   
  // Compact: every row after the deleted slot moves up by 1
  for each Disposizione d in Disposizioni
  {
    if (!(d.deleted) and d.NRORIGA > removedNroRiga)
    {
      d.NRORIGA = d.NRORIGA - 1
    }
  }
   
  // Re-sort so the panel renders in NRORIGA order. NRORIGA defines display
   
  this.sortDisposizioniByNroRiga()
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Evento.sortDisposizioniByNroRiga()
{
  if (!(Disposizioni.loaded))
    return 
//  Disposizione d = new()
  int nroRigaIndex = toPropertyIndex(Disposizione.NRORIGA)
  Disposizioni.resetSortCriteria()
  Disposizioni.addSortCriteria(nroRigaIndex)
  Disposizioni.doSort()
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Evento.loadEventoCosti(
  optional boolean forceReloadFromDB = 1 // Write a comment for this parameter or press backspace to delete this comment
)
{
  EventiCosti.clear()
   
  if (forceReloadFromDB)
  {
    EventiCosti.loaded = false
  }
   
  IDCollection eventoCosti of EventiCosto = new()
  select into collection (eventoCosti)
  from 
    EventiCosto // master table
  where
    IDEVENTO == IDEVENTO
  EventiCosti.addAll(eventoCosti, ...)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean Evento.reOpen(
  Utente user // 
)
{
  boolean hasEventoBeenReopened = false
  if (this.isClosed())
  {
    boolean userCanReOpenEvento = this.canUserCloseOrReopenEvento(user)
     
    if (userCanReOpenEvento)
    {
      // common for wkf and normale
      IDGRAVITA = null
      DATACHIUSURA = null
      IDUTENTECHIUS = null
       
      IDSTATOEVENTO = this.getStatoEventoBasedOnDisposizioni()
       
      hasEventoBeenReopened = true
    }
    else 
    {
      hasEventoBeenReopened = false
    }
  }
   
   
  return hasEventoBeenReopened
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public int Evento.getStatoEventoBasedOnDisposizioni()
{
  if (!(Disposizioni.loaded))
  {
    this.loadCollectionFromDB(Disposizioni, ...)
  }
   
  boolean allDisposizioniAreClosed = this.allDisposizioniAreClosed()
  boolean atLeastOneDisposizioneExists = Disposizioni.count() > 0
   
  if (!(atLeastOneDisposizioneExists))
  {
    return Immesso
  }
  if (!(allDisposizioniAreClosed))
  {
    return InAttuazione
  }
  if (allDisposizioniAreClosed and atLeastOneDisposizioneExists)
  {
    return Attuato
  }
   
  return Attuato
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean Evento.allDisposizioniAreClosed()
{
  if (!(Disposizioni.loaded))
  {
    this.loadCollectionFromDB(Disposizioni, ...)
  }
   
  boolean atLeastOneDisposizioneIsOpen = false
  for each Disposizione d in Disposizioni
  {
    if (!(d.isClosed()))
    {
      atLeastOneDisposizioneIsOpen = true
      break 
    }
  }
   
  return !(atLeastOneDisposizioneIsOpen)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public Disposizione Evento.getMatchingDisposizione(
  int idAttivita // 
)
{
  Disposizione disposizione = null
  for each Disposizione d in Disposizioni
  {
    if (d.IDATTIVITA == idAttivita)
    {
      disposizione = d
      break 
    }
  }
  return disposizione
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string Evento.checkIfEventoIsClosable(
  Utente closingUser // 
)
{
  string eventoIsNotClosableResultMessage = ""
   
  if (!(Disposizioni.loaded))
  {
    this.loadCollectionFromDB(Disposizioni, ...)
  }
   
  // first check: user has roles to close the evento
  if (!(this.canUserCloseOrReopenEvento(closingUser)))
  {
    eventoIsNotClosableResultMessage = "L'utente non possiede i privilegi necessari per chiudere l'evento."
  }
   
  // second check: there is at least one esito defined for the class
  int countOfAssignedEsitiOnClass = 0
  select into variables (found variable)
    set countOfAssignedEsitiOnClass = count(...)
  from 
    EventiEsitoClasseLinks // master table
  where
    IDCLASSE == IDCLASSE
   
  if (countOfAssignedEsitiOnClass <= 0)
  {
    eventoIsNotClosableResultMessage = "Non è possibile chiudere l'evento: Nessun Esito è stato assegnato alla classe."
  }
   
  // third check: all disposizioni are closed
  boolean allDisposizioniAreClosed = true
  for each Disposizione d in Disposizioni
  {
    if (!(d.isClosed()))
    {
      allDisposizioniAreClosed = false
      break 
    }
  }
   
  if (!(allDisposizioniAreClosed))
  {
    eventoIsNotClosableResultMessage = "Non è possibile chiudere l'evento: Sono presenti disposizioni ancora aperte."
  }
   
  // fourth check: evento is closed
  if (this.isClosed())
  {
    eventoIsNotClosableResultMessage = "Non è possibile chiudere l'evento: l'evento è già chiuso."
  }
   
  return eventoIsNotClosableResultMessage
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public IDCollection Evento.getAvailableEsitiForEventiClosure()
{
  IDCollection availableEsiti of EventiEsitoClasseLinks = new()
  select into collection (availableEsiti)
    set IDCLASSE = IDCLASSE
    set IDGRAVITA = IDGRAVITA
    set ATTIVO = ATTIVO
    set IDEventiEsitoClasseLink = IDEVAGRAVITACLASSI
    set IDPARENTTEMPLATEREMOTE = IDPARENTTEMPLATEREMOTE
  from 
    EventiEsitoClasseLinks // master table
  where
    IDCLASSE == IDCLASSE
    ATTIVO == Yes
   
  return availableEsiti
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean Evento.canUserCloseOrReopenEvento(
  Utente user // 
)
{
  ClasseEvento ce = ClasseEvento.get(IDCLASSE)
  TipologiaEvento te = TipologiaEvento.GetTipologia(IDTIPOLOGIA)
   
  boolean userHasImmissioneRole = user.hasSpecificRoleOnEventoClasse(ce, Immissione)
  boolean userHasDisposizioniRole = user.hasSpecificRoleOnEventoClasse(ce, Disposizioni)
  boolean userHasChiusuraRole = user.hasSpecificRoleOnEventoClasse(ce, Chiusura)
   
  boolean isLoggedUserEventoResponsible = user.IDUTENTE == IDUTENTERESP
   
  boolean isUserPers1InEventi = user.hasSpecificPrivilege(Eventi, Pers1)
   
  boolean isTipologiaOk = te.ATTIVITAOBBLIGATORIE == No and te.COSTOOBBLIGATORIO == No
   
  boolean userCanCloseOrReOpenEvento = (userHasImmissioneRole and userHasDisposizioniRole and isTipologiaOk) or userHasChiusuraRole or isLoggedUserEventoResponsible or isUserPers1InEventi
   
  return userCanCloseOrReOpenEvento
}


// ──────────────────────────────────

// ****************************************************
// Method to retrieve Customdata type from ID CDATA FLD
// ****************************************************
private int Evento.GetCustomDataType(
  int IDCDFld // 
)
{
  int:cdataType CDType = 0
  MainModuleDatoPersonalizzatoInfo cfi = new()
  cfi.IDCDATAFLD = IDCDFld
  try 
  {
    cfi.loadFromDB(0)
    CDType = cfi.Type
  }
  return CDType
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public Disposizione Evento.getNonClosedWorkflowDisposizione()
{
  if (WORKFLOWTYPE != Workflow)
  {
    QappCore.DTTLogMessage("this method is meant for a Workflow Evento", ..., DTTError)
    return null
  }
   
  if (!(Disposizioni.loaded))
  {
    this.loadCollectionFromDB(Disposizioni, ...)
  }
   
  Disposizione nonClosedDisposizione = null
   
  for each Disposizione d in Disposizioni
  {
    if (isNull(d.DATAEFFETTIVA))
    {
      nonClosedDisposizione = d
      break 
    }
  }
   
  return nonClosedDisposizione
}


// ──────────────────────────────────

// ******************************************************************************
// in this method we check that there is a reference for each Mandatory reference
// ******************************************************************************
private boolean Evento.eachMandatoryRiferimentiHasValue(
  inout string listOfNotFoundMandatoryRef // 
)
{
  IDCollection mandatoryRefTypes of ReferenceType = this.getMandatoryRefTypes()
   
  boolean mandatoryReferenceFound = true
  for each ReferenceType rt in mandatoryRefTypes
  {
    IDCollection references of Riferimento = base.GetRiferimenti(rt)
    if (references.count() == 0)
    {
      mandatoryReferenceFound = false
      listOfNotFoundMandatoryRef = SH.Concat(listOfNotFoundMandatoryRef, rt.Name, ", ")
      QappCore.DTTLogMessage(formatMessage("A riferimento for the mandatory reference type (|1) not found in evento (|2).", rt.Name, IDEVENTO, ...), ..., DTTWarning)
    }
  }
  return mandatoryReferenceFound
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void Evento.computeNROEvento()
{
  ClasseEvento ce = this.getClasseEvento()
   
  int eventoYear = year(DATAEVENTO)
   
  NROEVENTO = ce.CreateNRO(updateCounter, IDAMBITO, IDTIPOLOGIA, eventoYear)
}


// ──────────────────────────────────



// ──────────────────────────────────

// ***************************************************************************************************************************************************************
// called from the afterSave of evento this method decreases the Classe coutner if needed
// if the evento being deleted is the last generated by the class counter, counter is decreased
// 
// this method uses the trick to compare the NroEvento that would be created for the current evento with the actual NRO: if they match it means evento is the last
// ***************************************************************************************************************************************************************
private void Evento.ensureCounterIsUpdated()
{
  ClasseEvento ce = this.getClasseEvento()
   
  int eventoYear = year(DATAEVENTO)
   
  string theoreticalNroEvento = ce.CreateNRO(computeOnly, IDAMBITO, IDTIPOLOGIA, eventoYear)
   
  boolean eventoIsTheLastCreatedForTheClasseCounter = theoreticalNroEvento = NROEVENTO
   
  // if the current value of the counter would generate a NRoEvento equals to the one of the current evento it means the current evento is the last being generted for that counter, so the coutner can be decreased
  if (eventoIsTheLastCreatedForTheClasseCounter)
  {
    QappCore.DTTLogMessage("the counter is now decreased since we are deleting the last generated evento", ..., DTTInfo)
    ContatoreEvento ec = this.getContatore()
    ec.decrease()
    ec.saveToDB(...)
  }
}


// ──────────────────────────────────

// *******************************************************
// validate disposizioni start time, end time and executor
// *******************************************************
private boolean Evento.validateEachDispoizioni(
  int reason // 
)
{
   
  // every NON CLOSED disposizione must have start and end time, date and executor
  // we need to validate only open disposizioni (so this should work both for normal and workflow)
   
  boolean allDisposizioniAreValid = true
   
  for each Disposizione d in Disposizioni
  {
    allDisposizioniAreValid = d.validate(reason, ...)
    if (!(allDisposizioniAreValid))
    {
      break 
    }
  }
  return allDisposizioniAreValid
}


// ──────────────────────────────────



// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void Evento.createPublicInfo()
{
  if (ModelloEvento.SUPPORTPUBLICINFO == No)
    return 
   
  EventoPublicStato eps = ModelloEvento.getStatusOfFirstFlow()
   
  EventoPublicInfo = new()
  EventoPublicInfo.init()
  EventoPublicInfo.IDEVENTO = IDEVENTO
  EventoPublicInfo.IdEventoPublicStato = eps.IdEventoPublicStato
   
  // in the auto creation of public info with the current status of the specs we do not populate notes (this could change after NES feedback)
  EventoPublicInfo.Notes = null
   
  EventoPublicInfo.saveToDB(...)
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event Evento.OnInit()
{
  COSTOTOTALE = 0
  EventoCreationInput = getObjectTag("eci")
   
   
  // this trick is needed to perform a simple init without going to this.clear() below that will invalidate init/
  // this has been done because agende Qapp has the bad situation in which a class extends evento and so it is impossible
  // to create an object correctly and init must be called in the subclass and then create evento.create on an evento and manually copy
  // all properties in the subclassed doc, but init must be called manually of inde won't insert the doc to db.
  boolean skipInitCode = (getTag("skipInitCode") != null)
  if (skipInitCode)
    return 
   
  if (EventoCreationInput)
  {
    Utente u = Utente.get(EventoCreationInput.IDUSER)
    this.prepareInstance(u, EventoCreationInput.IDModelloEvento, EventoCreationInput.IdDipendenteAvvio, EventoCreationInput.StartDate, EventoCreationInput.IdUtenteResponsabile)
  }
  else 
  {
    QappCore.DTTLogMessage("EventoCreationInput not initialized, OnInit seems called directly, use Evento.create instead, init will not be performed", ..., DTTWarning)
     
    this.clear()
  }
}


// ──────────────────────────────────

// ************************************************************
// Event raised by the document after loading from the database
// ************************************************************
event Evento.AfterLoad(
  boolean AlreadyLoaded     // A boolean parameter that specifies whether the document was previously loaded.
  IDArray LoadedCollections // IDArray type parameter that contains the collections loaded by the framework. If the value of the Already Loaded parameter is False, the Loaded Collections parameter is NULL.
)
{
  base.AfterLoad(AlreadyLoaded, LoadedCollections)
   
  boolean IsQuickLoadRequested = base.quickLoadRequested()
   
  if (IsQuickLoadRequested)
  {
    return 
  }
   
  if (!(Disposizioni.loaded))
  {
    this.loadCollectionFromDB(Disposizioni, 0)
  }
  if (!(EventiCosti.loaded))
  {
    this.loadEventoCosti(...)
  }
  if (!(Promemoria.loaded))
  {
    this.loadCollectionFromDB(Promemoria, ...)
  }
  if (!(DocCollegati.loaded))
  {
    base.loadDocCollegati()
  }
}


// ──────────────────────────────────

// ****************************************************************************
// Freezable event raised to document when one of its public properties changes
// ****************************************************************************
event Evento.OnChangingProperty(
  int PropertyIndex    // An integer specifying which property was changed. Use the ToPropertyIndex function to make comparisons.
  inout boolean Cancel // A boolean output parameter. If set to True, the property is not changed.
)
{
  // If the value of the additional prop changed I set the original one so the document is ready for saving
  if (PropertyIndex == toPropertyIndex(DescrizioneTXT) && this.getOriginalValue(toPropertyIndex(DescrizioneTXT)) != DescrizioneTXT)
  {
    DESCREVENTO = DescrizioneTXT
  }
}


// ──────────────────────────────────

// *************************************************************
// Event raised to the document at the end of the save procedure
// *************************************************************
event throws exception Evento.AfterSave(
  inout boolean Cancel // Boolean output parameter that makes it possible to cancel the entire save. It is equivalent to the Cancel parameter of the BeforeSave event.
)
{
  if (Disposizioni.count() > 0)
  {
    for each Disposizione d in Disposizioni
    {
      if (d.Checklist != null)
      {
         d.Checklist.saveToDB(...)
      }
    }
  }
   
  base.AfterSave(Cancel)
   
  // if event is inserted send Qualibus messages to users in roles
  if (inserted)
  {
    if (MessaggiResponsible)
      MessaggiResponsible.saveToDB(...)
    MessaggiCC.saveToDB(...)
     
     
    this.createPublicInfo()
     
  }
  else if (deleted)
  {
    // if the evento being deleted is the one who generated the last NROEVENTO we decrease the counter
     
    this.ensureCounterIsUpdated()
  }
}


// ──────────────────────────────────

// **************************************************************************************
// Event raised to the document during the initial steps of the document saving procedure
// **************************************************************************************
event throws exception Evento.BeforeSave(
  inout boolean Skip         // A boolean output parameter. If set to True, tells the framework not to perform operations on the database, because they will be managed independently.
  inout boolean Cancel       // A boolean output parameter. If set to True, tells the framework to cancel the save procedure due to errors that occurred. Since the entire document is saved in the same transa...
  int:beforeSavePhases Phase // An integer from 0 to 3 that specifies the progress status of the save procedure as detailed at the beginning of the article.
)
{
  base.BeforeSave(Skip, Cancel, Phase)
   
  if (Phase == PreSave)
  {
    if (deleted)
    {
      for each Disposizione d in Disposizioni
      {
         d.deleted = true
      }
      for each Promemoria p in Promemoria
      {
         p.deleted = true
      }
      for each DocCollegato dc in DocCollegati
      {
         dc.deleted = true
      }
      for each EventiCosto ec in EventiCosti
      {
         ec.deleted = true
      }
      EventoPublicInfo epi = this.getEventoPublicInfo()
      if (epi)
      {
         epi.deleted = true
         epi.saveToDB(...)
      }
    }
     
    this.computeStato()
  }
   
  if (Phase == Inserting)
  {
    // only when inserting a newly created evento we compute NROEVENTO
    if (inserted)
    {
      if (NROEVENTO == "" or isNull(NROEVENTO))
      {
         this.computeNROEvento()
      }
       
      // now that NRO_EVENTO is known we can prepare messaggi and add them to the collection of messaggi
      this.CreateMessages()
    }
  }
}


// ──────────────────────────────────

// ************************************************************
// Event raised to the document during the validation procedure
// ************************************************************
event Evento.OnValidate(
  int:validateReasons Reason // An integer specifying the reason the document is being validated. The most common values are 0 to indicate complete validation before saving, or 1 to indicate a quick validati...
  inout boolean Error        // A boolean output parameter. If set to True, specifies an error condition and stops the validation immediately.
  inout boolean Skip         // A boolean output parameter. If set to True, does not check the rules for automatic validation of the document (see notes).
)
{
  if (getTag("skipEventoValidation") == true or getTag("skipValidation") == true)
  {
    Skip = true
    QappCore.DTTLogMessage("skipEventoValidation used: evento OnValidate will not be executed", ..., DTTInfo)
    return 
  }
   
  base.OnValidate(Reason, Error, Skip)
  if (deleted)
  {
    Skip = true
    return 
  }
  if (Reason == Quick)
  {
    QappCore.DTTLogMessage("Validazione veloce ma chi la chiama?", ...)
  }
   
  boolean mandatoryRefHasValue = true
  boolean validDisposizioni = true
   
  string listOfNotFoundMandatoryRef = ""
   
  if ((Reason == Complete) or (Reason == validateMandatoryReferences))
    mandatoryRefHasValue = this.eachMandatoryRiferimentiHasValue(listOfNotFoundMandatoryRef)
   
  if (!(mandatoryRefHasValue))
  {
    this.setPropertyError("Alcuni dei riferimenti obbligatori non definiti: " + listOfNotFoundMandatoryRef, IDEVENTO)
  }
  if ((Reason == Complete) or (Reason == validateDisposizioni))
    validDisposizioni = this.validateEachDispoizioni(Reason)
   
  if (!(mandatoryRefHasValue) or !(validDisposizioni))
  {
    Error = true
  }
   
  if (IDTIPOLOGIA == null)
  {
    this.setPropertyError("E' necessario inserire un valore", IDTIPOLOGIA)
    Error = true
  }
  if (IDAMBITO == null)
  {
    this.setPropertyError("E' necessario inserire un valore", IDAMBITO)
    Error = true
  }
   
  if (length(DESCRTITOLO) == 0)
  {
    this.setPropertyError("E' necessario inserire un valore", DESCRTITOLO)
    Error = true
  }
  if (IDUTENTERESP == null)
  {
    this.setPropertyError("Il responsabile deve essere definito.", IDUTENTERESP)
    Error = true
  }
   
   
}


// ──────────────────────────────────

// ***********************************************************
// Event raised to the document when the transaction is closed
// ***********************************************************
event Evento.OnEndTransaction()
{
   
   
  // on a evento still not saved, when we change responsible (e.g. in creation evento from web)...
  if (inserted)
  {
    if (wasModified(IDUTENTERESP))
    {
       
      // ...we set the executor of the first disposizione = idUtenteResp (converting utente in personale) only if the 1st step has executortype=resposible
      this.handleOnEndTransactionForIdUtenteResp()
    }
  }
}


// ──────────────────────────────────

// *****************************************************************************************
// this method completes the preparation
// 
// NOTE: it is not meant to be called directly
// 
// idUtenteResponsabile: pre-resolved responsabile IDUTENTE (0 = caller did not pre-resolve,
// InitializeDefaultValuesforNewEvento will run its own fallback)
// *****************************************************************************************
private void Evento.prepareInstance(
  Utente creationUser      // 
  int idModelloEvento      // 
  int idDipendenteAvvio    // 
  date startDate           // 
  int idUtenteResponsabile // 
)
{
  // Populate evento content from modello data
  // initialise input values
  this.initializeModelloEventoObject(idModelloEvento)
   
  // Initialize evento fields
  this.InitializeDefaultValuesforNewEvento(creationUser.IDUTENTE, idDipendenteAvvio, startDate, idUtenteResponsabile)
   
  this.CreateEventoDataFromModello(creationUser)
   
  this.checkAndSetEventoTitoloOnCreation()
}


// ──────────────────────────────────

// ******************************************************
// Initialize Default Values for new evento to be created
// ******************************************************
private void Evento.InitializeDefaultValuesforNewEvento(
  int idUtente             // 
  int idDipendenteAvvio    // 
  date startDate           // 
  int idUtenteResponsabile // 
)
{
  // assign/initialize evento fields from modello
  IDEVENTO = Sequence.getNextSequence(EVAN_ID_EVENTO, ...)
  QappCore.DTTLogMessage(formatMessage("INFO - ID_EVENTO: '|1' reserved for the new evento", IDEVENTO, ...), ..., DTTInfo)
  IDTEMPLATEEVENTO = ModelloEvento.IDTEMPLATEEVENTO
  IDCLASSE = ModelloEvento.IDCLASSE
   
  this.prepareAmbitoAndTipologia()
   
  // update Class description
  ClasseEvento Classe = ClasseEvento.get(IDCLASSE)
  DESCRCLASSE = Classe.DESCRCLASSE
   
  // update Ambito description
  AmbitoEvento Ambito = AmbitoEvento.GetAmbito(IDAMBITO)
  DESCRAMBITO = Ambito.DESCRAMBITO
   
  // update Tipologia description
  TipologiaEvento Tipologia = TipologiaEvento.GetTipologia(IDTIPOLOGIA)
  DESCRTIPOLOGIA = Tipologia.DESCRTIPOLOGIA
   
  // --- responsabile resolution ------------------------------------------------------------
  // When the caller pre-resolved the responsabile (UI path) we trust the value and skip the
  // auto-resolution. Otherwise we run the legacy logic, kept for callers that don't supply
  // a value (idUtenteResponsabile == 0).
  if (idUtenteResponsabile > 0)
  {
    IDUTENTERESP = idUtenteResponsabile
    if (ModelloEvento.ISFUNCTION == Yes)
      IdFunzioneResponsible = ModelloEvento.IDUTENTERESP
  }
  else 
  {
    DevTools.ToBeReviewed("following part is old code, it can be removed if we make sure that always IdUtenteResponsabile is passed")
     
    // OLD CODE- legacy auto-resolution  below ---
    // if responsible not defined in modello then we consider logged in user as responsible...
    if (isNull(ModelloEvento.IDUTENTERESP) or (ModelloEvento.IDUTENTERESP == 0))
      IDUTENTERESP = idUtente
    else 
      IDUTENTERESP = ModelloEvento.IDUTENTERESP
     
    if (ModelloEvento.ISFUNCTION == Yes)
    {
      IdFunzioneResponsible = ModelloEvento.IDUTENTERESP
       
      Funzione f = Funzione.getFromDB(IdFunzioneResponsible, quickLoad)
       
      // if funzione contains only one member we set it anyway
      int membersCount = f.getActiveMemmbersCount()
       
      if ((membersCount == 1))
      {
         if (!(f.MSQPERSFUNZIONI.loaded))
           f.loadCollectionFromDB(f.MSQPERSFUNZIONI, ...)
          
         int vIDUTENTEEmployee = 0
          
         for each FunzioneMembers fm in f.MSQPERSFUNZIONI
         {
           Personale personale = Personale.getFromDB(fm.IDDIPENDENTE, quickLoad)
           if (personale.IDUTENTE > 0)
           {
             Utente utente = Utente.get(personale.IDUTENTE)
             if (utente != null and utente.ATTIVO == Yes)
             {
                vIDUTENTEEmployee = personale.IDUTENTE
                break 
             }
           }
         }
          
         IDUTENTERESP = vIDUTENTEEmployee
      }
      else 
      {
         // we do not want to set an IDUTENTERESP in case of funzione responsible (to avoid strange lookup behavior), anyway we store the responsible in the IdFunzioneResponsible property
         IDUTENTERESP = null
      }
       
    }
  }
   
   
   
  // update user description of evento Responsible
  Utente utenteResp = Utente.get(IDUTENTERESP)
  if (utenteResp)
  {
    UTENTEDESCRRESP = utenteResp.DESCRUTENTE
  }
  IDDIPAVVIO = idDipendenteAvvio
   
  DESCREVENTO = ModelloEvento.DESCREVENTO
  DescrEventoHTML = ModelloEvento.DESCREVENTOHTML
  DESCREVENTOTXT = this.getDescrizioneTXT()
  DESCRTITOLO = ModelloEvento.DESCRTITOLO
  COSTOFISSO = ModelloEvento.COSTOFISSO
  INSTRUCTIONS = ModelloEvento.INSTRUCTIONS
  WORKFLOWTYPE = ModelloEvento.WORKFLOWTYPE
   
  // if a StartDate is set in the EventoCreationInput Object, we use it, otherwise "today" is used
   
  DevTools.ToBeReviewed("once prepreInstance will be private the else part will never be executed and therfore he if can be removed")
  if (startDate != null)
  {
    DATAEVENTO = startDate
  }
  else 
    DATAEVENTO = today()
   
  IDUTENTEINS = idUtente
   
   
  //  ID UTENTE INSERIMENTO --> ID PERSONALE AVVIO
  // -------------------------------------------------
  Personale p = Personale.GetPersonale(IDUTENTEINS)
  int idPersPropostoDa = p.IDDIPENDENTE
  IDDIPAVVIO = idPersPropostoDa
   
  // -------------------------------------------------
   
   
   
  DATACREAZIONE = now()
  PROFID = 0
  if (this.UpdateOnPreviewSelected())
  {
    PREVIEWONCALENDAR = Yes
  }
  else 
  {
    PREVIEWONCALENDAR = No
  }
   
  // We give a value to the Stato, it will be finally computed later at BeforeSave
  IDSTATOEVENTO = Immesso
}


// ──────────────────────────────────

// **********************************************************************************************************************************************
// Create Child collection  of evento i.e. CD,Reference,Dispozioni and it's checklist,NTF,Linked Document and other data like the doc spreadsheet
// **********************************************************************************************************************************************
private void Evento.CreateEventoDataFromModello(
  Utente creationUser // 
)
{
  // insert reference from modello to evento.reference collection
  this.CreateRiferimentiFromModello()
   
  if (WORKFLOWTYPE = Workflow)
  {
    // create first workflow disposizione
    this.CreateWKFDisposizione(creationUser)
  }
  else 
  {
    // Insert Disposizioni from modello to Evento.disposizioni collection
    this.CreateDisposizioni()
  }
  Disposizioni.loaded = true
   
  // insert costs from modello to evento.cost collection
  this.CreateCosts()
   
  // insert doc collegati from modello to evento.doc collegati collection
  this.createDocCollegatiFromModello()
   
  // insert Custom Data from modello to evento.Custom Data collection
  this.CreateCustomData()
   
  this.createDocSperadsheetFromModello()
}


// ──────────────────────────────────

// *************************************************
// Create references for eventi by referring modello
// *************************************************
private void Evento.CreateRiferimentiFromModello()
{
   
  if (ModelloEvento)
  {
    // if the collection Riferimenti of ModelloEvento has never been loaded
    if (ModelloEvento.Riferimenti.count() = 0)
    {
      this.loadCollectionFromDB(ModelloEvento.Riferimenti, ...)
    }
     
    // if the collection of Riferimenti is not empty this evento must have a clone of his riferimenti
    if (ModelloEvento.Riferimenti.count() != 0)
    {
      IDCollection riferimentiOfModelloEvento of Riferimento = ModelloEvento.Riferimenti
      for each Riferimento r in riferimentiOfModelloEvento
      {
         MainModule destination = r.GetDestination()
         if (!(destination))
         {
           continue 
         }
         ReferenceType refType = r.getReferenceType()
          
          
         Riferimento newRiferimento = new()
         newRiferimento = newRiferimento.CreateRiferimento(this, destination, refType)
          
          
         r.loadRDatiPersonalizzati()
         newRiferimento.loadRDatiPersonalizzati()
          
         if (r.RdatiPersonalizzati.count() != 0)
         {
           for each DatoPersonalizzato rp in r.RdatiPersonalizzati
           {
             string value = rp.getValueAsString()
             if (nullValue(value, "") != "")
             {
                RiferimentoDatoPersonalizzatoInfo rdi = cast(rp.getCustomDataInfo())
                newRiferimento.setRDatoPersonalizzato(rdi, value)
             }
           }
         }
          
         // per test
         if (newRiferimento)
         {
           Riferimenti.add(newRiferimento)
         }
      }
    }
  }
}


// ──────────────────────────────────

// *****************************************************************
// read disposizioni from modello and create disposizioni for eventi
// *****************************************************************
private void Evento.CreateDisposizioni()
{
  // get Dispisizioni collection in modello (please note those are sorted by NRO_RIGA)
  for each ModelloEventoDisposizione med in ModelloEvento.DisposizioniModello
  {
    Disposizione Disp = new()
    Disp = this.CreateDisposizioneFromModello(med)
    Disposizioni.add(Disp)
  }
}


// ──────────────────────────────────

// ****************************************************
// Create Disposizione from modello evento disposizione
// ****************************************************
private Disposizione Evento.CreateDisposizioneFromModello(
  ModelloEventoDisposizione ModelloDisposizione // 
)
{
  Disposizione Disp = new()
  Disp.init()
  Disp.IDEVENTO = IDEVENTO
  Disp.IDTIPOATTIVITA = ModelloDisposizione.IDTIPOATTIVITA
  Disp.NRORIGA = ModelloDisposizione.NRORIGA
  Disp.DESCRATTIVITA = ModelloDisposizione.DESCRATTIVITA
   
  // for Workflow we create only one disposizione so it is enough to set it to evento date, in the other cases (normale) we must consider GG_DA_INIZIO_EVENTO and DELAY DAYS respectively
  switch (ModelloEvento.WORKFLOWTYPE)
  {
    case Workflow:
      Disp.DATAPREVISTA = DATAEVENTO
    break
    case Normal:
      date dataPrevistaCalculated = dateAdd(Day, ModelloDisposizione.GGDAINSEVENTO, DATAEVENTO)
      Disp.DATAPREVISTA = dataPrevistaCalculated
    break
    default:
      QappCore.DTTLogMessage(formatMessage("createDisposizioniFromModello, unsupported behavior: |1", decode(ModelloEvento.WORKFLOWTYPE, EventiBehaviours), ...), ..., DTTError)
    break
  }
   
  if (isNull(ModelloDisposizione.IDRESPATTIVITA) or (ModelloDisposizione.IDRESPATTIVITA == 0))
  {
    Disp.IDRESPATTIVITA = EventoCreationInput.DefaultIDExecutorOfDisposizione
    Disp.ISFUNCTION = No
  }
  else 
  {
    Disp.IDRESPATTIVITA = ModelloDisposizione.IDRESPATTIVITA
    Disp.ISFUNCTION = ModelloDisposizione.ISFUNCTION
  }
  Disp.ORARIODANOTUSED = 0
  Disp.ORARIOANOTUSED = 0
  Disp.IDUTENTEINS = EventoCreationInput.IDUSER
  Disp.DATAINS = now()
  Disp.IDUTENTEULTMOD = EventoCreationInput.IDUSER
  Disp.DATAULTIMAMOD = now()
  Disp.ORAPREVISTA = ModelloDisposizione.ORARIOPREVISTA
  Disp.ORARIOFINE = ModelloDisposizione.ORARIOFINE
  Disp.ORE = (ModelloDisposizione.ORARIOFINE - ModelloDisposizione.ORARIOPREVISTA) * 24
  Disp.NOTIFICA = ModelloDisposizione.NOTIFICA
  Disp.NOTIFYRESPONSIBLE = ModelloDisposizione.NOTIFYRESPONSIBLE
  Disp.NOTIFYEXECUTOR = ModelloDisposizione.NOTIFYEXECUTOR
  Disp.NOTIFYOTHERS = ModelloDisposizione.NOTIFYOTHERS
  Disp.NOTIFYINADVANCE = ModelloDisposizione.NOTIFYINADVANCE
  Disp.NOTIFYADVANCEDAYS = ModelloDisposizione.NOTIFYADVANCEDAYS
  Disp.NOTIFYONEXECUTION = ModelloDisposizione.NOTIFYONEXECUTION
  Disp.NOTIFYONCLOSE = ModelloDisposizione.NOTIFYONCLOSE
  Disp.NOTIFYDELAYS = ModelloDisposizione.NOTIFYDELAYS
  Disp.NOTIFYDELAYSDAYS = ModelloDisposizione.NOTIFYDELAYSDAYS
  Disp.DELAYDAYS = ModelloDisposizione.DELAYDAYS
  Disp.NOTIFYINSUSER = ModelloDisposizione.NOTIFYINSUSER
  Disp.NOTIFYDELAYSCONTINUE = ModelloDisposizione.NOTIFYDELAYSCONTINUE
  Disp.NOTIFYDELAYSCONTINUEDAYS = ModelloDisposizione.NOTIFYDELAYSCONTINUEDAYS
  Disp.NOTIFYPERREFERENCE = ModelloDisposizione.NOTIFYPERREFERENCE
   
  // add checklist
  this.CreateChecklistFromModello(Disp, ModelloDisposizione)
  if (Disp.Checklist.ChecklistInstanceItemCollection.count() > 0)
  {
    Disp.STATOCKL = "A"
  }
  else 
  {
    Disp.STATOCKL = "N"
  }
   
  // add NTF
  this.CreateNTFFromModelloDisposizioni(Disp, ModelloDisposizione)
  return Disp
}


// ──────────────────────────────────

// **********************************************************************
// method to create checkist from Modello eventi disposizioni's checklist
// **********************************************************************
private void Evento.CreateChecklistFromModello(
  Disposizione Disp                             // Disposizioni where checklist to be added
  ModelloEventoDisposizione ModelloDisposizioni // We use Modello evento Disposizioni to get collection of modello checklist
)
{
  ModelloDisposizioni.ChecklistTemplate = Checklist.factory(ModelloEventoDisposizione, ModelloDisposizioni, ...)
  Checklist eventoDisposizioneChecklist = Checklist.factory(EventoDisposizione, Disp, ...)
   
  IDCollection checklistItems of ModelloEventoDisposizioneChecklistTemplateItem = ModelloDisposizioni.ChecklistTemplate.ChecklistInstanceItemCollection
   
  for each ModelloEventoDisposizioneChecklistTemplateItem medcti in checklistItems
  {
    EventoDisposizioneChecklistItem edci = new()
    edci.init()
    edci.IDATTIVITA = Disp.IDATTIVITA
    edci.NRORIGA = medcti.NRORIGA
    edci.RIFERIMENTI = medcti.RIFERIMENTI
    edci.DESCRDOMANDA = medcti.DESCRDOMANDA
    edci.NOTE = medcti.NOTE
    edci.IDEVENTO = Disp.IDEVENTO
    edci.MANDATORY = medcti.MANDATORY
    eventoDisposizioneChecklist.ChecklistInstanceItemCollection.add(edci)
  }
  Disp.Checklist = eventoDisposizioneChecklist
}


// ──────────────────────────────────

// ***********************************************************************************
// method to create all Notifications from Modello eventi disposizioni's notifications
// ***********************************************************************************
private void Evento.CreateNTFFromModelloDisposizioni(
  Disposizione Disp                             // Disposizioni where checklist to be added
  ModelloEventoDisposizione ModelloDisposizioni // We use Modello evento Disposizioni to get collection of modello NTF recipients
)
{
  Ntfrecipient ntf = null
   
  // create NTF object based on the modello NTF (Recipent type = others,perosnale and contatti)
  for each Ntfrecipient ntfmodello in ModelloDisposizioni.NTFRECIPIENTS
  {
    ntf = this.CreateSingleNTFfromModelloNTF(Disp, ntfmodello)
    Disp.NTFRECIPIENTS.add(ntf)
     
  }
   
  // create NTF object based on the modello NTF (Recipent type <> others)
   
  // create NTF for responsible
  if (Disp.NOTIFYRESPONSIBLE == Yes)
  {
    Personale p = Personale.GetPersonale(IDUTENTERESP)
    ntf = this.CreateSingleNTF(Disp, "R", p.IDDIPENDENTE, ...)
    Disp.NTFRECIPIENTS.add(ntf)
     
  }
   
  // create NTF for Inserted By
  if (Disp.NOTIFYINSUSER == Yes)
  {
    Personale p = Personale.GetPersonale(IDUTENTEINS)
    ntf = this.CreateSingleNTF(Disp, "I", p.IDDIPENDENTE, ...)
    Disp.NTFRECIPIENTS.add(ntf)
     
  }
   
  // create NTF for Executor
  if (Disp.NOTIFYEXECUTOR == Yes)
  {
    ntf = this.CreateSingleNTF(Disp, "E", Disp.IDRESPATTIVITA, ...)
    Disp.NTFRECIPIENTS.add(ntf)
     
  }
   
  // create NTF for Personale
  if (Disp.NOTIFYPERREFERENCE == Yes)
  {
     
    IDCollection collRifPersonale of Riferimento = new()
    collRifPersonale = this.findRiferimentiPersonaleFromModelloEvento()
    for each Riferimento r in collRifPersonale
    {
      Personale pers = cast(r.GetDestination())
      int idPersonaleRif = pers.IDDIPENDENTE
      ntf = this.CreateSingleNTF(Disp, "F", idPersonaleRif, ...)
      Disp.NTFRECIPIENTS.add(ntf)
    }
  }
   
}


// ──────────────────────────────────

// *****************************************************
// method to create Single Notification from Modello NTF
// *****************************************************
private Ntfrecipient Evento.CreateSingleNTFfromModelloNTF(
  Disposizione Disp               // Disposizioni where NTF to be added
  Ntfrecipient ModelloNTFRecpient // 
)
{
  // create NTF object based on the modello NTF 
  Ntfrecipient ntf = new()
  ntf.init()
  ntf.IDRECIPIENT = Sequence.getNextSequence(NTFN_ID_RECIPIENT, ...)
  ntf.KORDAPP = 14
  ntf.MAINID = IDEVENTO
  ntf.DETAILID = Disp.IDATTIVITA
  ntf.RECIPIENTTYPE = ModelloNTFRecpient.RECIPIENTTYPE
  ntf.RECPIENTVALUE = ModelloNTFRecpient.RECPIENTVALUE
  ntf.EMAIL = ModelloNTFRecpient.EMAIL
  ntf.NOTES = ModelloNTFRecpient.NOTES
  ntf.ISFUNCTION = ModelloNTFRecpient.ISFUNCTION
  this.CreateNTFStatus(Disp, ntf)
  return ntf
}


// ──────────────────────────────────

// ********************************************************
// method to create Single Notification based on parameters
// ********************************************************
private Ntfrecipient Evento.CreateSingleNTF(
  Disposizione Disp                // Disposizioni where NTF to be added
  string RecpType                  // 
  int RecpValue                    // 
  optional string IsFunction = "N" // 
)
{
  DevTools.RefactoringOpportunity("this method should be a public method of NTFRecipient class, the disposizione class that binds this method to evento shld be solved with an interface and this metthod should 
        have INotifiable in he first aprametre instead of Disposizione, same for CreateNTFStatus below")
   
  // create NTF object based on the modello NTF 
  Ntfrecipient ntfRecipient = new()
  ntfRecipient.init()
  ntfRecipient.IDRECIPIENT = Sequence.getNextSequence(NTFN_ID_RECIPIENT, ...)
  ntfRecipient.KORDAPP = 14
  ntfRecipient.MAINID = IDEVENTO
  ntfRecipient.DETAILID = Disp.IDATTIVITA
  ntfRecipient.RECIPIENTTYPE = RecpType
  ntfRecipient.RECPIENTVALUE = toString(RecpValue)
  ntfRecipient.ISFUNCTION = IsFunction
  this.CreateNTFStatus(Disp, ntfRecipient)
  return ntfRecipient
}


// ──────────────────────────────────

// ************************************************************
// Create NTF Status collection for Given Disposizioni and NTF 
// ************************************************************
private void Evento.CreateNTFStatus(
  Disposizione disposizione // Write a comment for this parameter or press backspace to delete this comment
  Ntfrecipient NTFRecipient // Write a comment for this parameter or press backspace to delete this comment
)
{
  Ntfstatus ntfStatus = null
  //  
  // In advance notification status
  if (disposizione.NOTIFYINADVANCE == Yes)
  {
    ntfStatus = this.CreateSingleNTFStatus(NTFRecipient.IDRECIPIENT, InAdvance)
    NTFRecipient.NTFSTATUS.add(ntfStatus)
  }
   
  // In on execution notification status
  if (disposizione.NOTIFYONEXECUTION == Yes)
  {
    ntfStatus = this.CreateSingleNTFStatus(NTFRecipient.IDRECIPIENT, OnExecutionDay)
    NTFRecipient.NTFSTATUS.add(ntfStatus)
  }
   
  // In on Closure notification status
  if (disposizione.NOTIFYONCLOSE == Yes)
  {
    ntfStatus = this.CreateSingleNTFStatus(NTFRecipient.IDRECIPIENT, Closure)
    NTFRecipient.NTFSTATUS.add(ntfStatus)
  }
   
  // In on Delay notification status
  if (disposizione.NOTIFYDELAYS == Yes)
  {
    ntfStatus = this.CreateSingleNTFStatus(NTFRecipient.IDRECIPIENT, Late)
    NTFRecipient.NTFSTATUS.add(ntfStatus)
  }
   
  // In on repeated delay notification status
  if (disposizione.NOTIFYDELAYSCONTINUE == Yes)
  {
    ntfStatus = this.CreateSingleNTFStatus(NTFRecipient.IDRECIPIENT, RepeatedLate)
    NTFRecipient.NTFSTATUS.add(ntfStatus)
  }
}


// ──────────────────────────────────

// ***************************************************
// read Costs from modello and create costs for eventi
// ***************************************************
private void Evento.CreateCosts()
{
  // get Costs collection in modello
  for each EventiCosto ModelloEventoCost in ModelloEvento.Costi
  {
    EventiCosto ec = new()
    ec.init()
    ec.IDEVENTO = IDEVENTO
    ec.IDTIPOCOSTO = ModelloEventoCost.IDTIPOCOSTO
    ec.ORE = ModelloEventoCost.ORE
    ec.PREZZO = ModelloEventoCost.PREZZO
    ec.NOTE = ModelloEventoCost.NOTE
    ec.IDUTENTEINS = EventoCreationInput.IDUSER
    ec.IDUTENTEULTMOD = EventoCreationInput.IDUSER
    ec.DATAINS = now()
    ec.DATAULTIMAMOD = now()
    EventiCosti.add(ec)
  }
}


// ──────────────────────────────────

// *********************************************************
// Create NTF Single Status object based on given parameters
// *********************************************************
private Ntfstatus Evento.CreateSingleNTFStatus(
  int IdRecipient        // 
  string NotifcationType // 
)
{
  Ntfstatus n = new()
  n.init()
  n.IDRECIPIENT = IdRecipient
  n.NOTIFICATIONTYPE = NotifcationType
  n.STATUS = 4
  return n
}


// ──────────────────────────────────

// **************************************************************
// Create Custom data of all types by reading modello customdata 
// **************************************************************
private void Evento.CreateCustomData()
{
  // if modello evento has been set, its dataPers must be cloned into dataPers of this evento
  if (ModelloEvento)
  {
    ModelloEvento.loadDatiPers()
    this.loadDatiPers()
    //  
    // if there are data Pers in the Modello Evento
    if (ModelloEvento.DatiPersonalizzati.count() != 0)
    {
      IDCollection datiPersModelloEvento of MainModuleDatoPersonalizzato = ModelloEvento.DatiPersonalizzati
       
      // foreach datoPersonalizzato of ModelloEvento this evento must have a clone
      for each MainModuleDatoPersonalizzato dp in datiPersModelloEvento
      {
         string value = dp.getValueAsString()
          
         if (nullValue(value, "") != "")
         {
           MainModuleDatoPersonalizzatoInfo cdataFieldInfo = cast(dp.getCustomDataInfo())
           this.setDatoPersonalizzato(value, cdataFieldInfo)
         }
      }
    }
     
     
     
     
  }
}


// ──────────────────────────────────

// *******************************************************************************
// Load modello evento object with help of input parameters(Evento Creation input)
// *******************************************************************************
private void Evento.initializeModelloEventoObject(
  int idModelloEvento // 
)
{
   
  ModelloEvento me = new()
  me.IDTEMPLATEEVENTO = idModelloEvento
  try 
  {
    me.loadFromDB(0)
    me.loadAllCollections()
  }
  catch 
  {
    QappCore.DTTLogMessage(formatMessage("Unable to load modello evento |1", idModelloEvento, ...), ..., DTTError)
  }
   
  // assign modelloevento object of evento with loaded one
  ModelloEvento = me
   
}


// ──────────────────────────────────

// **********************************************************************************************************
// Method reads Modello reference collection and findout if any reference is checked with Preview on calender
// **********************************************************************************************************
private boolean Evento.UpdateOnPreviewSelected()
{
  boolean Result = false
  for each ModelloEventoRiferimento mer in ModelloEvento.ModelloEventoRiferimenti
  {
    if (mer.PREVIEWONCALENDAR == Yes)
    {
      Result = true
      break 
    }
  }
  return Result
}


// ──────────────────────────────────

// *********************************************************************************
// create first disposizione of newly created evento based on the modello workflow  
// *********************************************************************************
private void Evento.CreateWKFDisposizione(
  Utente creationUser // 
)
{
  IDCollection modelloEventoFlow of WkfFlow = ModelloEvento.getWkfFlows()
  IDCollection modelloEventoSteps of WkfStep = ModelloEvento.getWkfSteps()
   
  for each WkfFlow wf in modelloEventoFlow
  {
    if (wf.ISSTART == Yes)
    {
      for each WkfStep ws in modelloEventoSteps
      {
         if (ws.IDSTEP == wf.IDNEXTSTEP)
         {
           Disposizione Disp = new()
           Disp = this.CreateWorkflowDisposizioneFromStep(creationUser, ws, wf, ...)
           Disposizioni.add(Disp)
           break 
         }
      }
      break 
    }
  }
}


// ──────────────────────────────────

// *********************************************************************************
// create first disposizione of newly created evento based on the modello workflow  
// *********************************************************************************
public Disposizione Evento.CreateWorkflowDisposizioneFromStep(
  Utente creationUser                // user that is closing the disposizione from the UI
  WkfStep ModelloStep                // next step
  WkfFlow Flow                       // flow to next step
  optional Utente manualStepExecutor // if next step has executor set to Manual, then the executor must be set
)
{
  Disposizione Disp = new()
  Disp.init()
  Disp.IDEVENTO = IDEVENTO
  Disp.IDTIPOATTIVITA = ModelloStep.IDTIPOATTIVITA
  Disp.DESCRATTIVITA = ModelloStep.STEPDESCRIPTION
  Disp.IDSTEP = ModelloStep.IDSTEP
  Disp.DATAPREVISTA = dateAdd(Day, Flow.DELAYDAYS, DATAEVENTO)
   
  switch (ModelloStep.EXECUTORTYPE)
  {
    case "M":
      if (isNull(ModelloStep.IDDIPENDENTE))
      {
         if (manualStepExecutor && manualStepExecutor.IDUTENTE > 0)
         {
           Disp.IDRESPATTIVITA = manualStepExecutor.IDUTENTE
         }
         else 
         {
           if (EventoCreationInput.DefaultIDExecutorOfDisposizione && EventoCreationInput.DefaultIDExecutorOfDisposizione > 0)
           {
             Disp.IDRESPATTIVITA = EventoCreationInput.DefaultIDExecutorOfDisposizione
           }
           else 
           {
             return null
           }
         }
//         throw 0, formatMessage("Responsible not defined in Step "|1"(|2).", ModelloStep.STEPDESCRIPTION, ModelloStep.IDSTEP, ...)
      }
      else 
      {
         Disp.IDRESPATTIVITA = ModelloStep.IDDIPENDENTE
      }
    break
    case "I":
      if (EventoCreationInput && EventoCreationInput.IdDipendenteAvvio > 0)
      {
         Disp.IDRESPATTIVITA = EventoCreationInput.IdDipendenteAvvio
      }
      else 
      {
         QappCore.DTTLogMessage("MISSING CODE TO MANAGE THE 'I' OPTION", ..., DTTWarning)
         return null
      }
    break
    case "R":
//      // OLD:
//      Disp.IDRESPATTIVITA = IDUTENTERESP
       
      int idDipendenteLinkedToResponsabile = 0
      select into variables (found variable)
         set idDipendenteLinkedToResponsabile = IDDIPENDENTE
      from 
         Personale // master table
      where
         IDUTENTE == IDUTENTERESP
      Disp.IDRESPATTIVITA = idDipendenteLinkedToResponsabile
    break
  }
  Disp.ISFUNCTION = ModelloStep.ISFUNCTION
   
  Disp.ORARIOANOTUSED = 0
  Disp.ORARIODANOTUSED = 0
   
   
  Disp.IDUTENTEINS = creationUser.IDUTENTE
  Disp.DATAINS = now()
  Disp.IDUTENTEULTMOD = creationUser.IDUTENTE
  Disp.DATAULTIMAMOD = now()
  Disp.ORAPREVISTA = ModelloStep.ORARIOINIZIO
  Disp.ORARIOFINE = ModelloStep.ORARIOFINE
  Disp.ORE = (ModelloStep.ORARIOFINE - ModelloStep.ORARIOINIZIO) * 24
  Disp.computeOraInizio()
  Disp.computeOraFine()
  Disp.updateOre()
  Disp.copyNotificationSettingsFromStep(ModelloStep)
   
  // add checklist
  this.CreateChecklistFromModelloStep(Disp, ModelloStep)
  if (Disp.Checklist.ChecklistInstanceItemCollection.count() > 0)
  {
    Disp.STATOCKL = "A"
  }
  else 
  {
    Disp.STATOCKL = "N"
  }
   
  // add NTF
  this.CreateNTFfromModelloStep(Disp, ModelloStep)
  return Disp
}


// ──────────────────────────────────

// **************************************************************
// method to create checkist from Modello eventi Step's checklist
// **************************************************************
private void Evento.CreateChecklistFromModelloStep(
  Disposizione Disp   // Disposizioni where checklist to be added
  WkfStep ModelloStep // We use Modello evento step to get collection of Step checklist
)
{
  ModelloStep.ChecklistTemplate = Checklist.factory(WorkflowStep, ModelloStep, ...)
   
  Checklist checklistEventoDisposizione = Checklist.factory(EventoDisposizione, Disp, ...)
   
  IDCollection checklistItems of WorkflowStepChecklstTemplateItem = ModelloStep.ChecklistTemplate.ChecklistInstanceItemCollection
   
  for each WorkflowStepChecklstTemplateItem wscti in checklistItems
  {
    EventoDisposizioneChecklistItem edci = new()
    edci.init()
    edci.IDATTIVITA = Disp.IDATTIVITA
    edci.NRORIGA = wscti.NRORIGA
    edci.RIFERIMENTI = wscti.RIFERIMENTI
    edci.NOTE = wscti.NOTE
    edci.MANDATORY = wscti.MANDATORY
    checklistEventoDisposizione.ChecklistInstanceItemCollection.add(edci)
  }
  Disp.Checklist = checklistEventoDisposizione
}


// ──────────────────────────────────

// ***************************************************************************
// method to create all Notifications from Modello eventi Step's notifications
// ***************************************************************************
private void Evento.CreateNTFfromModelloStep(
  Disposizione Disp   // Disposizioni where checklist to be added
  WkfStep ModelloStep // We use Modello evento Step to get collection of modello NTF recipients
)
{
  Ntfrecipient ntf = null
   
  // create NTF object based on the modello NTF (Recipent type = others)
  for each Ntfrecipient n in ModelloStep.NTFRECIPIENTS
  {
    ntf = this.CreateSingleNTFfromModelloNTF(Disp, n)
    Disp.NTFRECIPIENTS.add(ntf)
     
  }
   
  // create NTF object based on the modello NTF (Recipent type <> others)
   
  // create NTF for responsible
  if (Disp.NOTIFYRESPONSIBLE == Yes)
  {
    Personale p = Personale.GetPersonale(IDUTENTERESP)
    ntf = this.CreateSingleNTF(Disp, "R", p.IDDIPENDENTE, ...)
    Disp.NTFRECIPIENTS.add(ntf)
     
  }
  //  
  // create NTF for Inserted By
  if (Disp.NOTIFYINSUSER == Yes)
  {
    Personale p = Personale.GetPersonale(IDUTENTEINS)
    ntf = this.CreateSingleNTF(Disp, "I", p.IDDIPENDENTE, ...)
    Disp.NTFRECIPIENTS.add(ntf)
     
  }
   
  // create NTF for Executor
  if (Disp.NOTIFYEXECUTOR == Yes)
  {
    ntf = this.CreateSingleNTF(Disp, "E", Disp.IDRESPATTIVITA, Disp.ISFUNCTION)
    Disp.NTFRECIPIENTS.add(ntf)
     
  }
   
  // IMPORTANT : How to create NTF for Rif personale? because we dont have ID Dipendente of reference, moreover there can be multiple perosnale in reference
  // this must be handled from outside after calling Create Evento method.
   
}


// ──────────────────────────────────



// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void Evento.createDocSperadsheetFromModello()
{
  Utente u = new()
  u.IDUTENTE = EventoCreationInput.IDUSER
  u.loadFromDB(...)
   
  ModelloEvento.loadDocSpreadsheet()
   
  DocSpreadsheet ds = ModelloEvento.DocSpreadsheet
   
  if (ds)
  {
    DocFile df = ds.DocFile
     
    int:downloadFileErrorTypes downloadError = 0
    string xslxFile = df.GetPathOfDownloadableDOCFILE("xlsx", "modelloSpreadsheet", downloadError)
     
    base.updateDocSpreadsheetFromFile(xslxFile, u)
  }
}


// ──────────────────────────────────

// **********************************************************************************************************
// idAmbito and idTipologia of EventoCreationInput are set in the evento if modello has null values for those
// **********************************************************************************************************
private void Evento.prepareAmbitoAndTipologia()
{
  if (ModelloEvento.IDAMBITO == null)
  {
    if (EventoCreationInput.IdAmbito > 0)
      IDAMBITO = EventoCreationInput.IdAmbito
  }
  else 
    IDAMBITO = ModelloEvento.IDAMBITO
   
   
  if (ModelloEvento.IDTIPOLOGIA == null)
  {
    if (EventoCreationInput.IdTipologia > 0)
      IDTIPOLOGIA = EventoCreationInput.IdTipologia
  }
  else 
    IDTIPOLOGIA = ModelloEvento.IDTIPOLOGIA
   
}


// ──────────────────────────────────

// *************************************************************
// fallback to be sure every new evento has a titolo on creation
// *************************************************************
private void Evento.checkAndSetEventoTitoloOnCreation()
{
  if (DESCRTITOLO == "" or length(DESCRTITOLO) == 0)
  {
    DESCRTITOLO = ModelloEvento.DESCRTEMPLATE
  }
}


// ──────────────────────────────────

// *****************************************************
// create Qualibus messsage for Resonsabile and cc users
// *****************************************************
private void Evento.CreateMessages()
{
  this.CreateMessageForResponsible()
  this.CreateMessageForCC()
}


// ──────────────────────────────────

// ***********************************************************************************************************************
// create MSG_Messaggi object with it's child collections and later that can be saved if a new evento is Inserted(Created)
// ***********************************************************************************************************************
private void Evento.CreateMessageForResponsible()
{
  string vMESSAGGIO = ""
  select into variables (found variable)
    set vMESSAGGIO = MESSAGGIO
  from 
    EVATIPOLOGIEEVENTO // master table
  where
    IDTIPOLOGIA = IDTIPOLOGIA
   
  if (vMESSAGGIO == Yes)
  {
    MessaggiResponsible = new()
    MessaggiResponsible.init()
    MessaggiResponsible.OGGETTO = this.PopulateMsgSubject(Responsible)
    MessaggiResponsible.TESTO = this.PopulateMsgBody()
    MessaggiResponsible.IDUTENTE = EventoCreationInput.IDUSER
    this.AddMsgDestinationforResponsible(MessaggiResponsible.IDMESSAGGIO)
    DevTools.RefactoringOpportunity("we should use messaggio.addLink instead of using MSGLINK class  directly,anyway tryig to call add link here gives an eventoi nsertion error, it should be done with care")
     
    MSGLINK msglink = this.CreateMessageLink(MessaggiResponsible.IDMESSAGGIO)
    MessaggiResponsible.MSGLINK.add(msglink)
  }
}


// ──────────────────────────────────

// ***********************************************************************************************************************
// create MSG_Messaggi object with it's child collections and later that can be saved if a new evento is Inserted(Created)
// ***********************************************************************************************************************
private void Evento.CreateMessageForCC()
{
   
  MessaggiCC = new()
  MessaggiCC.init()
  MessaggiCC.OGGETTO = this.PopulateMsgSubject(CC)
  MessaggiCC.TESTO = this.PopulateMsgBody()
  MessaggiCC.IDUTENTE = EventoCreationInput.IDUSER
  MessaggiCC.DATAMESSAGGIO = now()
  this.AddMsgDestinationForCCRecipients(MessaggiCC.IDMESSAGGIO)
  DevTools.RefactoringOpportunity("we should use messaggio.addLink instead of using MSGLINK class  directly, it should anyway be done with care because calling MessaggiCC.AddLing(this) here won't work, there 
        is something more to be reviewed")
  MSGLINK msglink = this.CreateMessageLink(MessaggiCC.IDMESSAGGIO)
  MessaggiCC.MSGLINK.add(msglink)
}


// ──────────────────────────────────

// ************************************************************************************************
// create MSG_DESTANARI objects each for every recipient and add in to collection of MSG_MESSAGGI  
// ************************************************************************************************
private void Evento.AddMsgDestinationForCCRecipients(
  int IdMsg // 
)
{
  for each row (readonly)
  {
    select
      msgIDUTENTE = Utenti.IDUTENTE
      EMAILNOTIFICATIONSUtente = Utenti.EMAILNOTIFICATIONS
    from 
      Utenti                  // master table
      EventoClasseRoles       // manually joined, see where clauses
      EventoAmbitoRoles       // manually joined, see where clauses
      EventiAmbitiClasseLinks // manually joined, see where clauses
    where
      Utenti.IDUTENTE = EventoClasseRoles.IDUTENTE
      Utenti.IDUTENTE = EventoAmbitoRoles.IDUTENTE
      Utenti.IDUTENTE != IDUTENTERESP
      EventoClasseRoles.IDCLASSE = EventiAmbitiClasseLinks.IDCLASSE
      EventoAmbitoRoles.IDAMBITO = EventiAmbitiClasseLinks.IDAMBITO
      Utenti.ATTIVO = Yes
      EventoClasseRoles.MESSAGGIO = Yes
      EventoClasseRoles.IDCLASSE = IDCLASSE
      EventoAmbitoRoles.IDAMBITO = IDAMBITO
     
     
    MSGDESTINATARI msgdestinatari = new()
    msgdestinatari.init()
    msgdestinatari.IDMESSAGGIO = IdMsg
    msgdestinatari.IDUTENTE = msgIDUTENTE
     
    // if EMAILNOTIFICATIONS is N we must set 0, else 1 (with 1 mailsender will send)
    msgdestinatari.EMAILSTATUS = if(EMAILNOTIFICATIONSUtente == No, 0, 1)
     
    DevTools.RefactoringOpportunity("EMAILNOTIFICATIONS shuld be a value list Y N W, now it is Y N in db, W is for With attachment, Y is without")
    DevTools.RefactoringOpportunity("EMAILSTATUS should also havea value list. 0 means do not send, 1 send as mail, other values to be checked in qualibus code")
     
    MessaggiCC.MSGDESTINATARI.add(msgdestinatari)
  }
   
   
}


// ──────────────────────────────────

// ***************************************************************
// Create MSG_LINK object and add in to collection of MSG_MESSAGGI
// ***************************************************************
private MSGLINK Evento.CreateMessageLink(
  int IdMSg // 
)
{
  MSGLINK msglink = new()
  msglink.init()
  msglink.IDMESSAGIO = IdMSg
  msglink.KORDAPP = Eventi
  msglink.IDITEM = IDEVENTO
  return msglink
}


// ──────────────────────────────────

// ************************************************************************************************
// create MSG_DESTANARI objects each for every recipient and add in to collection of MSG_MESSAGGI  
// ************************************************************************************************
private void Evento.AddMsgDestinationforResponsible(
  int IdMsg // 
)
{
  MSGDESTINATARI msgdestinatari = new()
  msgdestinatari.init()
  msgdestinatari.IDMESSAGGIO = IdMsg
  msgdestinatari.IDUTENTE = IDUTENTERESP
   
  MessaggiResponsible.MSGDESTINATARI.add(msgdestinatari)
   
}


// ──────────────────────────────────

// ******************************************
// get comma separated list of all references
// ******************************************
private string Evento.GetReferenceText()
{
  string RefText = ""
  string CRLF = "\n"
   
  Riferimento refObj = new()
   
  int IndexIdTipoRef = toPropertyIndex(refObj.IDRiferimento)
   
  Riferimenti.addSortCriteria(IndexIdTipoRef)
  Riferimenti.doSort()
  int OldIdRefType = 0
   
  for each Riferimento r in Riferimenti
  {
     
    if (RefText == "")
    {
      RefText = r.getNamedPropertyValue("REF_NAME") + ": " + r.getNamedPropertyValue("DESCRIPTION")
    }
    else 
    {
      if (OldIdRefType != r.IDTipoRiferimento)
      {
         RefText = RefText + CRLF + r.getNamedPropertyValue("REF_NAME") + ": " + r.getNamedPropertyValue("DESCRIPTION")
      }
      else 
      {
         RefText = RefText + ", " + r.getNamedPropertyValue("DESCRIPTION")
      }
    }
    OldIdRefType = r.IDTipoRiferimento
  }
  Riferimenti.resetSortCriteria()
  if (Riferimenti.count() > 0)
  {
    RefText = "Riferimenti: " + CRLF + RefText
  }
   
  return RefText
}


// ──────────────────────────────────

// *********************************
// populate message subject and Body
// *********************************
private string Evento.PopulateMsgBody()
{
  string BodyText = ""
  string CRLF = "\n"
   
  Utente userInsertion = Utente.get(IDUTENTEINS)
  string descrUtenteInsertion = userInsertion.DESCRUTENTE
   
  // done to handle the case in which in initializeDefaultVAlues IDUTENTERESP is still null
  if (UTENTEDESCRRESP == "")
  {
    Utente userReponsible = Utente.get(IDUTENTERESP)
    if (userReponsible)
    {
      UTENTEDESCRRESP = userReponsible.DESCRUTENTE
    }
  }
   
  BodyText = formatMessage("Il |1 è stato inserito dall'utente |3 il seguente evento:|2", today(), CRLF, descrUtenteInsertion, ...)
  BodyText = BodyText + formatMessage("Evento n.ro |1 del |2 |3", IDEVENTO, DATAEVENTO, CRLF, ...)
  BodyText = BodyText + formatMessage("Rif.: |1 |2", NROEVENTO, CRLF, ...)
  BodyText = BodyText + formatMessage("Titolo: |1 |2", DESCRTITOLO, CRLF, ...)
  BodyText = BodyText + formatMessage("Responsabile: |1 |2", UTENTEDESCRRESP, CRLF, ...)
  BodyText = BodyText + formatMessage("Classe/Ambito: |1/|2|3", DESCRCLASSE, DESCRAMBITO, CRLF, ...)
  BodyText = BodyText + formatMessage("Tipologia: |1|2", DESCRTIPOLOGIA, CRLF, ...)
  BodyText = BodyText + formatMessage("Descrizione: |1|2", DESCREVENTOTXT, CRLF, ...)
  BodyText = BodyText + this.GetReferenceText()
   
  return BodyText
}


// ──────────────────────────────────

// *********************************
// populate message subject and Body
// *********************************
private string Evento.PopulateMsgSubject(
  string:QBMessageType QBMsgType // 
)
{
  string SubjectText = DESCRCLASSE
  if (!(isNull(NROEVENTO)))
  {
    if (NROEVENTO != "")
    {
      SubjectText = DESCRCLASSE + " - " + NROEVENTO
    }
  }
  string Msg = ""
  if (QBMsgType == CC)
  {
    Msg = "Notifica inserimento evento (|1): per conoscenza"
  }
  else if (QBMsgType == Deletion)
  {
    Msg = "Notifica eliminazione evento (|1)"
  }
  else if (QBMsgType == Responsible)
  {
    Msg = "Notifica inserimento evento (|1): Responsabile gestione"
  }
  SubjectText = formatMessage(Msg, SubjectText, ...)
   
  return SubjectText
}


// ──────────────────────────────────

// ***************************************
// Descrivi lo scopo di questa funzione...
// ***************************************
public int Evento.getMainID()
{
  return IDEVENTO
}


// ──────────────────────────────────

// ***************************************
// Descrivi lo scopo di questa funzione...
// ***************************************
public int Evento.getTypeID()
{
  return IDCLASSE
}


// ──────────────────────────────────



// ──────────────────────────────────

// ****************************************
// Describe the purpose of this function...
// ****************************************
public string Evento.getDescription()
{
  string description = SH.Concat(NROEVENTO, DESCRTITOLO, " ")
  return description
}


// ──────────────────────────────────

// ***********************************
// return a collectiono of Map Element
// ***********************************
public IDCollection Evento.GetModuleSpecificTextReplacements()
{
  IDCollection coll of MapElement = new()
   
  coll.add(MapElement.create("ID_EVENTO", toString(IDEVENTO)))
  coll.add(MapElement.create("NRO_EVENTO", toString(NROEVENTO)))
  coll.add(MapElement.create("ID_CLASSE", toString(IDCLASSE)))
  coll.add(MapElement.create("ID_AMBITO", toString(IDAMBITO)))
  coll.add(MapElement.create("ID_TIPOLOGIA", toString(IDTIPOLOGIA)))
  coll.add(MapElement.create("ID_STATO_EVENTO", toString(IDSTATOEVENTO)))
  coll.add(MapElement.create("ID_DIP_AVVIO", toString(IDDIPAVVIO)))
  coll.add(MapElement.create("ID_UTENTE_INS", toString(IDUTENTEINS)))
  coll.add(MapElement.create("ID_UTENTE_RESP", toString(IDUTENTERESP)))
  coll.add(MapElement.create("ID_UTENTE_CHIUS", toString(IDUTENTECHIUS)))
  coll.add(MapElement.create("DATA_CHIUSURA", toString(DATACHIUSURA)))
  coll.add(MapElement.create("DATA_EVENTO", toString(DATAEVENTO)))
  coll.add(MapElement.create("DESCR_EVENTO", toString(DESCREVENTO)))
  coll.add(MapElement.create("COSTO_FISSO", toString(COSTOFISSO)))
  coll.add(MapElement.create("COSTO_TOTALE", toString(COSTOTOTALE)))
  coll.add(MapElement.create("ID_TEMPLATE_EVENTO", toString(IDTEMPLATEEVENTO)))
  coll.add(MapElement.create("ID_GRAVITA", toString(IDGRAVITA)))
  coll.add(MapElement.create("DATA_CREAZIONE", toString(DATACREAZIONE)))
  coll.add(MapElement.create("PROF_ID", toString(PROFID)))
  coll.add(MapElement.create("DESCR_TITOLO", toString(DESCRTITOLO)))
  coll.add(MapElement.create("INSTRUCTIONS", toString(INSTRUCTIONS)))
  coll.add(MapElement.create("PREVIEW_ON_CALENDAR", toString(PREVIEWONCALENDAR)))
  coll.add(MapElement.create("WORKFLOW_TYPE", toString(WORKFLOWTYPE)))
  coll.add(MapElement.create("DESCR_EVENTO_TXT", toString(DESCREVENTOTXT)))
  coll.add(MapElement.create("Descr_Evento_HTML", toString(DescrEventoHTML)))
   
  return coll
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public IDCollection Evento.retrieveAvailableCDSections()
{
  int IDTipoAnagrafica = this.getTypeID()
  int:kordapp kordApp = this.getKordApp()
   
  IDCollection retrievedAvailableCDSectionsInEventi of CdataSection = new()
  select into collection distinct (retrievedAvailableCDSectionsInEventi)
    set IDCDATASEC = CDATASECTIONS.IDCDATASEC
    set Name = CDATASECTIONS.Name
    set Description = CDATASECTIONS.Description
    set Sequence = CDATASECTIONS.Sequence
    set Active = CDATASECTIONS.Active
    set KordApp = CDATASECTIONS.KordApp
    set ISCOMMON = CDATASECTIONS.ISCOMMON
    set DEPRECATEDIDTEMPLATE = CDATASECTIONS.DEPRECATEDIDTEMPLATE
    set DEPRECATEDTEMPLATE = CDATASECTIONS.DEPRECATEDTEMPLATE
    set DEPRECATEDIDPARENTTEMPLATEREMOTE = CDATASECTIONS.DEPRECATEDIDPARENTTEMPLATEREMOTE
    set HIDDEN = CDATASECTIONS.HIDDEN
  from 
    CDATASECTIONS   // master table
    CDATAMODULETYPE // joined with CDATA SECTIONS using key FK_CDATA_MODULE_TYPE_ID_CDATA_SEC
  where
    CDATASECTIONS.Active == Yes
    CDATASECTIONS.KordApp = kordApp
    (CDATASECTIONS.ISCOMMON == Yes) or ((CDATAMODULETYPE.IDTIPO == IDTipoAnagrafica) and (CDATASECTIONS.ISCOMMON == No))
    CDATASECTIONS.IDCDATASEC in subquery
      select top 1 // 
         CDATASECTIONS.IDCDATASEC
      from 
         ModelloEventoSezioniDatiPersonalizzati1 // master table
      where
         IDTEMPLATEEVENTO = ModelloEventoSezioniDatiPersonalizzati1.IDTEMPLATEEVENTO
         ModelloEventoSezioniDatiPersonalizzati1.VISIBLE == Yes
         CDATASECTIONS.IDCDATASEC == ModelloEventoSezioniDatiPersonalizzati1.IDCDATASEC
  order by
    CDATASECTIONS.Sequence
    CDATASECTIONS.Description
   
  return retrievedAvailableCDSectionsInEventi
}


// ──────────────────────────────────

// *************************************************************************************************************************************************
// to be extended in each subcass: it must return an array of property indexes names for the properties we do not want to include in the json export
// 
// NOTE: it is done with property index so we are forced to use the property itself, making it searchable better
// *************************************************************************************************************************************************
public IDArray Evento.getPropertyIndexesOfPropertiesExcludedFromJsonExport()
{
   
  IDArray exludedPropertyIndexes = new()
  exludedPropertyIndexes.addValue(toPropertyIndex(Evento.DESCREVENTO))
  exludedPropertyIndexes.addValue(toPropertyIndex(Evento.INSTRUCTIONS))
   
  return exludedPropertyIndexes
}


// ──────────────────────────────────

// *******************************************************************************************************************
// it returns the property index of the property linked to the main image field of the class (e.g. ART_ANAGRAFICA.FOTO
// *******************************************************************************************************************
public int Evento.PROTECTEDgetMainImagePropertyIndex()
{
  return null
}


// ──────────────────────────────────

// ****************************************************************************************************************************
// it returns the property index of the property linked to the second image field of the class (e.g. ART_ANAGRAFICA.FOTO_SECOND
// ****************************************************************************************************************************
public int Evento.PROTECTEDgetSecondImagePropertyIndex()
{
  return null
}


// ──────────────────────────────────

// *********************
// Method to be extended
// *********************
public int Evento.setMainHtmlFieldPropertyIndex()
{
  return toPropertyIndex(DescrEventoHTML)
}


// ──────────────────────────────────

// *********************
// Method to be extended
// *********************
public int Evento.setMainTxtFieldPropertyIndex()
{
  return toPropertyIndex(DESCREVENTOTXT)
}


// ──────────────────────────────────



// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event Disposizione.OnInit()
{
  IDATTIVITA = Sequence.getNextSequence(EVAN_ID_ATTIVITA, ...)
  ORAPREVISTA = Tools.TimeToFloat(toTime(9, 0, 0))
  ORARIOFINE = Tools.TimeToFloat(toTime(10, 0, 0))
  ORE = 1
  OraInizio = toTime(9, 0, 0)
  OraFine = toTime(10, 0, 0)
  NRORIGA = 1
  DATAINS = now()
  DATAULTIMAMOD = now()
  IDUTENTEINS = QappCore.Loggeduser.IDUTENTE
  IDUTENTEULTMOD = QappCore.Loggeduser.IDUTENTE
  STATOCKL = "N"
   
  this.AfterLoadMultiSourceResponsibleCreation()
}


// ──────────────────────────────────

// ***********************************************************************
// Event raised to the document to determine the value of a named property
// ***********************************************************************
event Disposizione.OnGetNamedPropertyValue(
  string PropertyName        // The name of the named property whose value to retrieve.
  inout string PropertyValue // An output parameter containing the value of the named property.
)
{
  if (PropertyName = "STARTDATE")
  {
    PropertyValue = convert(getDate(DATAPREVISTA))
  }
   
  if (PropertyName = "STARTTIME")
  {
    int minutesfrommidnight = 0
    minutesfrommidnight = Tools.FloatTimeToMinutes(ORAPREVISTA)
    PropertyValue = convert(toTime(0, minutesfrommidnight, 0))
  }
   
  if (PropertyName = "ENDTIME")
  {
    int minutesfrommidnight = 0
    minutesfrommidnight = Tools.FloatTimeToMinutes(ORARIOFINE)
    PropertyValue = convert(toTime(0, minutesfrommidnight, 0))
  }
   
  if (PropertyName == "STATUS")
  {
    if (isNull(DATAEFFETTIVA))
    {
      PropertyValue = Open
    }
    else 
    {
      PropertyValue = Closed
    }
  }
   
  if (PropertyName == "DURATION")
  {
    PropertyValue = convert(Tools.FloatTimeToMinutes(ORARIOFINE - ORAPREVISTA))
  }
   
  if (PropertyName == "DESCRIPTION")
  {
    string vNROEVENTOEvento = ""
    select into variables (found variable)
      set vNROEVENTOEvento = NROEVENTO
    from 
      Eventi // master table
    where
      IDEVENTO == this.IDEVENTO
    //  
    // Just to try
    PropertyValue = vNROEVENTOEvento + " - " + DESCRATTIVITA
     
  }
   
  if (PropertyName == "COLOR")
  {
    // 
    if (getTag(PropertyName) != "")
    {
      PropertyValue = convert(getTag(PropertyName))
      return 
      //  
      // in questo caso non serve rifare le query per il colore per questa specifica disposizione
       
    }
    int ColoreClasse = 0
    string:OPZIONICOLOREEVACLASSIEVENTO OpzioneColore = ""
    //  
    // FINIRE IL JOIN....
    select into variables (found variable)
      set ColoreClasse = EVACLASSIEVENTO.COLORE
      set OpzioneColore = EVACLASSIEVENTO.OPZIONICOLORE
    from 
      EVACLASSIEVENTO // master table
      Eventi          // joined with EVA CLASSI EVENTO using key FK_EVA_TESTATA_EVENTO01
    where
      Eventi.IDEVENTO = IDEVENTO
//     
//     
    if (OpzioneColore = Classe)
    {
      PropertyValue = convert(Tools.DxcolorToInde(ColoreClasse))
    }
    else if (OpzioneColore = Ambito)
    {
      int ColoreAmbito = 0
      select into variables (found variable)
         set ColoreAmbito = COLORE
      from 
         EVAAMBITIEVENTO // master table
      PropertyValue = convert(Tools.DxcolorToInde(ColoreAmbito))
    }
     
     
    this.setTag(PropertyName, PropertyValue)
  }
   
  if (PropertyName == "ICON")
  {
    if (getNamedPropertyValue("STATUS") == Closed)
    {
      PropertyValue = convert(DisposizioneChiusa)
    }
    else if (getNamedPropertyValue("STATUS") == Open)
    {
      PropertyValue = convert(DisposizioneAperta)
    }
  }
  if (PropertyName == "POPUPACTIONS")
  {
    string Commands = "Apri evento;OPEN;minfo.gif"
    if (getNamedPropertyValue("STATUS") == Open)
    {
      Commands = Tools.Concatenate(Commands, "Chiudi disposizione;CLOSE;myes.gif", "|")
    }
    PropertyValue = Commands
  }
}


// ──────────────────────────────────

// *************************************************************
// Event raised to the document at the end of the save procedure
// *************************************************************
event throws exception Disposizione.AfterSave(
  inout boolean Cancel // Boolean output parameter that makes it possible to cancel the entire save. It is equivalent to the Cancel parameter of the BeforeSave event.
)
{
  if (FlowItemThatClosesEvento)
  {
    if (UtenteWhoClosedLastDisposizione)
    {
      Evento e = this.getParentEvento(...)
      e.close(UtenteWhoClosedLastDisposizione, FlowItemThatClosesEvento)
      FlowItemThatClosesEvento = null
      UtenteWhoClosedLastDisposizione = null
      e.saveToDB(...)
       
    }
     
  }
}


// ──────────────────────────────────

// ************************************************************
// Event raised to the document during the validation procedure
// ************************************************************
event Disposizione.OnValidate(
  int:validateReasons Reason // An integer specifying the reason the document is being validated. The most common values are 0 to indicate complete validation before saving, or 1 to indicate a quick validati...
  inout boolean Error        // A boolean output parameter. If set to True, specifies an error condition and stops the validation immediately.
  inout boolean Skip         // A boolean output parameter. If set to True, does not check the rules for automatic validation of the document (see notes).
)
{
  // if disposzione is deleted no need to validate
  if (deleted)
    return 
   
  if ((Reason == Complete) or (Reason == validateDisposizioni))
  {
    // validate non closed disposizione for valid startTime,EndTime,Date and Executor
    // we need to validate only open disposizioni (so this should work both for normal and workflow)
     
    if (isNull(DATAEFFETTIVA) or (toString(DATAEFFETTIVA) == ""))
    {
      boolean validDisposizioniStartTime = !(isNull(ORAPREVISTA)) or (toString(ORAPREVISTA) != "")
      boolean validDisposizioniEndTime = !(isNull(ORARIOFINE)) or (toString(ORARIOFINE) != "")
      boolean validDisposizioniDate = !(isNull(DATAPREVISTA)) or (toString(DATAPREVISTA) != "")
      boolean validDisposizioniExecutor = !(isNull(IDRESPATTIVITA)) or (toString(IDRESPATTIVITA) != "")
      boolean validDuration = ORE > 0
      boolean startTimeIsLowerThanEndTime = false
      if (validDisposizioniStartTime and validDisposizioniEndTime)
      {
         startTimeIsLowerThanEndTime = (ORAPREVISTA < ORARIOFINE)
      }
       
      if (!(validDisposizioniExecutor))
      {
         this.setPropertyError("L'esecutore della disposizione non è definito.", IDRESPATTIVITA)
         Error = true
      }
      if (!(validDisposizioniStartTime and validDisposizioniEndTime and validDisposizioniDate and validDisposizioniExecutor and startTimeIsLowerThanEndTime and validDuration))
      {
         Error = true
      }
    }
  }
   
  if (EventoType == Normal)
  {
    if (isNull(ESITO) and !(isNull(DATAEFFETTIVA)))
    {
      this.setPropertyError("Specificare l'esito della disposizione.", ESITO)
      Error = true
    }
     
    if ((OraInizio == #00:00:00# && OraFine == #00:00:00#))
    {
      this.setPropertyError("Impostare l'ora di inizio.", OraInizio)
      this.setPropertyError("Impostare l'ora di fine.", OraFine)
      Error = true
    }
     
    if (OraInizio > OraFine)
    {
      this.setPropertyError("L'ora di fine deve essere maggiore dell'ora di inizio.", OraFine)
      Error = true
    }
  }
   
  this.refreshUserInterface()
}


// ──────────────────────────────────

// ************************************************************
// Event raised by the document after loading from the database
// ************************************************************
event Disposizione.AfterLoad(
  boolean AlreadyLoaded     // A boolean parameter that specifies whether the document was previously loaded.
  IDArray LoadedCollections // IDArray type parameter that contains the collections loaded by the framework. If the value of the Already Loaded parameter is False, the Loaded Collections parameter is NULL.
)
{
  if (IDEVENTO == null or IDEVENTO <= 0)
  {
    return 
  }
  this.loadEventoType()
  this.computeInfoProperties()
  this.computeOrarioFineBasingOnDurata()
  this.computeOraInizio()
  this.computeOraFine()
  this.computeEsitoForUi()
   
  if (!(Checklist))
  {
    Checklist = Checklist.factory(EventoDisposizione, this, ...)
  }
   
  this.AfterLoadMultiSourceResponsibleCreation()
  this.setOriginal()
}


// ──────────────────────────────────

// ***********************************************************
// Event raised to the document when the transaction is closed
// ***********************************************************
event Disposizione.OnEndTransaction()
{
  if (wasModified(EsecutoreIDForDecodingInUI))
  {
    MultiSourceResponsible.MultiSourceResponsibleInterface.updateResponsiblePropertiesEndTransactionHandler()
  }
   
  // in case type of esecutore is modified we clear the field that hosts the ID, to avoid to store a wrong value
  if (wasModified(ISFUNCTION))
  {
    IDRESPATTIVITA = null
  }
  if ((wasModified(OraFine) or wasModified(OraInizio)) and !(wasModified(ORE)))
  {
    this.updateOraPrevistaEOrarioFine()
  }
   
  if (wasModified(ORE))
  {
    this.computeOrarioFineBasingOnDurata()
    this.computeOraFine()
  }
  if (wasModified(OraFine) or wasModified(OraInizio))
  {
    this.computeDurataBasingOnOrarioFineAndInizio()
  }
   
//  if (wasModified(IDUTENTEINS) or wasModified(DATAINS) or wasModified(IDUTENTEULTMOD) or wasModified(DATAULTIMAMOD))
//  {
//    this.computeInfoProperties()
//  }
}


// ──────────────────────────────────

// **************************************************************************************
// Event raised to the document during the initial steps of the document saving procedure
// **************************************************************************************
event throws exception Disposizione.BeforeSave(
  inout boolean Skip         // A boolean output parameter. If set to True, tells the framework not to perform operations on the database, because they will be managed independently.
  inout boolean Cancel       // A boolean output parameter. If set to True, tells the framework to cancel the save procedure due to errors that occurred. Since the entire document is saved in the same transa...
  int:beforeSavePhases Phase // An integer from 0 to 3 that specifies the progress status of the save procedure as detailed at the beginning of the article.
)
{
  if (Phase == PreSave)
  {
    ORAPREVISTA = Tools.TimeToFloat(OraInizio)
    ORARIOFINE = Tools.TimeToFloat(OraFine)
    DATAULTIMAMOD = now()
    IDUTENTEULTMOD = QappCore.Loggeduser.IDUTENTE
    this.computeInfoProperties()
    this.computeEsitoForUi()
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static Disposizione Disposizione.create(
  Evento evento // 
)
{
  if (evento == null || evento.IDEVENTO <= 0)
  {
    QappCore.DTTLogMessage("evento missing", ..., DTTError)
    return null
  }
   
  Disposizione d = new()
  d.init()
  d.IDEVENTO = evento.IDEVENTO
  d.DATAPREVISTA = today()
   
  d.EventoType = evento.WORKFLOWTYPE
   
  d.computeInfoProperties()
   
  return d
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public static IDCollection Disposizione.getUserDisposizioni(
  Utente Utente     // 
  date StartDate    // 
  date time EndDate // 
)
{
  IDCollection UserDisposizioni of Disposizione = new()
  select into collection (UserDisposizioni)
    set IDATTIVITA = Disposizioni.IDATTIVITA
    set IDEVENTO = Disposizioni.IDEVENTO
    set IDTIPOATTIVITA = Disposizioni.IDTIPOATTIVITA
    set NRORIGA = Disposizioni.NRORIGA
    set DESCRATTIVITA = Disposizioni.DESCRATTIVITA
    set DATAPREVISTA = Disposizioni.DATAPREVISTA
    set DATAEFFETTIVA = Disposizioni.DATAEFFETTIVA
    set ESITO = Disposizioni.ESITO
    set IDRESPATTIVITA = Disposizioni.IDRESPATTIVITA
    set IDUTENTERESPEVENTO = Disposizioni.IDUTENTERESPEVENTO
    set NOTECHIUSURA = Disposizioni.NOTECHIUSURA
    set IDUTENTECHIUSURA = Disposizioni.IDUTENTECHIUSURA
    set ORARIODANOTUSED = Disposizioni.ORARIODA
    set ORARIOANOTUSED = Disposizioni.ORARIOA
    set IDUTENTEINS = Disposizioni.IDUTENTEINS
    set DATAINS = Disposizioni.DATAINS
    set IDUTENTEULTMOD = Disposizioni.IDUTENTEULTMOD
    set DATAULTIMAMOD = Disposizioni.DATAULTIMAMOD
    set ORAPREVISTA = Disposizioni.ORAPREVISTA
    set ORARIOFINE = Disposizioni.ORARIOFINE
    set ORE = Disposizioni.ORE
    set NOTIFICA = Disposizioni.NOTIFICA
    set STATOCKL = Disposizioni.STATOCKL
    set ISFUNCTION = Disposizioni.ISFUNCTION
    set NOTIFYRESPONSIBLE = Disposizioni.NOTIFYRESPONSIBLE
    set NOTIFYEXECUTOR = Disposizioni.NOTIFYEXECUTOR
    set NOTIFYOTHERS = Disposizioni.NOTIFYOTHERS
    set NOTIFYINADVANCE = Disposizioni.NOTIFYINADVANCE
    set NOTIFYADVANCEDAYS = Disposizioni.NOTIFYADVANCEDAYS
    set NOTIFYONEXECUTION = Disposizioni.NOTIFYONEXECUTION
    set NOTIFYONCLOSE = Disposizioni.NOTIFYONCLOSE
    set NOTIFYDELAYS = Disposizioni.NOTIFYDELAYS
    set NOTIFYDELAYSDAYS = Disposizioni.NOTIFYDELAYSDAYS
    set IDSTEP = Disposizioni.IDSTEP
    set DELAYDAYS = Disposizioni.DELAYDAYS
    set BINDINGATTIVO = Disposizioni.BINDINGATTIVO
    set BINDINGDATE = Disposizioni.BINDINGDATE
    set NOTIFYINSUSER = Disposizioni.NOTIFYINSUSER
    set NOTIFYDELAYSCONTINUE = Disposizioni.NOTIFYDELAYSCONTINUE
    set NOTIFYDELAYSCONTINUEDAYS = Disposizioni.NOTIFYDELAYSCONTINUEDAYS
    set IDRESULT = Disposizioni.IDRESULT
    set NOTIFYPERREFERENCE = Disposizioni.NOTIFYPERREFERENCE
    set NOTIFYCLIFORREFERENCE = Disposizioni.NOTIFYCLIFORREFERENCE
  from 
    Disposizioni // master table
    Personale    // manually joined, see where clauses
  where
    Disposizioni.DATAPREVISTA >= StartDate && Disposizioni.DATAPREVISTA <= EndDate
    Personale.IDUTENTE = Utente.IDUTENTE
    Personale.IDDIPENDENTE = Disposizioni.IDRESPATTIVITA
   
  return UserDisposizioni
}


// ──────────────────────────────────

// ****************************************************
// This method will Update following fields
// 1. Execution Start date with time (DATA_PREVISTA)
// 2. Execution Start Time (float value - ORA_PREVISTA)
// 3. Execution End time   (float value - ORARIO_FINE)
// 4. Duration (Flot value - ORE)
// ****************************************************
public void Disposizione.SetExecutionDateTime(
  date Date                          // 
  time Time                          // 
  optional int DurationInMinutes = 0 // Duration of time in minutes
)
{
  // if DUrationin minutes is zero keep original duration
  DATAPREVISTA = Date
  ORAPREVISTA = Tools.TimeToFloat(Time)
  if (DurationInMinutes = 0)
  {
    ORARIOFINE = Tools.TimeToFloat(dateAdd(Minute, (ORE * 60), Time))
  }
  else 
  {
    ORARIOFINE = Tools.TimeToFloat(dateAdd(Minute, DurationInMinutes, Time))
    ORE = (ORARIOFINE - ORAPREVISTA) * 24,0
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private Evento Disposizione.getParentEvento(
  optional boolean loadLightVersion = 0 // 
)
{
  Evento parentEvento = null
   
  // recuperare il comportmento dell'evento
  if (parent != null)
  {
    if (Evento.isMyInstance(parent))
      parentEvento = parent
  }
   
  if (!(parentEvento))
  {
    parentEvento = Evento.getFromDB(IDEVENTO, quickLoad)
    if (!(loadLightVersion))
    {
      parentEvento.loadDatiPers()
      parentEvento.loadCollectionFromDB(parentEvento.Riferimenti, ...)
    }
  }
   
  return parentEvento
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean Disposizione.hasChecklistAtLeastOneRowWithoutResult()
{
  TipoDisposizione td = getLinkedDocument(false, toPropertyIndex(IDTIPOATTIVITA), ...)
   
  if (td.CHKLISTCOMPMANDATORY == Yes)
  {
    boolean atLeastOneRowWithoutResult = false
     
    IDCollection coll of EventoDisposizioneChecklistItem = cast(Checklist.ChecklistInstanceItemCollection)
     
    for each EventoDisposizioneChecklistItem edci in coll
    {
      if (isNull(edci.IDEVARESULTSTYPE))
      {
         atLeastOneRowWithoutResult = true
         break 
      }
    }
    return atLeastOneRowWithoutResult
  }
  else 
  {
    return false
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public Disposizione Disposizione.createNext(
  WkfResult result                   // 
  Utente executor                    // 
  optional Utente manualStepExecutor // 
)
{
  if (executor == null || executor.IDUTENTE <= 0)
    return null
   
  Evento e = this.getParentEvento(...)
   
  WkfFlow chosenFlow = new()
  chosenFlow.IDSTEP = IDSTEP
  chosenFlow.IDRESULT = result.IDRESULT
  chosenFlow.IDTEMPLATEEVENTO = e.IDTEMPLATEEVENTO
   
  try 
  {
    chosenFlow.loadFromDB(0)
     
    WkfStep nextStep = new()
    nextStep.IDSTEP = chosenFlow.IDNEXTSTEP
    nextStep.IDTEMPLATEEVENTO = e.IDTEMPLATEEVENTO
     
    nextStep.loadFromDB(0)
    if (nextStep.BLOCKTYPE == Step)
    {
      Disposizione dispo = e.CreateWorkflowDisposizioneFromStep(executor, nextStep, chosenFlow, manualStepExecutor)
      dispo.NRORIGA = NRORIGA + 1
      dispo.IDUTENTEINS = executor.IDUTENTE
      dispo.IDUTENTEULTMOD = executor.IDUTENTE
      return dispo
    }
  }
   
  // if there is no next disposizione, return null
  return null
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static Disposizione Disposizione.get(
  int idAttivita // 
)
{
  Disposizione d = new()
  d.IDATTIVITA = idAttivita
  try 
  {
    d.loadFromDB(...)
  }
  catch 
  {
    QappCore.DTTLogMessage(formatMessage("Impossible to retrieve disposizione with ID: |1", idAttivita, ...), ...)
  }
   
  return d
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Disposizione.computeOraInizio()
{
  OraInizio = Tools.FloatToTime(ORAPREVISTA)
//  if (ORAPREVISTA != null and ORAPREVISTA != 0)
//  {
//    int minFrom0Am = DTH.FloatTimeToMinutes(ORAPREVISTA)
//    if (minFrom0Am > 0)
//    {
//      int hours = floor(minFrom0Am / 60)
//      int minutes = minFrom0Am % 60
//      OraInizio = toTime(hours, minutes, 0)
//    }
//    else 
//    {
//      OraInizio = toTime(0, 0, 0)
//    }
//  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Disposizione.computeOraFine()
{
  OraFine = Tools.FloatToTime(ORARIOFINE)
//  if (ORARIOFINE != null and ORARIOFINE != 0)
//  {
//    int minFrom0Am = DTH.FloatTimeToMinutes(ORARIOFINE)
//    if (minFrom0Am > 0)
//    {
//      int hours = floor(minFrom0Am / 60)
//      int minutes = minFrom0Am % 60
//      OraFine = toTime(hours, minutes, 0)
//    }
//    else 
//    {
//      OraFine = toTime(0, 0, 0)
//    }
//  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Disposizione.computeOrarioFineBasingOnDurata()
{
  float oreToBeSum = (toFloat(ORE) / 24)
  ORARIOFINE = ORAPREVISTA + oreToBeSum
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Disposizione.computeDurataBasingOnOrarioFineAndInizio()
{
   
  if (!(isNull(OraFine)) and !(isNull(OraInizio)))
  {
    int hourInizio = hour(OraInizio)
    int hourfine = hour(OraFine)
    int hDifference = abs(hourfine - hourInizio)
     
    int minInizio = minute(OraInizio)
    int minFine = minute(OraFine)
    int mDifference = abs(minFine - minInizio)
     
    ORE = toFloat(toFloat(hDifference) + toFloat(mDifference) / 60)
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Disposizione.updateOraPrevistaEOrarioFine()
{
  int hoursInizio = hour(OraInizio)
  float minInizio = minute(OraInizio)
  ORAPREVISTA = (hoursInizio * 60 + minInizio) / 1440
  int hoursFine = hour(OraFine)
  float minFine = minute(OraFine)
  ORARIOFINE = (hoursFine * 60 + minFine) / 1440
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Disposizione.updateOre()
{
  int hoursInizio = hour(OraInizio)
  float minInizio = minute(OraInizio)
  float hoursTotInizio = hoursInizio + minInizio / 60
  int hoursFine = hour(OraFine)
  float minFine = minute(OraFine)
  float hoursTotFine = hoursFine + minFine / 60
  ORE = hoursTotFine - hoursTotInizio
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static boolean Disposizione.thereAreDisposizioniLinkedToUser(
  Evento evento // 
  Utente user   // 
)
{
  Personale p = user.getLinkedPersonale()
  boolean personaleIsLinkedWithDisposizioni = false
  if (p)
  {
    for each Disposizione d in evento.Disposizioni
    {
      if (d.ISFUNCTION == Yes)
      {
         Funzione f = Funzione.getFromDB(d.IDRESPATTIVITA, ...)
         boolean personaleBelongsToFunction = f.containsPersonale(p)
         personaleIsLinkedWithDisposizioni = personaleBelongsToFunction
      }
      else 
      {
         personaleIsLinkedWithDisposizioni = d.IDRESPATTIVITA == p.IDDIPENDENTE
      }
       
      if (personaleIsLinkedWithDisposizioni)
      {
         break 
      }
    }
  }
   
  return personaleIsLinkedWithDisposizioni
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean Disposizione.executorDropDownShouldBeLocked()
{
  boolean firstStepExecutorTypeIsInsertedByOfResponsible = false
   
  Evento e = null
  if (parent)
  {
    e = cast(parent)
     
  }
  else 
  {
    QappCore.DTTLogMessage("parent not found, impossible to cast", ..., DTTError)
  }
   
   
  if (e)
  {
    DevTools.ToBeReviewed("disposizione class uses a lot modello evento and steps here, not so nice but effective...")
    ModelloEvento me = e.getModelloEvento()
    if (me.WORKFLOWTYPE == Workflow)
    {
      WkfStep firstModelloStep = me.getFirstStep()
      if (IDSTEP == firstModelloStep.IDSTEP)
      {
         firstStepExecutorTypeIsInsertedByOfResponsible = firstModelloStep.EXECUTORTYPE == InseritoDa or firstModelloStep.EXECUTORTYPE == Responsabile
      }
    }
  }
   
  boolean dropDownShouldBeLocked = !(this.isClosed()) and firstStepExecutorTypeIsInsertedByOfResponsible
   
   
  return dropDownShouldBeLocked
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Disposizione.computeEsitoForUi()
{
  string computedEsito = ""
   
  if (this.isClosed())
  {
    if (EventoType == Normal)
    {
      computedEsito = decode(ESITO, ESITODisposizioneNormale)
    }
    else if (EventoType == Workflow)
    {
       
      // a multi with ID_RESULT null is a "non last" multi disposizione
      if (this.isMultipleDisposizione() and IDRESULT == null)
      {
         computedEsito = "Attuata - Multiplo"
      }
      else if (IDRESULT > 0)
      {
         WkfResult wr = WkfResult.get(IDRESULT)
         computedEsito = wr.RESULTDESCRIPTION
      }
      else 
      {
         computedEsito = ""
      }
    }
  }
  else 
  {
    QappCore.DTTLogMessage("not computing esitoForUi for a non closed disposizione", ..., DTTInfo)
  }
   
   
  EsitoForUI = computedEsito
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean Disposizione.closeDisposizione(
  Utente utenteChiusura                                                                 // 
  optional string:executionModes executionMode = "normalMode"                           // 
  optional int chosenResultId = 0                                                       // workflow specific
  optional IDCollection multiStepSelectedDisposizioniCollection of WkfDisposizioneDraft // 
)
{
  if (EventoType == "")
  {
    this.loadEventoType()
  }
   
  boolean result = false
   
  if (this.validate(...))
  {
    if (EventoType == Normal)
    {
      result = this.closeNormaleDisposizione(utenteChiusura, executionMode)
    }
    else 
    {
      result = this.CloseWorkflowDisposizione(utenteChiusura, chosenResultId, executionMode, multiStepSelectedDisposizioniCollection)
    }
  }
   
  return result
}


// ──────────────────────────────────

// *********************************************************************************************************************************************************************
// closes the disposizione returning true in case of success, in case of error a errorMessage tag is attached to the document
// the call to this method without passing the chosenResult makes sense only if there is one only possible result, otherwise passing the optional parameter is mandatory
// *********************************************************************************************************************************************************************
private boolean Disposizione.closeNormaleDisposizione(
  Utente utenteChiusura                                       // 
  optional string:executionModes executionMode = "normalMode" // 
)
{
  if (EventoType != Normal)
  {
    QappCore.DTTLogMessage("trying to close a disposizione in a non Lineare evento with 'closeLineareDisposizione'", ..., DTTError)
    return false
  }
//   
  try 
  {
    // needed to close the Evento in the AfterSave event
    UtenteWhoClosedLastDisposizione = utenteChiusura
     
    if (isNull(ESITO))
    {
      return false
    }
     
    this.SetNormaleDisposizioneAsClosed(NOTECHIUSURA, utenteChiusura, ESITO, OraInizio, OraFine)
     
    if (executionMode == normalMode)
      this.saveToDB(...)
     
    return true
  }
  catch 
  {
    QappCore.DTTLogMessage("Errore durante il salvataggio della disposizione.", ..., DTTError)
    return false
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void Disposizione.SetNormaleDisposizioneAsClosed(
  string notes          // 
  Utente utenteChiusura // 
  string:ESITODisposizioneNormale esitoChiusura // 
  time orarioStart      // 
  time orarioEnd        // 
)
{
  NOTECHIUSURA = notes
  DATAEFFETTIVA = now()
  ORAPREVISTA = Tools.TimeToFloat(orarioStart)
  ORARIOFINE = Tools.TimeToFloat(orarioEnd)
  ESITO = esitoChiusura
  IDUTENTECHIUSURA = utenteChiusura.IDUTENTE
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void Disposizione.setWorkflowDisposizioneAsClosed(
  string notes          // 
  Utente utenteChiusura // 
  WkfResult wkfResult   // 
)
{
  NOTECHIUSURA = notes
  DATAEFFETTIVA = now()
  ESITO = Yes
  IDUTENTECHIUSURA = utenteChiusura.IDUTENTE
  IDRESULT = wkfResult.IDRESULT
}


// ──────────────────────────────────

// *********************************************************************************************************************************************************************
// closes the disposizione returning true in case of success, in case of error a errorMessage tag is attached to the document
// the call to this method without passing the chosenResult makes sense only if there is one only possible result, otherwise passing the optional parameter is mandatory
// *********************************************************************************************************************************************************************
private boolean Disposizione.CloseWorkflowDisposizione(
  Utente utenteChiusura                                                 // 
  optional int chosenResult = 0                                         // 
  optional string:executionModes executionMode = "normalMode"           // 
  optional IDCollection nextDraftedDisposizioni of WkfDisposizioneDraft // 
)
{
  if (EventoType != Workflow)
  {
    QappCore.DTTLogMessage("trying to close a disposizione in a non workflow evento with 'closeWorkflwoDisposizione'", ..., DTTError)
    return false
  }
   
  WkfFlow flowToNextStep = new()
  WkfResult choosenWkfResult = new()
   
  if (chosenResult > 0)
  {
    choosenWkfResult.IDRESULT = chosenResult
    choosenWkfResult.loadFromDB(0)
     
    flowToNextStep.IDSTEP = IDSTEP
    flowToNextStep.IDRESULT = chosenResult
    flowToNextStep.loadFromDB(...)
  }
  else 
  {
    if (!(NextFlows.loaded))
    {
      this.loadCollectionFromDB(NextFlows, ...)
    }
     
    if (NextFlows.count() == 1)
    {
      NextFlows.moveFirst()
      flowToNextStep = (WkfFlow)NextFlows.getAt()
    }
  }
   
  if (flowToNextStep.ISSTOP == Yes)
  {
    // if next step is stop you can close with no esito selected
    if (chosenResult == null || chosenResult <= 0)
    {
      choosenWkfResult.IDRESULT = 0
    }
     
    // needed to close the Evento in the AfterSave event
    FlowItemThatClosesEvento = flowToNextStep
    UtenteWhoClosedLastDisposizione = utenteChiusura
     
    this.setWorkflowDisposizioneAsClosed(NOTECHIUSURA, utenteChiusura, choosenWkfResult)
    if (executionMode == normalMode)
      this.saveToDB(...)
     
    return true
  }
  else 
  {
     
    this.setWorkflowDisposizioneAsClosed(NOTECHIUSURA, utenteChiusura, choosenWkfResult)
     
    Evento e = this.getParentEvento(...)
    int counter = 1
    int disposizioniCount = e.Disposizioni.count()
    for each WkfDisposizioneDraft nd in nextDraftedDisposizioni
    {
      Disposizione newDisp = e.createFromWKFDisposizioneDraft(nd, this)
      newDisp.NRORIGA = disposizioniCount + counter
      if (executionMode == normalMode)
      {
         newDisp.saveToDB(...)
      }
      counter = counter + 1
    }
    if (executionMode == normalMode)
    {
      this.saveToDB(...)
    }
    return true
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public DisposizioneClosureContext Disposizione.createClosureContext(
  int idSelectedResult // 
  Evento evento        // 
)
{
   
  DisposizioneClosureContext dcc = new()
   
  // load flow
  WkfFlow wf = new()
  wf.IDRESULT = idSelectedResult
  wf.loadFromDB(...)
  dcc.FlowToNextStep = wf
   
  if (wf.ISSTOP == Yes)
  {
    if (wf.CLOSURERESULTTYPE == Richiedi)
    {
      dcc.WokflowNextStepKind = NextStepIsStopWithoutEsito
    }
    else if (wf.CLOSURERESULTTYPE == ImpostaSpecifico and !(isNull(wf.IDGRAVITA)))
    {
      dcc.WokflowNextStepKind = NextStepIsStopWithEsito
    }
  }
  else 
  {
    WkfStep nextStep = new()
    nextStep.IDSTEP = wf.IDNEXTSTEP
    nextStep.loadFromDB(...)
    dcc.NextStep = nextStep
     
    if (nextStep.isMultiple())
    {
      dcc.WokflowNextStepKind = NextStepWithMultipleDisposizioni
      dcc.WkfDisposizioneDraft = dcc.createCollectionForMultiple(evento, nextStep, wf, DATAPREVISTA)
    }
    else 
    {
      dcc.WokflowNextStepKind = NextStepWithSIngleDisposizione
      WkfDisposizioneDraft nd = dcc.createSingleDisposizione(evento, nextStep, wf, DATAPREVISTA)
      dcc.WkfDisposizioneDraft.add(nd)
    }
  }
   
  return dcc
}


// ──────────────────────────────────



// ──────────────────────────────────

// ***************************************************************************
// Closes a multiple-step disposizione (ESITO = "M").
// Must be called for all disposizioni in the step except the last;
// the last one is closed via CloseWorkflowDisposizione with a real WkfResult.
// ***************************************************************************
public boolean Disposizione.closeMultipleDisposizione(
  Utente utenteChiusura                                       // 
  optional string:executionModes executionMode = "normalMode" // 
)
{
  if (EventoType != Workflow)
  {
    QappCore.DTTLogMessage("closeMultipleDisposizione called on a non-Workflow disposizione", ..., DTTError)
    return false
  }
//   
  try 
  {
    this.SetMultipleDisposizioneAsClosed(NOTECHIUSURA, utenteChiusura)
     
    if (executionMode == normalMode)
      this.saveToDB(...)
     
    return true
  }
  catch 
  {
    QappCore.DTTLogMessage("Error while saving disposizione", ..., DTTError)
    return false
  }
}


// ──────────────────────────────────

// ************************************************************************************************************************************
// Returns true if this is the last open disposizione among those sharing the same step in the same evento.
// Used by the form to decide which close form to show
// NOTE: this method hides the complexity of the inOut parameter in allMultipleDisposizioniClosed making it more straightforward to use
// ************************************************************************************************************************************
public boolean Disposizione.isLastOpenMultipleDisposizione(
  Evento evento // 
)
{
  boolean isOnlyOneLeft = false
  this.allMultipleDisposizioniClosed(evento, isOnlyOneLeft)
  return isOnlyOneLeft
}


// ──────────────────────────────────

// ******************************************
// returns true if the disposizione is clsoed
// ******************************************
public boolean Disposizione.isClosed()
{
  boolean isOpen = isNull(DATAEFFETTIVA)
  return !(isOpen)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string Disposizione.getEsitoCssClassForClosedDisposizione()
{
  if (ESITO == null || ESITO == "")
  {
  }
   
  if (ESITO == Yes)
  {
    return "addPositiveIcon"
  }
   
  if (ESITO == No)
  {
    return "addNegativeIcon"
  }
   
  return ""
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Disposizione.loadEventoType()
{
  Evento e = this.getParentEvento(true)
  EventoType = e.WORKFLOWTYPE
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Disposizione.reOpen()
{
  NOTECHIUSURA = ""
  DATAEFFETTIVA = null
  ESITO = null
  IDUTENTECHIUSURA = null
  IDRESULT = null
  EsitoForUI = null
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string Disposizione.GetChecklistTextIconForIcon()
{
  this.updateStatoCKL()
   
  string icon = "{{icon-fa-list}}"
   
//  switch (STATOCKL)
//  {
//    case "N":
//      icon = "{{icon-fa-list}}"
//    break
//    case "C":
//      icon = "{{icon-fa-list}}"
//    break
//    case "2":
//      icon = "{{icon-fa-list}}"
//    break
//    case "A":
//      icon = "{{icon-fa-list}}"
//    break
//  }
   
  return icon
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string Disposizione.GetChecklistCssClassForIcon()
{
  this.updateStatoCKL()
   
  string cssClass = "chkl-iconDefault"
   
  QappCore.DTTLogMessage(formatMessage("STATOCKL: |1", STATOCKL, ...), ..., DTTWarning)
   
  switch (STATOCKL)
  {
    case "A":
      cssClass = "chkl-iconOpen"
    break
    case "C":
      cssClass = "chkl-iconClosed"
    break
    case "1":
      cssClass = "chkl-iconHalfOpen"
    break
  }
   
  return cssClass
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Disposizione.updateStatoCKL()
{
  if (Checklist == null or Checklist.ChecklistInstanceItemCollection == null or Checklist.ChecklistInstanceItemCollection.count() == 0)
  {
    STATOCKL = "N"
  }
  else 
  {
    switch (Checklist.getChecklistCompletionStatus())
    {
      case NotExist:
         STATOCKL = "N"
      break
      case Open:
         STATOCKL = "A"
      break
      case Half:
         STATOCKL = "1"
      break
      case Complete:
         STATOCKL = "C"
      break
    }
  }
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public Checklist Disposizione.getChecklist()
{
  if (Checklist == null)
  {
    Checklist = Checklist.factory(EventoDisposizione, this, ...)
  }
   
  return Checklist
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Disposizione.copyNotificationSettingsFromStep(
  WkfStep step // 
)
{
  NOTIFICA = step.NOTIFICA
  NOTIFYRESPONSIBLE = step.NOTIFYRESPONSIBLE
  NOTIFYEXECUTOR = step.NOTIFYEXECUTOR
  NOTIFYOTHERS = step.NOTIFYOTHERS
  NOTIFYINSUSER = step.NOTIFYINSUSER
  NOTIFYPERREFERENCE = step.NOTIFYPERREFERENCE
  NOTIFYONEXECUTION = step.NOTIFYONEXECUTION
  NOTIFYONCLOSE = step.NOTIFYONCLOSE
  NOTIFYINADVANCE = step.NOTIFYINADVANCE
  NOTIFYADVANCEDAYS = step.NOTIFYADVANCEDAYS
  NOTIFYDELAYS = step.NOTIFYDELAYS
  NOTIFYDELAYSDAYS = step.NOTIFYDELAYSDAYS
  NOTIFYDELAYSCONTINUE = step.NOTIFYDELAYSCONTINUE
  NOTIFYDELAYSCONTINUEDAYS = step.NOTIFYDELAYSCONTINUEDAYS
  NOTIFYCLIFORREFERENCE = step.NOTIFYCLIFORREFERENCE
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean Disposizione.allMultipleDisposizioniClosed(
  Evento parentEvento                               // 
  inout boolean isOnlyOneMultipleDisposizioneOpened // 
)
{
  isOnlyOneMultipleDisposizioneOpened = false
   
  if (parentEvento == null)
    return true
   
  if (!(parentEvento.Disposizioni.loaded))
    parentEvento.loadCollectionFromDB(parentEvento.Disposizioni, ...)
   
   
  int totalMultipleDisposizioniCount = 0
  int closedDisposizioniCount = 0
  for each Disposizione d in parentEvento.Disposizioni
  {
    if (d.deleted)
      continue 
     
    if (d.IDSTEP != IDSTEP)
      continue 
     
    totalMultipleDisposizioniCount = totalMultipleDisposizioniCount + 1
    if (d.DATAEFFETTIVA != null)
      closedDisposizioniCount = closedDisposizioniCount + 1
     
  }
  isOnlyOneMultipleDisposizioneOpened = (totalMultipleDisposizioniCount - closedDisposizioniCount) == 1
   
  return totalMultipleDisposizioniCount == closedDisposizioniCount
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean Disposizione.isMultipleDisposizione()
{
  boolean isMultiple = false
  if (IDSTEP > 0)
  {
    string:flagYN visMultiple = ""
    select into variables (found variable)
      set visMultiple = MULTIPLE
    from 
      WKFSTEPS // master table
    where
      IDSTEP == IDSTEP
     
    isMultiple = visMultiple == Yes
  }
   
  return isMultiple
}


// ──────────────────────────────────



// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public EventoPublicInfo Disposizione.updateEventoPublicInfo(
  WkfPublicInfoDraft draftedPublicInfo                        // 
  optional string:executionModes executionMode = "normalMode" // 
)
{
  EventoPublicInfo epi = new()
  epi.IDEventoPublicInfo = draftedPublicInfo.IDEventoPublicInfo
  epi.IDEVENTO = draftedPublicInfo.IDEVENTO
  epi.loadFromDB(...)
   
  epi.IdEventoPublicStato = draftedPublicInfo.IdPublicStatusInFlow
  epi.Notes = draftedPublicInfo.Notes
   
  if (executionMode == normalMode)
  {
    epi.saveToDB(...)
  }
  return epi
}


// ──────────────────────────────────

// ******************************************************************************
// given IdUtente and DAte time a string is returned with "DESCR_UTENTE dateTime"
// ******************************************************************************
private string Disposizione.computeSpecificInfo(
  int idUtente       // 
  date time dateTime // 
)
{
  string result = ""
  Utente u = Utente.get(idUtente)
  string descrUtente = ""
  if (u)
    descrUtente = u.DESCRUTENTE
   
  result = formatMessage("|1 |2", descrUtente, dateTime, ...)
  return result
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Disposizione.computeInfoProperties()
{
  InsertionInfo = this.computeSpecificInfo(IDUTENTEINS, DATAINS)
  UpdationInfo = this.computeSpecificInfo(IDUTENTEULTMOD, DATAULTIMAMOD)
}


// ──────────────────────────────────

// *************************************************************************************************************
// can be removed only if is the last disposizione of the multiple step and if there is more than 1 disposizione
// *************************************************************************************************************
public boolean Disposizione.canBeRemoved(
  Evento evento // 
)
{
  if (evento.isLocked())
  {
    return false
  }
   
  if (evento.isNormaleType())
  {
    // not managing for normale type
    return true
  }
   
  if (evento.isWorkflowType())
  {
    boolean onlyOneOfDisposizioneLeftNonClosedFromMultiples = false
    if (this.isMultipleDisposizione())
    {
      boolean allMultipleDispClosed = this.allMultipleDisposizioniClosed(evento, onlyOneOfDisposizioneLeftNonClosedFromMultiples)
      if (onlyOneOfDisposizioneLeftNonClosedFromMultiples)
      {
         return false
      }
      else 
      {
         return true
      }
    }
    return false
     
//    WkfStep ws = evento.getCurrentWorkflowStep()
//     
//    if (ws == null)
//    {
//      QappCore.DTTLogMessage("wkfStep is null, this should not happen", ..., DTTError)
//      return false
//    }
//     
//    // if the step is not multiple, return true
//    if (!(ws.isMultiple()))
//      return true
//     
//    // multiple step: get all disposizioni of the multiple step
//    IDCollection coll of Disposizione = new()
//    for each Disposizione d in evento.Disposizioni
//    {
//      if (d.IDSTEP == this.IDSTEP)
//         coll.addRef(d)
//    }
//     
//    // only 1 disposizione -> can't delete
//    if (coll.count() <= 1)
//      return false
//     
//    // get last row number
//    int maxRow = 0
//    for each Disposizione d in coll
//    {
//      if (d.NRORIGA > maxRow)
//         maxRow = d.NRORIGA
//    }
//     
//    // if the current disposizione is the last disposizione can be deleted
//    if (this.NRORIGA == maxRow)
//      return true
//     
//    return false
  }
   
  QappCore.DTTLogMessage("Unexpected", ..., DTTError)
  return false
}


// ──────────────────────────────────

// ***********************************************************************************************
// Duplicates this disposizione under the given parent evento.
//   The caller is responsible for computing the DATA_PREVISTA value appropriate for its scenario:
//  - Panel "Duplica" command: today() 
//  - Evento duplication: caller computes per-disposizione offset from source evento date
//  
//  The duplicate is appended at the end of the parent evento's Disposizioni collection
//  (NRORIGA = current max + 1).
//  
//  Sets STATO_CKL based on whether the duplicated checklist has items.
// ***********************************************************************************************
public Disposizione Disposizione.duplicateDisposizione(
  Utente currentUser                       // 
  Evento parentEvento                      // 
  date dataPrevistaForduplicate            // 
  optional boolean shouldDuplicateNote = 0 // 
)
{
  if (currentUser == null)
    currentUser = QappCore.Loggeduser
   
  int row = parentEvento.GetNextNroRigaForDisposizione()
   
  if (row == 0)
  {
    row = 1
  }
   
  Disposizione duplicatedDisposizione = this.create(parentEvento)
   
  this.duplicateMainData(duplicatedDisposizione, row, dataPrevistaForduplicate)
  this.duplicateChecklistItems(duplicatedDisposizione, shouldDuplicateNote)
   
  // update STATO_CKL
  if (duplicatedDisposizione.Checklist != null and duplicatedDisposizione.Checklist.ChecklistInstanceItemCollection.count() > 0)
    duplicatedDisposizione.STATOCKL = "A"
  else 
    duplicatedDisposizione.STATOCKL = "N"
   
  return duplicatedDisposizione
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void Disposizione.duplicateMainData(
  Disposizione targetDisposzione // 
  int nroRiga                    // 
  date dataPrevistaForDuplicate  // 
)
{
  targetDisposzione.IDSTEP = IDSTEP
  targetDisposzione.DESCRATTIVITA = DESCRATTIVITA
  targetDisposzione.IDTIPOATTIVITA = IDTIPOATTIVITA
   
  // notifications
  targetDisposzione.NOTIFICA = NOTIFICA
  targetDisposzione.NOTIFYRESPONSIBLE = NOTIFYRESPONSIBLE
  targetDisposzione.NOTIFYEXECUTOR = NOTIFYEXECUTOR
  targetDisposzione.NOTIFYOTHERS = NOTIFYOTHERS
  targetDisposzione.NOTIFYINADVANCE = NOTIFYINADVANCE
  targetDisposzione.NOTIFYADVANCEDAYS = NOTIFYADVANCEDAYS
  targetDisposzione.NOTIFYONEXECUTION = NOTIFYONEXECUTION
  targetDisposzione.NOTIFYONCLOSE = NOTIFYONCLOSE
  targetDisposzione.NOTIFYDELAYS = NOTIFYDELAYS
  targetDisposzione.NOTIFYDELAYSDAYS = NOTIFYDELAYSDAYS
   
  targetDisposzione.DELAYDAYS = DELAYDAYS
  targetDisposzione.NOTIFYINSUSER = NOTIFYINSUSER
  targetDisposzione.NOTIFYDELAYSCONTINUE = NOTIFYDELAYSCONTINUE
  targetDisposzione.NOTIFYDELAYSCONTINUEDAYS = NOTIFYDELAYSCONTINUEDAYS
  targetDisposzione.NOTIFYCLIFORREFERENCE = NOTIFYCLIFORREFERENCE
   
  // timings
  targetDisposzione.OraInizio = OraInizio
  targetDisposzione.OraFine = OraFine
  targetDisposzione.ORAPREVISTA = ORAPREVISTA
  targetDisposzione.ORARIOFINE = ORARIOFINE
  targetDisposzione.ORE = ORE
  //  
  // executor
  targetDisposzione.EsecutoreIDForDecodingInUI = EsecutoreIDForDecodingInUI
  targetDisposzione.IDRESPATTIVITA = IDRESPATTIVITA
  targetDisposzione.ISFUNCTION = ISFUNCTION
   
  // Positioning (append at end of parent evento's Disposizioni)
  targetDisposzione.NRORIGA = nroRiga
   
  // disposizione date — caller decides the rule
  targetDisposzione.DATAPREVISTA = dataPrevistaForDuplicate
   
  // Closure fields explicitly cleared
  targetDisposzione.DATAEFFETTIVA = null
  targetDisposzione.NOTECHIUSURA = null
  targetDisposzione.ESITO = null
  targetDisposzione.IDUTENTECHIUSURA = null
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void Disposizione.duplicateChecklistItems(
  Disposizione duplicatedDisp // 
  boolean shouldDuplicateNote // 
)
{
  duplicatedDisp.Checklist = Checklist.factory(EventoDisposizione, duplicatedDisp, ...)
   
  if (Checklist)
  {
    if (Checklist.ChecklistInstanceItemCollection)
    {
      IDCollection checklistItems of EventoDisposizioneChecklistItem = Checklist.ChecklistInstanceItemCollection
       
      for each EventoDisposizioneChecklistItem eventoDisposizione in checklistItems
      {
         EventoDisposizioneChecklistItem newDisp = new()
         newDisp.init()
         newDisp.IDATTIVITA = duplicatedDisp.IDATTIVITA
         newDisp.NRORIGA = eventoDisposizione.NRORIGA
         newDisp.RIFERIMENTI = eventoDisposizione.RIFERIMENTI
         newDisp.DESCRDOMANDA = eventoDisposizione.DESCRDOMANDA
         if (shouldDuplicateNote)
         {
           newDisp.NOTE = eventoDisposizione.NOTE
         }
         newDisp.MANDATORY = eventoDisposizione.MANDATORY
         duplicatedDisp.Checklist.ChecklistInstanceItemCollection.add(newDisp)
      }
    }
  }
}


// ──────────────────────────────────

// **********************************************************************************
//  Computes visibility of the row-level Disposizioni-panel commands for `this`
//  disposizione, given the evento context and the logged-in user.
// 
//  Pure decision logic — no UI access. Dispatches to per-evento-type implementations
//  because the rules diverge significantly between Normal and Workflow eventi.
// 
//  Caller is responsible for the panel-level commands (AddDisposizione for Normal
//  when no row is selected) — see Evento.canAddDisposizioneFromPanel.
// 
//  PORT-GAP: Delphi additionally guards every mutating command with
//  `actSalvaBase.Enabled` (form-level editability — covers user-edit-rights and
//  read-only state, broader than just isLocked). Not ported because no
//  `Evento.isEditableBy` / equivalent currently exists in Qualibus.
// **********************************************************************************
public DisposizioneCommandVisibility Disposizione.computeCommandVisibility(
  Evento evento     // 
  Utente loggedUser // 
)
{
  DisposizioneCommandVisibility dcv = new()
   
  boolean canSeeChecklist = this.userCanSeeChecklist(loggedUser, evento)
   
  // A locked (Chiuso) evento permits no row-level actions other than viewing.
  if (evento.isLocked())
  {
    // Checklist visibility is independent of evento type 
    dcv.ShowChecklist = canSeeChecklist
    return dcv
  }
   
  //  No Update privilege on Eventi ? mutating commands hidden, Checklist still shown.
  if (!(loggedUser.hasSpecificPrivilegeForKordApp(Eventi, Update)))
  {
    // Checklist visibility is independent of evento type 
    dcv.ShowChecklist = canSeeChecklist
    return dcv
  }
  if (evento.isWorkflowType())
    dcv = this.computeCommandVisibilityForWorkflow(evento, loggedUser)
  else if (evento.isNormaleType())
    dcv = this.computeCommandVisibilityForNormal(evento, loggedUser)
   
  // re initialize showCheckList
  dcv.ShowChecklist = canSeeChecklist
   
  return dcv
}


// ──────────────────────────────────

// *****************************************************************************
// Row-level command visibility for a disposizione belonging to a Normal evento.
// 
// In Normal mode the workflow is linear/free-form: every saved disposizione
// can be modified, removed, duplicated. Closing requires the user has the right
// role/function and the disposizione has the data needed to be closed.
// Re-opening a closed disposizione is gated on userCanReOpen.
// 
// Caller has already verified evento is unlocked and editable.
// *****************************************************************************
private DisposizioneCommandVisibility Disposizione.computeCommandVisibilityForNormal(
  Evento evento     // 
  Utente loggedUser // 
)
{
  DisposizioneCommandVisibility dcv = new()
  dcv.init()
  boolean isOpened = !(this.isClosed())
   
  // -- CLOSE / RE-OPEN  ----------------------------------
  if (isOpened)
    dcv.CloseDisposizione = this.canBeClosedByUser(loggedUser, evento)
  else 
    dcv.ReopenDisposizione = this.userCanReOpen(loggedUser, evento)
   
  // --Inesrt
  dcv.InsertDisposizione = true
   
  // --Duplicate
  dcv.DuplicateDisposizione = true
   
  // -- Remove
  dcv.RemoveDisposizione = isOpened
   
  // -- Move Up / Down (gated by NRORIGA position; canMoveUp/Down own the rule)
  dcv.MoveUp = this.canMoveUp(evento)
  dcv.MoveDown = this.canMoveDown(evento)
   
  return dcv
}


// ──────────────────────────────────

// *********************************************************************************
// Row-level command visibility for a disposizione belonging to a Workflow evento.
// 
// In Workflow mode the disposizione lives inside a workflow step. Most mutating
// commands are limited to "multiple" steps. Closed disposizioni offer Rollback
// instead of re-open. Multiple-step open disposizioni use CloseMultipleDisposizione
// instead of the standard close, until only one remains open (then standard close).
// 
// Caller (computeRowCommandVisibility) has already verified evento is unlocked.
// *********************************************************************************
private DisposizioneCommandVisibility Disposizione.computeCommandVisibilityForWorkflow(
  Evento evento     // 
  Utente loggedUser // 
)
{
  DisposizioneCommandVisibility dcv = new()
  dcv.init()
  boolean isMultiple = this.isMultipleDisposizione()
  boolean isOpened = !(this.isClosed())
   
  boolean onlyOneOfDisposizioneLeftFromMultiples = false
  boolean allMultipleDispClosed = false
   
   
  if (isMultiple)
    allMultipleDispClosed = this.allMultipleDisposizioniClosed(evento, onlyOneOfDisposizioneLeftFromMultiples)
   
  // -- CLOSE / RE-OPEN / ROLLBACK ----------------------------------
  if (isOpened)
  {
    if (isMultiple)
      dcv.CloseMultipleDisposizione = !(allMultipleDispClosed)
    else 
      dcv.CloseDisposizione = this.canBeClosedByUser(loggedUser, evento)
  }
  else 
  {
    boolean isAdminEventi = loggedUser.hasSpecificPrivilegeForKordApp(Eventi, Pers1)
    boolean isRespEvento = evento.IDUTENTERESP == loggedUser.IDUTENTE
     
    // Rollback: admin or evento responsabile only.
    if (isAdminEventi or isRespEvento)
    {
      dcv.Rollback = true
    }
  }
   
  // -- ADD within Multipe / REMOVE / DUPLICATE ---
  if (isMultiple)
  {
    dcv.DuplicateMultipleDisposizione = !(allMultipleDispClosed)
    dcv.RemoveDisposizione = isOpened and !(onlyOneOfDisposizioneLeftFromMultiples)
     
    // -- Move Up / Down (gated by NRORIGA position; canMoveUp/Down own the rule)
    dcv.MoveUp = this.canMoveUp(evento)
    dcv.MoveDown = this.canMoveDown(evento)
  }
   
  return dcv
}


// ──────────────────────────────────

// ********************************************************************************
//  Whether the given user is permitted to see the checklist for this disposizione.
// 
//  Used by computeCommandVisibility to decide ShowChecklist.
// 
//  Ports the Delphi CanSeeCheckList rule. Returns true if the user matches any of:
//    1. Admin on the Eventi QApp (Pers1 privilege)
//    2. Responsible user of the parent evento (evento.IDUTENTERESP)
//    3. Direct executor of this disposizione (when ISFUNCTION = 'N',
//       IDRESPATTIVITA references the dipendente ID)
//    4. Member of the function set as executor (when ISFUNCTION = 'Y',
//       IDRESPATTIVITA references the function ID)
//    5. Pers2 on the Eventi QApp (added per Delphi task #10763)
// ********************************************************************************
public boolean Disposizione.userCanSeeChecklist(
  Utente utente       // 
  Evento parentEvento // 
)
{
  // 1. Admin on Eventi(Pers1)
  if (utente.hasSpecificPrivilegeForKordApp(Eventi, Pers1))
    return true
   
  // 2. Responsible of the evento
  if (parentEvento.IDUTENTERESP == utente.IDUTENTE)
    return true
   
   
  // 3. disposizione executor
  if (this.userIsDisposizioneExecutor(utente))
    return true
   
  // 4. Pers2 on Eventi
  if (utente.hasSpecificPrivilegeForKordApp(Eventi, Pers2))
    return true
   
  return false
}


// ──────────────────────────────────

// *********************************************************************************
// Whether this disposizione can be moved up (toward smaller NRORIGA) given
// the evento's type and locked state.
// 
//  Rules:
//    - Locked evento: never (read-only)
//    - Normal evento: true if NRORIGA > 1 (single global ordering, contiguous 1..N)
//    - Workflow evento, multiple step: true if not the first in the step
//    - Workflow evento, non-multiple: never (steps determine order, not NRORIGA)
// 
// Used both by visibility (DisposizioneCommandVisibility.MoveUp) and as the
// guard inside moveUp itself — single source of truth for the rule.
// 
// PARAM RATIONALE (parentEvento): need isLocked / isWorkflowType / isNormaleType
// and Disposizioni.count() — all live on Evento. See findPositionInMultiple
// for why we don't reach the evento via parentCollection().
// *********************************************************************************
public boolean Disposizione.canMoveUp(
  Evento parentEvento // 
)
{
  if (parentEvento == null)
    return false
   
  if (parentEvento.isLocked())
    return false
   
  if (parentEvento.isNormaleType())
  {
    return NRORIGA > 1
  }
   
  if (parentEvento.isWorkflowType() and this.isMultipleDisposizione())
  {
    // Once all multiples in the step are closed, the order is locked — moving rows then would change the historical record (stricter than Delphi, which always allowed Up/Down for multiple regardless of closure
    // state).
     
    boolean onlyOneMultipleDispIsOpened = false
    if (this.allMultipleDisposizioniClosed(parentEvento, onlyOneMultipleDispIsOpened))
      return false
     
    boolean isFirstMultipleInNonClosedStep = false
    boolean isLastMultipleInNonClosedStep = false
    this.findPositionInMultiple(parentEvento, isFirstMultipleInNonClosedStep, isLastMultipleInNonClosedStep)
    return !(isFirstMultipleInNonClosedStep)
  }
  return false
}


// ──────────────────────────────────

// *************************************************************************************
// Whether this disposizione can be moved down (toward larger NRORIGA). 
// 
//  Rules:
//    - Locked evento: never (read-only)
//    - Normal evento: true if NRORIGA < count (single global ordering, contiguous 1..N)
//    - Workflow evento, multiple step: true if not the last in stepp
//    - Workflow evento, non-multiple: never (steps determine order, not NRORIGA)
// 
// Used both by visibility (DisposizioneCommandVisibility.MoveUp) and as the
// guard inside moveUp itself — single source of truth for the rule.
// 
// PARAM RATIONALE (parentEvento): need isLocked / isWorkflowType / isNormaleType
// and Disposizioni.count() — all live on Evento. See findPositionInMultiple
// for why we don't reach the evento via parentCollection().
// *************************************************************************************
public boolean Disposizione.canMoveDown(
  Evento parentEvento // 
)
{
  if (parentEvento == null)
    return false
   
  if (parentEvento.isLocked())
    return false
   
  if (parentEvento.isNormaleType())
  {
    if (!(parentEvento.Disposizioni.loaded))
      parentEvento.loadCollectionFromDB(parentEvento.Disposizioni, ...)
     
    return NRORIGA < parentEvento.Disposizioni.count()
  }
   
  if (parentEvento.isWorkflowType() and this.isMultipleDisposizione())
  {
    // Once all multiples in the step are closed, the order is locked — moving rows then would change the historical record (stricter than Delphi, which always allowed Up/Down for multiple regardless of closure
    // state).
     
    boolean onlyOneMultipleDispIsOpened = false
    if (this.allMultipleDisposizioniClosed(parentEvento, onlyOneMultipleDispIsOpened))
      return false
     
    boolean isFirstMultipleInNonClosedStep = false
    boolean isLastMultipleInNonClosedStep = false
    this.findPositionInMultiple(parentEvento, isFirstMultipleInNonClosedStep, isLastMultipleInNonClosedStep)
    return !(isLastMultipleInNonClosedStep)
  }
  return false
}


// ──────────────────────────────────

// ***************************************************************************
// For a multiple-step disposizione, determines whether `this` is the first
// and/or last by NRORIGA among all disposizioni belonging to the same step.
// 
// "First" means smallest NRORIGA in the step; "last" means largest. A single
// disposizione in a step is both first and last (callers should treat that
// case as "neither MoveUp nor MoveDown is meaningful").
// 
// If `this` is not a multiple disposizione, both flags are set to false and
// the method returns silently — caller is expected to gate on
// isMultipleDisposizione before invoking.
// 
// PARAM RATIONALE (parentEvento): we need to walk sibling disposizioni in the
// same step. parentCollection() would give us those siblings, but only when
// this` was loaded as part of an Evento.Disposizioni traversal — it returns
// null for standalone-loaded disposizioni (e.g. via Disposizione.getFromDB),
// which breaks unit tests. Taking parentEvento explicitly keeps the method
// callable in any context.
// ***************************************************************************
public void Disposizione.findPositionInMultiple(
  Evento parentEvento   // 
  inout boolean isFirst // 
  inout boolean isLast  // 
)
{
  isFirst = false
  isLast = false
   
  if (parentEvento == null)
    return 
   
  if (!(this.isMultipleDisposizione()))
    return 
   
  if (!(parentEvento.Disposizioni.loaded))
    parentEvento.loadCollectionFromDB(parentEvento.Disposizioni, ...)
   
  int minNroRiga = -1
  int maxNroRiga = -1
   
  for each Disposizione d in parentEvento.Disposizioni
  {
    if (d.IDSTEP == IDSTEP)
    {
      if (minNroRiga < 0 or d.NRORIGA < minNroRiga)
         minNroRiga = d.NRORIGA
      if (maxNroRiga < 0 or d.NRORIGA > maxNroRiga)
         maxNroRiga = d.NRORIGA
    }
  }
   
  if (minNroRiga >= 0)
  {
    isFirst = NRORIGA == minNroRiga
    isLast = NRORIGA == maxNroRiga
     
  }
}


// ──────────────────────────────────

// ***************************************************************************
// Moves this disposizione one position up by swapping NRORIGA with the
// adjacent (NRORIGA - 1) disposizione in the same scope.
// 
// Scope:
//    - Normal evento: all disposizioni (single global ordering)
//    - Workflow evento, multiple step: only same-step disposizioni
// 
// Assumes NRORIGA is contiguous within scope. Insert and Remove maintain this
// invariant; if it's broken (e.g. by external data fix-ups), the swap
//  silently no-ops when no adjacent row is found.
// 
// Re-sorts the Disposizioni collection by NRORIGA after the swap so the
// panel renders in the correct order.
// 
// No-op if already at the top of scope. Caller should typically check
// canMoveUp first and hide/disable the action — this method's no-op is a
// safety net, not the primary gate.
// 
// PARAM RATIONALE (parentEvento): we need access to the Disposizioni
// collection to find the adjacent row and to re-sort. parentCollection()
// would also work but only when `this` was loaded as part of
// Evento.Disposizioni — it returns null in unit tests and any context
// where the disposizione was loaded standalone. Explicit Evento parameter
// keeps the method callable everywhere.
// ***************************************************************************
public void Disposizione.moveUp(
  Evento parentEvento // 
)
{
  if (parentEvento == null)
    return 
   
  if (!(parentEvento.Disposizioni.loaded))
    parentEvento.loadCollectionFromDB(parentEvento.Disposizioni, ...)
   
  boolean scopeByStep = parentEvento.isWorkflowType()
  Disposizione previous = null
  for each Disposizione d in parentEvento.Disposizioni
  {
    if (d.NRORIGA == NRORIGA - 1)
    {
      if (!(scopeByStep) or d.IDSTEP == IDSTEP)
      {
         previous = d
         break 
      }
    }
  }
   
  if (previous == null)
    return 
  NRORIGA = NRORIGA - 1
  previous.NRORIGA = previous.NRORIGA + 1
  parentEvento.sortDisposizioniByNroRiga()
   
}


// ──────────────────────────────────

// **************************************************************************
// Moves this disposizione one position down by swapping NRORIGA with the
// adjacent (NRORIGA + 1) disposizione in the same scope. See moveUp for full
// scope rules and parameter rationale.
// 
// **************************************************************************
public void Disposizione.moveDown(
  Evento parentEvento // 
)
{
  if (parentEvento == null)
    return 
   
  if (!(parentEvento.Disposizioni.loaded))
    parentEvento.loadCollectionFromDB(parentEvento.Disposizioni, ...)
   
  boolean scopeByStep = parentEvento.isWorkflowType()
  Disposizione next = null
  for each Disposizione d in parentEvento.Disposizioni
  {
    if (d.NRORIGA == NRORIGA + 1)
    {
      if (!(scopeByStep) or d.IDSTEP == IDSTEP)
      {
         next = d
         break 
      }
    }
  }
   
  if (next == null)
    return 
   
  NRORIGA = NRORIGA + 1
  next.NRORIGA = next.NRORIGA - 1
  parentEvento.sortDisposizioniByNroRiga()
   
}


// ──────────────────────────────────



// ──────────────────────────────────

// *****************************************************************************************************
// Whether the given user has the role/permission and the disposizione has the data needed to be closed.
// 
// Ports the Delphi popDisposizioniChiudi.Visible role + data checks. Does NOT
// check disposizione is open or is non-multiple — caller must enforce those.
// 
// 
// *****************************************************************************************************
public boolean Disposizione.canBeClosedByUser(
  Utente utente       // 
  Evento parentEvento // 
)
{
  // Must be open and non-multiple to close via standard close form
  if (this.isClosed())
    return false
  if (this.isMultipleDisposizione())
    return false
   
  // data completeness
  if (DATAPREVISTA == null)
    return false
   
  if (IDRESPATTIVITA <= 0)
    return false
   
  if (DESCRATTIVITA == null)
    return false
   
  // Role/permission
   
  boolean isAdminvEventi = utente.hasSpecificPrivilegeForKordApp(Eventi, Pers1)
  boolean isRespEvento = parentEvento.IDUTENTERESP == utente.IDUTENTE
  if (isAdminvEventi or isRespEvento or this.userIsDisposizioneExecutor(utente))
    return true
   
  return false
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean Disposizione.userCanReOpen(
  Utente utente       // 
  Evento parentEvento // 
)
{
  if ((utente.hasSpecificPrivilegeForKordApp(Eventi, Pers1) or this.IDUTENTERESPEVENTO == utente.IDUTENTE) and !(parentEvento.isLocked()))
  {
    return true
  }
  return false
}


// ──────────────────────────────────

// *******************************************************************************
// Moves this disposizione to the position of the given target disposizione,
// shifting other rows in the same scope to make room.
// 
// Used by drag-and-drop: source.moveTo(evento, target) where source is the
// dragged row and target is the row dropped onto. The source takes the
// target's NRORIGA, and the rows between source and target shift accordingly.
// 
// Scope:
//    - Normal evento: all disposizioni (single global ordering)
//    - Workflow evento, multiple step: only same-step disposizioni
// 
// Direction (relative to current position):
//    - Forward (target > current): rows in (current, target] shift down by 1
//    - Backward (target < current): rows in [target, current) shift up by 1
//    - Same row: no-op
// 
// Re-sorts the Disposizioni collection by NRORIGA after the move so the panel
//  renders in the correct order.
// 
// Returns silently if:
//   - target is null or unsaved
//   - target belongs to a different evento
//   - source and target are in different scopes (Workflow: different IDSTEP)
//    - source and target are the same row
// 
//  PARAM RATIONALE (parentEvento): same as moveUp/moveDown — we need the
//  Disposizioni collection. parentCollection() doesn't work for standalone-loaded
//  disposizioni (e.g. unit tests).
// 
// *******************************************************************************
public void Disposizione.moveTo(
  Evento parentEvento // 
  Disposizione target // 
)
{
  if (parentEvento == null)
    return 
   
  if (target == null or target.IDATTIVITA <= 0)
    return 
   
  // same evento check
  if (target.IDEVENTO != IDEVENTO)
    return 
   
  // moving on same target
  if (target == this)
    return 
   
   
  boolean scopeByStep = parentEvento.isWorkflowType()
   
  // Same scope check (Workflow: same step required)
  if (scopeByStep and target.IDSTEP != IDSTEP)
    return 
   
  if (!(parentEvento.Disposizioni.loaded))
    parentEvento.loadCollectionFromDB(parentEvento.Disposizioni, ...)
   
  int currentNroRiga = NRORIGA
  int targetNroRiga = target.NRORIGA
   
  // already in position (defensive)
  if (currentNroRiga == targetNroRiga)
    return 
   
  boolean movingForward = targetNroRiga > currentNroRiga
   
  for each Disposizione d in parentEvento.Disposizioni
  {
    if (d.IDATTIVITA == IDATTIVITA)
      continue 
     
    if (scopeByStep and d.IDSTEP != IDSTEP)
      continue 
     
    if (movingForward)
    {
      if (d.NRORIGA > currentNroRiga and d.NRORIGA <= targetNroRiga)
      {
         d.NRORIGA = d.NRORIGA - 1
      }
    }
    else 
    {
      if (d.NRORIGA >= targetNroRiga and d.NRORIGA < currentNroRiga)
      {
         d.NRORIGA = d.NRORIGA + 1
      }
    }
  }
  NRORIGA = targetNroRiga
   
   
  parentEvento.sortDisposizioniByNroRiga()
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean Disposizione.canDropOn(
  Evento parentEvento // 
)
{
  return this.canMoveUp(parentEvento) or this.canMoveDown(parentEvento)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean Disposizione.canDrag(
  Evento parentEvento // 
)
{
  return this.canMoveUp(parentEvento) or this.canMoveDown(parentEvento)
}


// ──────────────────────────────────

// ****************************************************************************************************************************************************************************************************************
// Computes the DATA_PREVISTA value for this disposizione when it is being duplicated as part of an evento duplication.
// 
// Rule : preserve the disposizione's positive offset from its original evento date. If the disposizione was scheduled before the original evento date (negative offset), clamp to 0 so the duplicate lands on the
// new evento date.
// ****************************************************************************************************************************************************************************************************************
public date Disposizione.computeDataPrevistaForEventoDuplication(
  date sourceEventoDate // DATAEVENTO of the source (original) evento
  date targetEventoDate // DATAEVENTO of the target (new) evento
)
{
  int daysDifference = DH.differenceInDays(sourceEventoDate, DATAPREVISTA)
  if (daysDifference < 0)
    daysDifference = 0
   
  return dateAdd(Day, daysDifference, targetEventoDate)
}


// ──────────────────────────────────

// ****************************************
// Describe the purpose of this function...
// ****************************************
public void Disposizione.UpdateResponsiblePropertiesEndTransactionHandler()
{
  if (MultiSourceResponsible.MultiSourceResponsibleInterface)
  {
    if (EsecutoreIDForDecodingInUI != null && EsecutoreIDForDecodingInUI != "")
    {
      string:flagYN isFunctionValue = null
      int idResposibleValue = null
       
      MultiSourceResponsible.decodeKey(EsecutoreIDForDecodingInUI, isFunctionValue, idResposibleValue)
       
      ISFUNCTION = isFunctionValue
      IDRESPATTIVITA = idResposibleValue
    }
    else 
    {
      ISFUNCTION = No
      IDRESPATTIVITA = null
    }
  }
}


// ──────────────────────────────────

// **************************************************************************************************************************************************************************
// this method must set the string (such as F103) used for decoding in panel, starting from the DB fields (such as IS_FUNCTION and ID_UTENTE RESP, in case of modello evento)
// **************************************************************************************************************************************************************************
public void Disposizione.MultiSourceResponsibleCreationHandler()
{
  if (MultiSourceResponsible != null)
  {
    if (IDRESPATTIVITA > 0)
    {
      string computeId = MultiSourceResponsible.prepareDecodingKey(ISFUNCTION, IDRESPATTIVITA, "P")
      EsecutoreIDForDecodingInUI = computeId
      MultiSourceResponsible.ID = computeId
    }
    else 
    {
      EsecutoreIDForDecodingInUI = ""
      MultiSourceResponsible.ID = ""
    }
  }
}


// ──────────────────────────────────

// ****************************************************************************************************************************************************************************************************************
// this method is intentionally put in the interface to remember the developer that in the after load of the class that implments IMultiSourceResponsible it is mandatory to instantiate a multiSourceResponsbile
// object
// so in this object a line of code like
// multiSourceResponsible = multiSourceResponsible.create(this, funzioneOrDipendente) must be written
// ****************************************************************************************************************************************************************************************************************
public void Disposizione.AfterLoadMultiSourceResponsibleCreation()
{
  MultiSourceResponsible = MultiSourceResponsible.create(this, funzioneOrDipendente)
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event DisposizioneCommandVisibility.OnInit()
{
  RemoveDisposizione = false
  DuplicateDisposizione = false
  InsertDisposizione = false
  CloseDisposizione = false
  CloseMultipleDisposizione = false
  ShowChecklist = false
  Rollback = false
  ReopenDisposizione = false
  DuplicateMultipleDisposizione = false
  MoveUp = false
  MoveDown = false
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static TipoDisposizione TipoDisposizione.create(
  ClasseEvento classe // 
  string description  // 
)
{
  TipoDisposizione td = new()
  td.init()
  td.IDCLASSE = classe.IDCLASSE
  td.DESCRTIPOATTIVITA = description
   
  return td
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public Recordset TipoDisposizione.createSmartlookupRecordset(
  string queryString // 
  int idClasse       // 
)
{
  int idOrigine = this.getOrigine()
   
  Recordset rs = new()
  select into recordset (rs)
    set IDTIPOATTIVITA = IDTIPOATTIVITA
    set DESCRTIPOATTIVITA = DESCRTIPOATTIVITA
    SEQUENZA as SEQUENZA
  from 
    TipiDisposizioni // master table
  where
    IDORIGINE == idOrigine
    ATTIVO == Yes
    DESCRTIPOATTIVITA like "%" + queryString + "%"
    IDCLASSE == idClasse
  order by
    SEQUENZA
    DESCRTIPOATTIVITA
  return rs
}


// ──────────────────────────────────

// ***************************************************************************
// Returns the active TipiDisposizione for the given ClasseEvento as a
// collection of hydrated entities, sorted by SEQUENZA then DESCRTIPOATTIVITA.
// Used to populate the "Add disposizione" popup form in DisposizioniSubform.
// 
// NOTE:IdOrigine is 1 for evento
// ***************************************************************************
public static IDCollection TipoDisposizione.listActiveForClasse(
  int idClasse // 
)
{
  IDCollection tipoDisposizioni of TipoDisposizione = new()
   
  if (idClasse <= 0)
  {
    QappCore.DTTLogMessage("listActiveForClasse called with invalid IdClasse", ..., DTTWarning)
    return tipoDisposizioni
  }
   
  int idOrigine = this.getOrigine()
   
  select into collection (tipoDisposizioni)
  from 
    TipoDisposizione // master table
  where
    IDCLASSE == idClasse
    ATTIVO == Yes
    IDORIGINE == idOrigine
  order by
    SEQUENZA
    DESCRTIPOATTIVITA
   
  return tipoDisposizioni
}


// ──────────────────────────────────



// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event TipoDisposizione.OnInit()
{
  IDTIPOATTIVITA = Sequence.getNextSequence(EVAN_ID_TABELLA, ...)
   
  DevTools.ToBeReviewed("i saw in qualibus c/s, there we are initialising IDORIGINE with 1 even if it is FK with EVA_ORIGINI_ATTIVITA, this must be check and adpat")
  IDORIGINE = 1
  ATTIVO = Yes
  SEQUENZA = 1
   
   
}


// ──────────────────────────────────

// **************************************************************************
// Event called to customize the Smart Lookup procedure at the document level
// **************************************************************************
event TipoDisposizione.OnGetSmartLookup(
  Recordset RecordSet       // The recordset to be filled if the queries run during the Smart Lookup event are customized.
  inout int Level           // The Smart Lookup procedure performs multiple attempts, increasingly widening the search field. This integer parameter specifies the number of attempts made.
  inout boolean NullValue   // A boolean output parameter. If set to True, then NULL (no results found) will be returned to the panel.
  inout boolean Skip        // A boolean output parameter. If set to True, then the standard queries for the current level will not be executed.
  inout boolean Cancel      // A boolean output parameter. If set to True, the Smart Lookup procedure will be stopped.
  IDDocument CallerDocument // The document for which the smart lookup procedure is launched. It can be null.
)
{
  string queryString = nullValue(DESCRTIPOATTIVITA, "")
  if (queryString == "*")
    queryString = ""
   
  // based on caller document we retrieve idClasse since we need it for lookup
  int idClasse = null
  if (CallerDocument)
  {
    if (Disposizione.isMyInstance(CallerDocument))
    {
      Disposizione d = (Disposizione)CallerDocument
       
      if (d.parent)
      {
         if (Evento.isMyInstance(d.parent))
         {
           Evento e = (Evento)d.parent
           idClasse = e.IDCLASSE
         }
      }
    }
    else if (WkfStep.isMyInstance(CallerDocument))
    {
      WkfStep ws = cast(CallerDocument)
       
      // retrieve IDClasse of the WkfStep
      int vIDCLASSEModelloEvento = 0
      select into variables (found variable)
         set vIDCLASSEModelloEvento = IDCLASSE
      from 
         ModelliEvento // master table
      where
         IDTEMPLATEEVENTO == ws.IDTEMPLATEEVENTO
      idClasse = vIDCLASSEModelloEvento
    }
  }
  Recordset rs = this.createSmartlookupRecordset(queryString, idClasse)
  RecordSet.copyFrom(rs)
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event ModelloEvento.OnInit()
{
  int nextID = Sequence.getNextSequence(EVAN_ID_TEMPLATE, ...)
  IDTEMPLATEEVENTO = nextID
  COSTOFISSO = 0
  ATTIVO = Yes
  DESCRTEMPLATE = "Nuovo modello evento"
  MultiSourceResponsible = MultiSourceResponsible.create(this, funzioneOrUtente)
}


// ──────────────────────────────────

// **************************************************************************************
// Event raised to the document during the initial steps of the document saving procedure
// **************************************************************************************
event throws exception ModelloEvento.BeforeSave(
  inout boolean Skip         // A boolean output parameter. If set to True, tells the framework not to perform operations on the database, because they will be managed independently.
  inout boolean Cancel       // A boolean output parameter. If set to True, tells the framework to cancel the save procedure due to errors that occurred. Since the entire document is saved in the same transa...
  int:beforeSavePhases Phase // An integer from 0 to 3 that specifies the progress status of the save procedure as detailed at the beginning of the article.
)
{
  base.BeforeSave(Skip, Cancel, Phase)
   
  if (Phase == PreSave)
  {
     
    if (deleted)
    {
      // manually deleting collections of modelloDisposizioni and modelloCDataSections
       
      if (!(DisposizioniModello.loaded))
         this.loadDisposizioniModello(...)
      for each ModelloEventoDisposizione med in DisposizioniModello
      {
         med.deleted = true
      }
       
      if (!(ModelloEventoCDSections.loaded))
         this.loadModelloEventoCDSections(...)
      for each ModelloEventoCDSection mesdp in ModelloEventoCDSections
      {
         mesdp.deleted = true
      }
    }
  }
}


// ──────────────────────────────────

// ***********************************************************
// Event raised to the document when the transaction is closed
// ***********************************************************
event ModelloEvento.OnEndTransaction()
{
  if (wasModified(ModelloEventoReponsibleIDForDecodingInUI))
  {
    MultiSourceResponsible.MultiSourceResponsibleInterface.updateResponsiblePropertiesEndTransactionHandler()
  }
   
  if (wasModified(WORKFLOWTYPE))
  {
    if (WORKFLOWTYPE == Normal)
    {
      IDCollection coll of IDDocument = this.getLinkedEventi()
      if (coll.count() > 0)
      {
         IDArray errorMsg = Tools.SingleStringToArray("Non è possibile cambiare il comportamento di questo modello evento in quanto ci sono degli eventi che sono stati generati da esso.")
         QappCore.sendAppMessage("MESSAGEBOX", errorMsg)
         WORKFLOWTYPE = Workflow
      }
      else 
      {
         Diagram = null
         WKFSTEPS.loaded = false
         WKFFLOW.loaded = false
         WKFRESULTS.loaded = false
          
         IDForm callerFrom = this.getObjectTag("caller")
         callerFrom.sendMessage("WORKFLOW_TYPE_CHANGED", null, Normal, ...)
      }
    }
    if (WORKFLOWTYPE == Workflow)
    {
      if (DisposizioniModello.count() > 0)
      {
         IDArray errorMsg = Tools.SingleStringToArray("Non è possibile settare un modello a workflow se ci sono delle disposizioni, è necessario eliminarle.")
         QappCore.sendAppMessage("MESSAGEBOX", errorMsg)
         WORKFLOWTYPE = Normal
      }
      else 
      {
         IDForm callerFrom = this.getObjectTag("caller")
         callerFrom.sendMessage("WORKFLOW_TYPE_CHANGED", null, Workflow, ...)
      }
    }
  }
}


// ──────────────────────────────────

// ************************************************************
// Event raised by the document after loading from the database
// ************************************************************
event ModelloEvento.AfterLoad(
  boolean AlreadyLoaded     // A boolean parameter that specifies whether the document was previously loaded.
  IDArray LoadedCollections // IDArray type parameter that contains the collections loaded by the framework. If the value of the Already Loaded parameter is False, the Loaded Collections parameter is NULL.
)
{
  base.AfterLoad(AlreadyLoaded, LoadedCollections)
   
  this.AfterLoadMultiSourceResponsibleCreation()
}


// ──────────────────────────────────

// ************************************************************
// Event raised to the document during the validation procedure
// ************************************************************
event ModelloEvento.OnValidate(
  int:validateReasons Reason // An integer specifying the reason the document is being validated. The most common values are 0 to indicate complete validation before saving, or 1 to indicate a quick validati...
  inout boolean Error        // A boolean output parameter. If set to True, specifies an error condition and stops the validation immediately.
  inout boolean Skip         // A boolean output parameter. If set to True, does not check the rules for automatic validation of the document (see notes).
)
{
  base.OnValidate(Reason, Error, Skip)
   
  if (!(deleted))
  {
    if (DESCRTEMPLATE == "")
    {
      Error = true
      this.setPropertyError("è necessario impostare un valore", DESCRTEMPLATE)
    }
     
    for each EventiCosto ec in Costi
    {
      boolean validCosto = ec.validate(...)
      Error = !(validCosto)
    }
     
    if (this.isWorkflowModelloEvento())
    {
      if (Diagram)
      {
         IDArray errorsArray = new()
         boolean isDiagramValid = Diagram.isValidDiagram(errorsArray)
          
         if (!(isDiagramValid))
         {
           string errorMessage = Tools.ArrayToString(errorsArray, "<br/>")
           Error = true
           this.setPropertyError(errorMessage, WKFDIAGRAM)
         }
      }
      else 
      {
         QappCore.DTTLogMessage("Diagram not instantiated", ..., DTTWarning)
      }
    }
    else 
    {
      for each ModelloEventoDisposizione med in DisposizioniModello
      {
         boolean validDisposizione = med.validate(...)
         Error = !(validDisposizione)
      }
    }
  }
}


// ──────────────────────────────────

// *************************************************************
// Event raised to the document at the end of the save procedure
// *************************************************************
event throws exception ModelloEvento.AfterSave(
  inout boolean Cancel // Boolean output parameter that makes it possible to cancel the entire save. It is equivalent to the Cancel parameter of the BeforeSave event.
)
{
  // Save Diagram data to db and reload to populate Workflow collection (used by other section)
  if (this.isWorkflowModelloEvento())
  {
    this.saveDiagramDataToDbAndPopulateLocalCollections(true)
  }
  else 
  {
    this.deleteWorkflowCollections()
  }
}


// ──────────────────────────────────

// **************************************************************************************************************************************************************************
// creation of all the assignments of cdata section to the modello evento, based on what is set in opzioni: all common eventi cdata sections + the ones assigned to the class
// **************************************************************************************************************************************************************************
public void ModelloEvento.createSezioniDatiPersonalizzatiLinkedToModello()
{
  DevTools.RefactoringOpportunity("this should be done with classes, query is a quic and dirty solution to make the creatino of EVA_TEMPL_CDATA_SECTIONS records working at the time of gortani unit tes wriing, 
        but this should be writen with proper collections")
   
   
  // fetching and inserting common cdata sections
  IDCollection activeCommonSections of CdataSection = new()
  select into collection (activeCommonSections)
  from 
    CdataSection // master table
  where
    Active == Yes
    ISCOMMON == Yes
    KordApp == Eventi
   
  QappCore.DTTLogMessage(formatMessage("|1 common sections found", activeCommonSections.count(), ...), ..., DTTInfo)
   
  for each CdataSection cs in activeCommonSections
  {
    QappCore.DTTLogMessage(formatMessage("inserting common section |1", cs.IDCDATASEC, ...), ..., DTTInfo)
     
    ModelloEventoCDSection mesdp = ModelloEventoCDSection.create(cs, this)
    this.addModelloEventoCDSection(mesdp)
  }
   
  // fetching and inserting non common cdata sections assigned to the idClasse
  for each row (readonly)
  {
    select
      IDCDATASECSection = CDATASECTIONS.IDCDATASEC
    from 
      CDATASECTIONS   // master table
      CDATAMODULETYPE // joined with CDATA SECTIONS using key FK_CDATA_MODULE_TYPE_ID_CDATA_SEC
    where
      CDATASECTIONS.Active == Yes
      CDATASECTIONS.ISCOMMON == No
      CDATASECTIONS.KordApp == Eventi
      CDATAMODULETYPE.IDTIPO == IDCLASSE
     
     
    QappCore.DTTLogMessage(formatMessage("inserting  non common section |1", IDCDATASECSection, ...), ..., DTTInfo)
     
    CdataSection cs = new()
    cs.IDCDATASEC = IDCDATASECSection
    cs.loadFromDB(...)
     
    ModelloEventoCDSection mesdp = ModelloEventoCDSection.create(cs, this)
    this.addModelloEventoCDSection(mesdp)
  }
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void ModelloEvento.addModelloEventoCDSection(
  ModelloEventoCDSection cdSection // 
)
{
  cdSection.VISIBLE = Yes
  ModelloEventoCDSections.add(cdSection)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static ModelloEvento ModelloEvento.getFromDB(
  int mainId                                                             // 
  optional string:getFromDBLoadingModes loadingMode = "normalZeroLevels" // 
)
{
  ModelloEvento me = cast(MainModule.retrieve(ModelliEventi, mainId, loadingMode))
   
   
  if (me != null)
  {
     
    // Diagram is a transient object not persisted by the ORM, so it must be initialized
    // after loadFromDB completes. Cannot be done in AfterLoad because initializeDiagramObject
    // reloads WKF collections on ModelloEvento — unsafe while loadFromDB is still running.
    if (me.isWorkflowModelloEvento())
    {
      me.GetDiagram()
    }
  }
  else 
  {
    QappCore.DTTLogMessage(formatMessage("cannot try to retrieve diagramObject sicne retrieved modello evento is null for id '|1'", mainId, ...), ..., DTTError)
  }
   
  return me
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public IDCollection ModelloEvento.retrieveAvailableCDSections()
{
   
  IDCollection allCdataSectionsInModello of CdataSection = new()
   
  if (this.ModelloEventoCDSections.count() == 0)
  {
    this.loadCollectionFromDB(ModelloEventoCDSections, ...)
  }
   
  // special case when a new Modello Evento is opened but still not saved, so we refer all the customdata sections which are present in collection ModelloEventoCDSections (collection for opzioni Modello eventi
  // CD sections and populated at "create Sezioni Dati Personalizzati Linked To Modello" while creating a new Modello evento)
  for each ModelloEventoCDSection mecds in ModelloEventoCDSections
  {
    if (mecds.VISIBLE == Yes)
    {
      CdataSection cs = CdataSection.get(mecds.IDCDATASEC)
      if (cs and cs.Active == Yes)
      {
         cs.loadCollectionFromDB(cs.CDATAFIELDSINFO, ...)
         allCdataSectionsInModello.add(cs)
      }
    }
  }
//  if (inserted)
//  {
//  }
//  else 
//  {
//    select into collection (allCdataSectionsInModello)
//      set IDCDATASEC = CDATASECTIONS.IDCDATASEC
//      set Name = CDATASECTIONS.Name
//      set Description = CDATASECTIONS.Description
//      set Sequence = CDATASECTIONS.Sequence
//      set Active = CDATASECTIONS.Active
//      set KordApp = CDATASECTIONS.KordApp
//      set ISCOMMON = CDATASECTIONS.ISCOMMON
//      set DEPRECATEDIDTEMPLATE = CDATASECTIONS.DEPRECATEDIDTEMPLATE
//      set DEPRECATEDTEMPLATE = CDATASECTIONS.DEPRECATEDTEMPLATE
//      set DEPRECATEDIDPARENTTEMPLATEREMOTE = CDATASECTIONS.DEPRECATEDIDPARENTTEMPLATEREMOTE
//      set HIDDEN = CDATASECTIONS.HIDDEN
//    from 
//      CDATASECTIONS                          // master table
//      ModelloEventoSezioniDatiPersonalizzati // joined with CDATA SECTIONS using key FK_EVA_TEMPL_CDATA_SECTIONS_CDATA_SECTIONS
//    where
//      CDATASECTIONS.Active == Yes
//      ModelloEventoSezioniDatiPersonalizzati.VISIBLE == Yes
//      ModelloEventoSezioniDatiPersonalizzati.IDTEMPLATEEVENTO == IDTEMPLATEEVENTO
//    order by
//      CDATASECTIONS.Sequence
//      CDATASECTIONS.Description
//     
//    for each CdataSection cs in allCdataSectionsInModello
//    {
//      cs.loadCollectionFromDB(cs.CDATAFIELDSINFO, ...)
//    }
//  }
   
  CdataSection cdSection = new()
  int seqenzaFieldindex = cdSection.getPropertyIndex("SEQUENZA", true, true, true, true)
  int descriptionFieldindex = cdSection.getPropertyIndex("CDATA_SECTION_DESCRIPTION", true, true, true, true)
   
  allCdataSectionsInModello.addSortCriteria(seqenzaFieldindex)
  allCdataSectionsInModello.addSortCriteria(descriptionFieldindex)
  allCdataSectionsInModello.doSort()
  for each CdataSection cs in allCdataSectionsInModello
  {
    QappCore.DTTLogMessage(formatMessage("il nome della sezione è |1", cs.Description, ...), ...)
  }
   
  return allCdataSectionsInModello
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean ModelloEvento.bypassCdataValidation()
{
   
  // modelli evento is valid even with non populated mandatory cdata
  return true
}


// ──────────────────────────────────

// *******************************************************************************************************************
// it returns the property index of the property linked to the main image field of the class (e.g. ART_ANAGRAFICA.FOTO
// *******************************************************************************************************************
public int ModelloEvento.PROTECTEDgetMainImagePropertyIndex()
{
  return null
}


// ──────────────────────────────────

// ****************************************************************************************************************************
// it returns the property index of the property linked to the second image field of the class (e.g. ART_ANAGRAFICA.FOTO_SECOND
// ****************************************************************************************************************************
public int ModelloEvento.PROTECTEDgetSecondImagePropertyIndex()
{
  return null
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean ModelloEvento.isLocked()
{
  return false
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public IDCollection ModelloEvento.getLoggedUserAvailableModelliEvento(
  string:eventiClassesRolesTypes role // 
)
{
  IDCollection coll of ClasseEvento = EventoClasseRole.getClasses(QappCore.Loggeduser, role)
  return coll
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private WkfFlow ModelloEvento.getFirstFlow()
{
  if (!(WKFFLOW.loaded))
  {
    this.loadWKFFLOW(...)
  }
   
  WkfFlow firstFlow = null
  for each WkfFlow wf in WKFFLOW
  {
    if (wf.ISSTART == Yes)
    {
      firstFlow = wf
      break 
    }
  }
   
  if (!(firstFlow))
  {
    QappCore.DTTLogMessage("first flow not found", ..., DTTError)
  }
   
  return firstFlow
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public EventoPublicStato ModelloEvento.getStatusOfFirstFlow()
{
  WkfFlow firstFlow = this.getFirstFlow()
   
  EventoPublicStato firstFlowPublicStato = EventoPublicStato.get(firstFlow.IdEventoPublicStato)
   
  return firstFlowPublicStato
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean ModelloEvento.isWorkflowModelloEvento()
{
  boolean workflowModelloEvento = WORKFLOWTYPE == Workflow
  return workflowModelloEvento
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public WkfStep ModelloEvento.getFirstStep()
{
  WkfFlow firstFlow = this.getFirstFlow()
  WkfStep firstStep = new()
  firstStep.IDSTEP = firstFlow.IDNEXTSTEP
   
   
  // first step is (at least at the moment of writing) a single step, the first flow goest from START to first step, so the NextStep of the first flow is for sure the IDStep of firstSteo
  try 
  {
    firstStep.loadFromDB(...)
  }
  catch 
  {
    QappCore.DTTLogMessage(formatMessage("impossible to load step with ID |1", firstFlow.IDNEXTSTEP, ...), ..., DTTError)
    firstStep = null
  }
  return firstStep
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public Diagram ModelloEvento.GetDiagram()
{
  if (Diagram == null and this.isWorkflowModelloEvento())
  {
    this.initializeDiagramObject()
  }
   
  return Diagram
}


// ──────────────────────────────────

// ***********************************************************
// centralized method to get the yellow used to paint workflow
// ***********************************************************
public static string ModelloEvento.getHexadecimalBrightYellowColor()
{
  string brightYellow = "#fff933"
  return brightYellow
}


// ──────────────────────────────────

// **********************************************************
// centralized method to get the green used to paint workflow
// **********************************************************
public static string ModelloEvento.getHexadecimalBrightGreenColor()
{
  string brightGreen = "#00ff00"
  return brightGreen
}


// ──────────────────────────────────

// **********************************************************************************
// takes a float string like "123,3232" or "123.3232" and returns a floor of it "123"
// static since it is an utility
// **********************************************************************************
public static void ModelloEvento.floatStringFloored(
  inout string floatString // 
)
{
  floatString = SH.leftUpToDelimiter(floatString, ",", ...)
  floatString = SH.leftUpToDelimiter(floatString, ".", ...)
}


// ──────────────────────────────────

// *********************************************************************************
// Applies step positions and flow directions to the workflow collections by reading
// the Delphi-exported XML stored in CUSTOMEXPORTEDDIAGRAMXML.
// If no XML is available, falls back to a default horizontal layout.
// No-op if the modello is not a workflow or has no steps.
// *********************************************************************************
public void ModelloEvento.applyDiagramPositionsFromXml()
{
   
  if (WORKFLOWTYPE != Workflow)
  {
    QappCore.DTTLogMessage("Modello evento has not Workflow behavior", ..., DTTError)
    return 
  }
   
  if (WKFSTEPS == null || WKFSTEPS.count() == 0)
  {
    QappCore.DTTLogMessage("No steps in collection, so no need to parse XML", ..., DTTWarning)
    return 
  }
   
  if (CUSTOMEXPORTEDDIAGRAMXML == null or CUSTOMEXPORTEDDIAGRAMXML == "")
  {
    QappCore.DTTLogMessage("No XML, applying default positions", ..., DTTInfo)
    this.applyDefaultStepAndFlowPositions()
    return 
  }
   
  IDMap diagramRoot = this.parseXMLToDiagramRoot()
  if (diagramRoot == null)
  {
    QappCore.DTTLogMessage("Diagram node not found in XML", ..., DTTError)
    return 
  }
   
  this.resetStepAndFlowPositionsBeforeXmlConversion()
   
  QappCore.DTTLogMessage("Migrating diagram positions from XML", ..., DTTInfo)
  this.applyStepPositionsFromXml(diagramRoot)
  this.applyFlowDirectionsFromXml(diagramRoot)
}


// ──────────────────────────────────

// ***************************************************************************************
// Converts a C/S XML anchor direction string to the corresponding DevExtreme point index.
// Top=4, Right=1, Bottom=2, Left=3.
// Unknown values are logged as errors and default to Right (1) — this should not happen
// in normal operation; if it does, the source XML contains an unexpected direction value.
// ***************************************************************************************
private static int ModelloEvento.directionToIndex(
  string xmlPosition // 
)
{
  switch (xmlPosition)
  {
    case "Top":
      return 4
    break
    case "Right":
      return 1
    break
    case "Bottom":
      return 2
    break
    case "Left":
      return 3
    break
    default:
      QappCore.DTTLogMessage(formatMessage("directionToIndex: unknown direction '|1', defaulting to Right (1)", xmlPosition, ...), ..., DTTError)
      return 1
    break
  }
}


// ──────────────────────────────────

// ********************************************************************************************
// a string like Mono step (Sel. esecutore) is split in
// first string: Mono step
// second string: (Sel. esecutore)
// 
// to reconstruct the C/S situation for which main text is on the first line
// 
// special handled case: MULTI step; in this case C/S exports like description (esecutore)MULTI
// so we handle that MULTI
// 
// ********************************************************************************************
private void ModelloEvento.splitTextInMainAndDetail(
  string inputString      // 
  inout string firstPart  // 
  inout string secondPart // 
)
{
  boolean specialCaseOfMulti = right(inputString, 6) == ")MULTI"
   
  if (specialCaseOfMulti)
  {
     
    // for multi we first remove MULTI from the end of the string...
    inputString = SH.removeRightChars(inputString, 5)
  }
   
  boolean lastCharIsClosedParenthesis = right(inputString, 1) == ")"
  if (lastCharIsClosedParenthesis)
  {
    string parentheisContentWithParenthesisAtTheEnd = SH.rightUpToDelimiter(inputString, "(", ...)
     
    string parenthesisContentWrappedByParenthesis = "(" + parentheisContentWithParenthesisAtTheEnd
    string intialPartOfStringWithoutParenthesis = replace(inputString, " " + parenthesisContentWrappedByParenthesis, "")
     
    firstPart = intialPartOfStringWithoutParenthesis
    secondPart = parenthesisContentWrappedByParenthesis
     
    if (specialCaseOfMulti)
    {
      // ...to finally reattach it
      secondPart = secondPart + "MULTI"
    }
  }
  else 
  {
    firstPart = inputString
    secondPart = ""
  }
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private int ModelloEvento.tmsSideToDXSide(
  string:TMSDiagramShapeSides tmsSide // 
)
{
  int:DXDiagramShapeSides dxSide = 0
  switch (tmsSide)
  {
    case rightSide:
      dxSide = rightSide
    break
    case bottomSide:
      dxSide = bottomSide
    break
    case leftSide:
      dxSide = leftSide
    break
    case topSide:
      dxSide = topSide
    break
  }
  return dxSide
}


// ──────────────────────────────────

// *******************************************************************************************
// Fallback used when no XML is available.
// Positions steps in a horizontal row with equal spacing and sets default flow point indexes.
// *******************************************************************************************
private void ModelloEvento.applyDefaultStepAndFlowPositions()
{
  float xPosDist = 2
  float nextXPos = 3
  float yPos = 1
  int stepCount = WKFSTEPS.count() - 2
   
  for each WkfStep ws in WKFSTEPS
  {
    // if already fixed, just skip
    if (ws.X != 0 && ws.X != 0,502)
      continue 
     
    // set default dimension of the box
    ws.WIDTH = 1,5
    ws.HEIGHT = 1
     
    // fixed position for start
    if (ws.isStartStep())
    {
      ws.X = 0,375
      ws.Y = 0,375
      continue 
    }
     
    if (ws.isStopStep())
    {
      ws.X = 1 + xPosDist + (stepCount * xPosDist)
      ws.Y = yPos + (stepCount * 0,25)
      continue 
    }
     
    // all other steps
    ws.Y = yPos
     
    ws.X = nextXPos
    nextXPos = nextXPos + xPosDist
  }
   
  // set that every from start from the bottom to the top of the next step
  for each WkfFlow wf in WKFFLOW
  {
    if (isNull(wf.FROMPOINTINTEX))
      wf.FROMPOINTINTEX = 2
     
    if (isNull(wf.TOPOINTINTEX))
      wf.TOPOINTINTEX = 0
  }
}


// ──────────────────────────────────

// ********************************************************************************
// Counts how many steps already have an X position within ±0,25 of the given x.
// Used to detect overlapping positions before assigning a new step's X coordinate.
// ********************************************************************************
private int ModelloEvento.CountStepOnSpecificXPosition(
  float x // the x position to check for overlaps
)
{
  float OVERLAPTolerance = 0,25
  float min = x - OVERLAPTolerance
  float max = x + OVERLAPTolerance
   
  int count = 0
  for each WkfStep ws in WKFSTEPS
  {
    if (ws.X == null)
      continue 
    if (ws.X < max and ws.X > min)
      count = count + 1
  }
   
  return count
}


// ──────────────────────────────────

// ********************************************************************************
// Counts how many steps already have a Y position within ±0,25 of the given y.
// Used to detect overlapping positions before assigning a new step's Y coordinate.
// ********************************************************************************
private int ModelloEvento.CountStepOnSpecificYPosition(
  float y // 
)
{
  float OVERLAPTolerance = 0,25
  float min = y - OVERLAPTolerance
  float max = y + OVERLAPTolerance
   
  int count = 0
  for each WkfStep ws in WKFSTEPS
  {
    if (ws.Y == null)
      continue 
    if (ws.Y < max and ws.Y > min)
      count = count + 1
  }
   
  return count
}


// ──────────────────────────────────

// ********************************************************************************
// Parses CUSTOMEXPORTEDDIAGRAMXML into an IDMap and returns the Diagram root node.
// Returns null and logs an error if the Diagram node is missing.
// ********************************************************************************
private IDMap ModelloEvento.parseXMLToDiagramRoot()
{
  XMLDocument modelloEventoDiagramXml = new()
  modelloEventoDiagramXml.loadFromString(CUSTOMEXPORTEDDIAGRAMXML, XML)
  string xmlAsJson = modelloEventoDiagramXml.saveToString(JSON)
  IDMap diagramIDMap = cast(JSON.parse(xmlAsJson))
  IDMap diagramRoot = (IDMap)diagramIDMap.getObject("Diagram")
   
  return diagramRoot
}


// ──────────────────────────────────

// *********************************************************************************
// Reads step positions from the XML diagram root and applies them to WKFSTEPS.
// C/S Delphi diagram uses different units than DevExtreme:
//   - Width, Height, Y: divide by 70 (vertical/size unit)
//   - X: divide by 75 (horizontal unit, wider spacing in C/S)
// Steps not found in XML get the sentinel position (0,502) and an error log.
// This should never happen in practice: Delphi always exports all steps in the XML.
// 
// Returns true if at least one step was updated from XML.
// *********************************************************************************
private boolean ModelloEvento.applyStepPositionsFromXml(
  IDMap diagramRoot // 
)
{
  boolean anyStepUpdated = false
   
  float UNITSizeAndY = 70,00
  float UNITX = 75,00
  float MINSize = 1
   
  // marks steps set to fallback in a previous run: re-process them
  float SENTINELUnresolvedPosition = this.getSentinelUnresolvedPositionValue()
   
  // manage xml STEP block (blocks = steps)
  IDArray xmlBlocks = (IDArray)diagramRoot.getObject("Block")
   
  int stepCounter = 0
   
  // check every step to found position info in XML
  for each WkfStep curStep in WKFSTEPS
  {
    // skip only if already positioned AND not the sentinel fallback value
    if (!(isNull(curStep.X)) and curStep.X != SENTINELUnresolvedPosition)
      continue 
     
    stepCounter = stepCounter + 1
     
    // search for a reference in XML
    for (int i = 0; i < xmlBlocks.length(); i = i + 1)
    {
      IDMap currentXmlBlock = (IDMap)xmlBlocks.getObject(i)
       
      // step found in XML
      if (curStep.IDSTEP == currentXmlBlock.getValue("IdStep"))
      {
         // The C/S Diagram and the Devextreme diagram use different units of measurement, so the properties need to be recalculated.
          
         // calculate width
         string widthString = currentXmlBlock.getValue("Width")
         float width = this.parseXmlFloat(widthString)
         float convertedWidth = round(width / UNITSizeAndY, 2)
         convertedWidth = if(convertedWidth < MINSize, MINSize, convertedWidth)
         curStep.WIDTH = toFloat(convertedWidth)
          
         // calculate height
         string heightString = currentXmlBlock.getValue("Height")
         float height = this.parseXmlFloat(heightString)
         float convertedHeight = round(height / UNITSizeAndY, 2)
         convertedHeight = if(convertedHeight < MINSize, MINSize, convertedHeight)
         curStep.HEIGHT = toFloat(convertedHeight)
          
         // calculate x position
         string xString = currentXmlBlock.getValue("Left")
         float x = this.parseXmlFloat(xString)
         float convertedX = round(x / UNITX, 2)
         curStep.X = toFloat(convertedX)
          
         // calculate y position
         string yString = currentXmlBlock.getValue("Top")
         float y = this.parseXmlFloat(yString)
         float convertedY = round(y, 2)
         convertedY = round(convertedY / UNITSizeAndY, 2)
         curStep.Y = toFloat(convertedY)
          
         anyStepUpdated = true
      }
    }
     
    // if not updated with XML: this should never happen — Delphi XML always exports all steps.
    // If it does, log an error and use sentinel position to make the problem visible.
    if (isNull(curStep.X))
    {
      QappCore.DTTLogMessage(formatMessage("applyStepPositionsFromXml: step |1 not found in XML — this should not happen", curStep.IDSTEP, ...), ..., DTTError)
      //  
      // fallback for steps not in XML but in DB
      curStep.WIDTH = 1
      curStep.HEIGHT = 0,7
      curStep.X = SENTINELUnresolvedPosition
      curStep.Y = SENTINELUnresolvedPosition
    }
  }
  return anyStepUpdated
}


// ──────────────────────────────────

// ******************************************************************
// Reads flow anchor directions from the XML diagram root and applies
// fromPointIndex / toPointIndex to WKFFLOW via directionToIndex().
// Flows not found in XML get default indexes (0, 0).
// ******************************************************************
private void ModelloEvento.applyFlowDirectionsFromXml(
  IDMap diagramRoot // 
)
{
  if (WKFFLOW == null or WKFFLOW.count() == 0)
    return 
   
   
  IDArray xmlLines = (IDArray)diagramRoot.getObject("Line")
   
  for each WkfFlow curFlow in WKFFLOW
  {
    if (!(isNull(curFlow.FROMPOINTINTEX)))
      continue 
     
    for (int i = 0; i < xmlLines.length(); i = i + 1)
    {
      IDMap currentXmlLine = (IDMap)xmlLines.getObject(i)
       
      if (curFlow.IDFLOW == currentXmlLine.getValue("IdFlow"))
      {
//         // populate flow points
//          
         // populate flow from and to point index
         int fromPointIndex = this.directionToIndex(currentXmlLine.getValue("SourceAnchorIndex"))
         int toPointIndex = this.directionToIndex(currentXmlLine.getValue("TargetAnchorIndex"))
//          
         curFlow.FROMPOINTINTEX = fromPointIndex
         curFlow.TOPOINTINTEX = toPointIndex
      }
    }
     
    // if no XML, set at least default values
    if (isNull(curFlow.FROMPOINTINTEX))
    {
      curFlow.POINTS = ""
      curFlow.FROMPOINTINTEX = 0
      curFlow.TOPOINTINTEX = 0
    }
  }
}


// ──────────────────────────────────

// **************************************************************************************
// Parses a float value from an XML node value that may use a comma as decimal separator,
// as exported by C/S Delphi. Direct float conversion would truncate "94,14" to 94.
// **************************************************************************************
private float ModelloEvento.parseXmlFloat(
  string rawValue // 
)
{
  string sanitizedRawValue = replace(rawValue, ",", ".")
  return toFloat(sanitizedRawValue)
}


// ──────────────────────────────────

// ********************************************************************
// Resets X/Y on all steps and FROMPOINTINTEX/TOPOINTINTEX on all flows
// so that applyStepPositionsFromXml and applyFlowDirectionsFromXml
// always re-process every element from scratch, regardless of any
// coordinates left by a previous partial conversion.
// ********************************************************************
private void ModelloEvento.resetStepAndFlowPositionsBeforeXmlConversion()
{
  for each WkfStep ws in WKFSTEPS
  {
    ws.X = null
    ws.Y = null
  }
   
  for each WkfFlow wf in WKFFLOW
  {
    wf.FROMPOINTINTEX = null
    wf.TOPOINTINTEX = null
  }
}


// ──────────────────────────────────

// ***************************************************************************************************************
// centralized method where to define the custom value for which the step has not been set by code and not by user
// ***************************************************************************************************************
private static float ModelloEvento.getSentinelUnresolvedPositionValue()
{
  return 0,502
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string ModelloEvento.createJsonOfDiagramShapes(
  optional Disposizione currentDisposizione // 
)
{
  string storedShape = ""
   
  storedShape = this.CreateShapeJsonForDiagramStatic(WKFSTEPS, currentDisposizione)
   
  if (!(inserted))
  {
    WKFSTEPS.setOriginal()
  }
   
  return storedShape
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string ModelloEvento.createJsonOfDiagramFlows()
{
  string storedFlow = ""
   
  storedFlow = this.CreateFlowJsonForDiagramStatic(WKFFLOW, WKFSTEPS)
   
  if (!(inserted))
  {
    WKFFLOW.setOriginal()
  }
   
  return storedFlow
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public IDCollection ModelloEvento.getWkfResults()
{
  return WKFRESULTS
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public IDCollection ModelloEvento.getWkfSteps()
{
  return WKFSTEPS
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public IDCollection ModelloEvento.getWkfFlows()
{
  return WKFFLOW
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public IDCollection ModelloEvento.getWkfDisposizioni()
{
  return MultipleStepTemplateDisposizioneCollection
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void ModelloEvento.initializeDiagramObject()
{
  if (this.isWorkflowModelloEvento())
  {
    Diagram = Diagram.create(this)
    this.saveDiagramDataToDbAndPopulateLocalCollections(false)
  }
  else 
  {
    Diagram = null
  }
}


// ──────────────────────────────────

// ***********************************************************************************************************
// Diagram hold a local copy of some table data (DGFLOW, DGSTEPS, DGRESULTS,..) to manage the diagram. 
// To let the Modello evento to work correctly and have the same data, we need to reload that data to DB.
// If this happens during the SAVE of modello evento, we need the force the Diagram to save his tables to DB, 
// so what modello evento read is the last version.
// ***********************************************************************************************************
public void ModelloEvento.saveDiagramDataToDbAndPopulateLocalCollections(
  optional boolean forceDiagramSaveToDb = 0 // 
)
{
  if (forceDiagramSaveToDb)
  {
    Diagram.SaveDiagramCollectionsToDb()
  }
   
  WKFFLOW.loaded = false
  WKFSTEPS.loaded = false
  WKFRESULTS.loaded = false
  MultipleStepTemplateDisposizioneCollection.loaded = false
   
  this.loadWorkflowCollections()
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void ModelloEvento.saveFlowCollection()
{
  WKFFLOW.saveToDB(...)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void ModelloEvento.saveStepsCollection()
{
  WKFSTEPS.saveToDB(...)
}


// ──────────────────────────────────

// ****************************************************************************************
// Builds and returns a JSON string representing the workflow diagram shapes
// from a list of WkfStep. Each shape includes position, size, type (startStop/step)
// and descriptive data. If a current Disposizione is provided, the active step
// is highlighted in yellow (multiple) or green (single). Returns empty string if no steps.
// ****************************************************************************************
public static string ModelloEvento.CreateShapeJsonForDiagramStatic(
  IDCollection steps of WkfStep             // 
  optional Disposizione currentDisposizione // 
)
{
  string storedShape = ""
   
  if (steps != null and steps.count() > 0)
  {
    IDArray storedShapeArray = new()
     
    for each WkfStep ws in steps
    {
      // if position and size not set, use default, this is a fallback in case step is without X or Y
      if (isNull(ws.X) || ws.X == 0)
      {
          
         // marks steps set to fallback in a previous run: re-process them
         float SENTINELUnresolvedPosition = this.getSentinelUnresolvedPositionValue()
          
         // x set to 0.502 (a strange value) so I can identify later that is a fallback value
         ws.X = SENTINELUnresolvedPosition
         ws.Y = SENTINELUnresolvedPosition
         ws.WIDTH = 1
         ws.HEIGHT = 0,7
      }
       
      // fix some possible missing width and height
      if (ws.WIDTH == 0 or isNull(ws.WIDTH))
         ws.WIDTH = 1
      if (ws.HEIGHT == 0 or isNull(ws.HEIGHT))
         ws.HEIGHT = 0,7
       
      IDMap singleShape = new()
      singleShape.setValue("x", ws.X)
      singleShape.setValue("y", ws.Y)
      singleShape.setValue("width", ws.WIDTH)
      singleShape.setValue("height", ws.HEIGHT)
      singleShape.setValue("id", toString(ws.IDSTEP))
      singleShape.setValue("itemType", "shape")
      string type = ""
      if (ws.isStartStep() || ws.isStopStep())
      {
         type = "startStop"
         singleShape.setValue("type", "startStop")
      }
      else 
      {
         type = "step"
         singleShape.setValue("type", "step")
      }
       
      // create data Item sub JSON
      IDMap dataItem = new()
      if (ws.isStartStep() || ws.isStopStep())
      {
         dataItem.setValue("name", "")
         dataItem.setValue("description", upper(ws.STEPDESCRIPTION))
      }
      else 
      {
         dataItem.setValue("name", ws.generateStepJSONTitle())
         dataItem.setValue("description", ws.generateStepJSONDescription())
      }
      dataItem.setValue("Type", type)
       
      singleShape.setObject("dataItem", dataItem)
       
      // set here the color for Eventi Workflow
      if (currentDisposizione != null && currentDisposizione.IDSTEP == ws.IDSTEP)
      {
         QappCore.DTTLogMessage(formatMessage("Highlight current step id |1 ", ws.IDSTEP, ...), ..., DTTInfo)
         if (ws.MULTIPLE == Yes)
         {
           singleShape.setValue("color", this.getHexadecimalBrightYellowColor())
         }
         else 
         {
           singleShape.setValue("color", this.getHexadecimalBrightGreenColor())
         }
      }
      else 
      {
         singleShape.setValue("color", "#fff")
      }
       
       
      // add the shape to the shape array
      storedShapeArray.addObject(singleShape)
    }
    storedShape = JSON.stringify(storedShapeArray)
  }
   
  return storedShape
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static string ModelloEvento.CreateFlowJsonForDiagramStatic(
  IDCollection flows of WkfFlow // 
  IDCollection steps of WkfStep // 
)
{
  string storedFlow = ""
   
  if (flows == null or flows.count() == 0)
  {
    return storedFlow
  }
  IDArray storedFlowArray = new()
   
  for each WkfFlow wf in flows
  {
    // default if point index is not set
    if (isNull(wf.TOPOINTINTEX))
    {
      wf.FROMPOINTINTEX = 2
      wf.TOPOINTINTEX = 0
      wf.POINTS = ""
    }
     
    IDMap singleFlow = new()
    singleFlow.setValue("id", toString(wf.IDFLOW))
    singleFlow.setValue("itemType", "connector")
    singleFlow.setValue("fromId", toString(wf.IDSTEP))
    singleFlow.setValue("toId", toString(wf.IDNEXTSTEP))
    singleFlow.setValue("fromPointIndex", wf.FROMPOINTINTEX)
    singleFlow.setValue("toPointIndex", wf.TOPOINTINTEX)
     
    IDMap dataItem = new()
    singleFlow.setObject("dataItem", dataItem)
     
    // flow label: description eesito + delay if present
    string flowText = this.resolveFlowResultDescription(wf, steps)
    string delayDays = if(wf.DELAYDAYS > 0, "(" + toString(wf.DELAYDAYS) + ")", "")
     
    // JSON FLOW LABEL
    string space = if(delayDays == "" or flowText == "", "", " ")
    flowText = flowText + space + delayDays
    singleFlow.setValue("text", flowText)
     
    // JSON points
    if (wf.POINTS != "")
    {
      string pointsJsonString = "[" + wf.POINTS + "]"
      IDArray pointsArray = cast(JSON.parse(pointsJsonString))
       
      singleFlow.setObject("points", pointsArray)
    }
    else 
    {
      IDArray emptyPoints = new()
      singleFlow.setObject("points", emptyPoints)
    }
    storedFlowArray.addObject(singleFlow)
  }
   
  storedFlow = JSON.stringify(storedFlowArray)
   
  return storedFlow
}


// ──────────────────────────────────

// ***************************************************************
// returns the result description of the result linked to the flow
// ***************************************************************
private static string ModelloEvento.resolveFlowResultDescription(
  WkfFlow wf                    // 
  IDCollection steps of WkfStep // 
)
{
  if (wf.IDRESULT <= 0)
    return ""
   
  // first search in steps alreadyin memory
  for each WkfStep ws in steps
  {
    if (ws.IDSTEP == wf.IDSTEP)
    {
      for each WkfResult wr in ws.WKFRESULTS
      {
         if (wr.IDRESULT == wf.IDRESULT)
           return wr.RESULTDESCRIPTION
      }
    }
  }
  WkfResult fallbackResult = new()
  fallbackResult.IDRESULT = wf.IDRESULT
  try 
  {
    fallbackResult.loadFromDB(...)
    return fallbackResult.RESULTDESCRIPTION
  }
  catch 
  {
    QappCore.DTTLogMessage(formatMessage("Il result con id |1 non è presente sul db", wf.IDRESULT, ...), ...)
    return ""
  }
}


// ──────────────────────────────────



// ──────────────────────────────────

// ******************
// frocefully reloads
// ******************
public throws exception boolean ModelloEvento.migrateDiagramFromXml()
{
  boolean success = true
   
  if (CUSTOMEXPORTEDDIAGRAMXML != null)
  {
    this.applyDiagramPositionsFromXml()
     
    try 
    {
       
      // since conversion done we clear the XML field
      CUSTOMEXPORTEDDIAGRAMXML = null
      this.saveToDB(...)
    }
    catch 
    {
      QappCore.DTTLogMessage("error in Invoke Xml converter", ..., DTTError)
      success = false
      throw 0, "reloadDiagramFromXml failed for id " + toString(IDTEMPLATEEVENTO)
    }
  }
  else 
  {
    QappCore.DTTLogMessage("no XML in DB, impossible to reload", ..., DTTError)
    success = false
  }
   
   
  return success
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void ModelloEvento.deleteWorkflowCollections()
{
  this.loadWorkflowCollections()
//   
  for each WkfStep ws in WKFSTEPS
  {
    for each WkfResult wr in ws.WKFRESULTS
    {
      IdCollectionTools.deleteCollectionFromDb(wr, wr.CdataWkfResultLinks)
    }
    IdCollectionTools.deleteCollectionFromDb(ws, ws.WKFRESULTS)
    IdCollectionTools.deleteCollectionFromDb(ws, ws.NTFRECIPIENTS)
    IdCollectionTools.deleteCollectionFromDb(ws, ws.Disposizioni)
    IdCollectionTools.deleteCollectionFromDb(ws, ws.MultipleStepTemplateDisposizioni)
  }
   
  IdCollectionTools.deleteCollectionFromDb(this, WKFSTEPS)
  IdCollectionTools.deleteCollectionFromDb(this, WKFFLOW)
//   
  Diagram = null
}


// ──────────────────────────────────

// **********************************************************************************************************
// Check if Document link already exists in collegati collection
// this method is require to validate/check when we create Document link to prevent Duplicate error on saving
// **********************************************************************************************************
public boolean ModelloEvento.DocumentLinkAlreadyExists(
  Documento document // 
)
{
  boolean sameDocumentLinkAlreadyExists = false
   
  for each DocCollegato dc in DocCollegati
  {
    if (dc.IdDocumento == document.IDDOCUMENTO)
    {
      sameDocumentLinkAlreadyExists = true
      break 
    }
  }
   
  return sameDocumentLinkAlreadyExists
}


// ──────────────────────────────────

// *****************************************************************************************************************
// create a new Link document for given document and Idrevision and add in to collection of Interventi doc collegati
// *****************************************************************************************************************
public void ModelloEvento.AddDocCollegati(
  Documento Document // 
)
{
  DocCollegato dc = DocCollegato.create(ModelliEventi, IDTEMPLATEEVENTO, Document.IDDOCUMENTO, Document.LastNroRevsion, Document.NOTE)
  DocCollegati.add(dc)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public IDCollection ModelloEvento.getLinkedEventi()
{
  IDCollection linkedEventi of Evento = new()
  for each row (readonly)
  {
    select
      idEvento = IDEVENTO
    from 
      Eventi // master table
    where
      IDTEMPLATEEVENTO == IDTEMPLATEEVENTO
     
    Evento e = Evento.getFromDB(idEvento, quickLoad)
    if (e and e.loaded)
    {
      linkedEventi.add(e)
    }
  }
  return linkedEventi
}


// ──────────────────────────────────

// ****************************************************************************************************************************************************************************************************************
// given a classe role (immissione, visibilità, ...), a user and a search string modelli evento user can see are returned, in case serach string is empty limit of 20 results is appleid (note: this is done to
// simplify UI implementation)
// ****************************************************************************************************************************************************************************************************************
public static IDCollection ModelloEvento.getSearchedModelliBasedOnRoles(
  string:eventiClassesRolesTypes role // 
  Utente utente                       // 
  optional string searchString = ""   // 
)
{
  string searchQuery = "%" + nullValue(searchString, "") + "%"
   
  IDCollection availableClasses of ClasseEvento = ClasseEvento.getVisibleClassesBasedOnUserRole(utente, role)
   
   
  IDCollection availableModelliEvento of ModelloEvento = new()
  for each ClasseEvento ce in availableClasses
  {
    boolean exitFromOuterLoop = false
    for each row (readonly)
    {
      select
         idModello = ModelliEvento.IDTEMPLATEEVENTO
      from 
         ModelliEvento                 // master table
         EVACLASSIEVENTO               // joined with Modelli Evento using key FK_EVA_TEMPL_TESTATA01
         outer join EVAAMBITIEVENTO    // joined with Modelli Evento using key FK_EVA_TEMPL_TESTATA02
         outer join EVATIPOLOGIEEVENTO // joined with Modelli Evento using key FK_EVA_TEMPL_TESTATA03
      where
         ModelliEvento.ATTIVO == Yes
         ModelliEvento.IDCLASSE == ce.IDCLASSE and (searchString == "" or (ModelliEvento.DESCRTEMPLATE like searchQuery) or (EVAAMBITIEVENTO.DESCRAMBITO like searchQuery) or (ModelliEvento.
           DESCRTITOLO like searchQuery) or (EVACLASSIEVENTO.DESCRCLASSE like searchQuery) or (EVATIPOLOGIEEVENTO.DESCRTIPOLOGIA like searchQuery))
       
       
      ModelloEvento modello = ModelloEvento.getFromDB(idModello, quickLoad)
      availableModelliEvento.add(modello)
       
      if (searchString == "")
      {
         QappCore.DTTLogMessage("No search String passed, only 20 records will be returned", ..., DTTInfo)
         if (availableModelliEvento.count() == 20)
         {
            
           // to exit from both the for each we break the inner one and set a boolean flag so the outer...
           exitFromOuterLoop = true
           break 
         }
      }
    }
    if (exitFromOuterLoop)
    {
       
      // ...can also break
      break 
    }
  }
   
  return availableModelliEvento
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public Recordset ModelloEvento.getSmartLookupForResponsabile(
  string queryString // 
)
{
   
  Recordset users = new()
   
  if (ISFUNCTION == No)
  {
    select into recordset (users)
      IDUTENTE as IDUTENTE
      DESCRUTENTE as DESCRUTENTE
    from 
      Utenti // master table
    where
      DESCRUTENTE like "%" + queryString + "%"
     
    return users
     
  }
  else 
  {
    Recordset funzioni = new()
    select into recordset (funzioni)
      IDFUNZIONE as IDUTENTE
      CODFUNZIONE as DESCRUTENTE
    from 
      Funzioni // master table
     
    return funzioni
  }
   
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean ModelloEvento.anyOneDisposizioneExists()
{
  boolean anyOneDisposizioneExists = false
  if (!(DisposizioniModello.loaded))
  {
    this.loadDisposizioniModello(...)
  }
  anyOneDisposizioneExists = DisposizioniModello.count() > 0
  return anyOneDisposizioneExists
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public IDMap ModelloEvento.getAvailableEsiti()
{
  IDMap esiti = new()
  for each row (readonly)
  {
    select
      IdGravita = EventiEsitoClasseLinks.IDGRAVITA
      descrGravita = EventoEsiti.DESCRGRAVITA
    from 
      EventiEsitoClasseLinks // master table
      EventoEsiti            // joined with Eventi Esito Classe Links using key FK_EVA_GRAVITA_CLASSI02
    where
      EventiEsitoClasseLinks.IDCLASSE = IDCLASSE
     
    esiti.setValue(IdGravita, descrGravita)
  }
  return esiti
}


// ──────────────────────────────────

// *****************************************************************************************
// This method find if MandatoryOnClosure is selected for any customdata in all the sections
// *****************************************************************************************
public boolean ModelloEvento.hasCustomDataWithMandatoryOnClosureFlag()
{
  boolean mandatoryClosureFlagFound = false
  IDCollection cdSections of CdataSection = this.retrieveAvailableCDSections()
  for each CdataSection cs in cdSections
  {
    for each MainModuleDatoPersonalizzatoInfo cfi in cs.CDATAFIELDSINFO
    {
      if (cfi.MandatoryOnClosure == Yes)
      {
         mandatoryClosureFlagFound = true
         break 
      }
    }
  }
  return mandatoryClosureFlagFound
}


// ──────────────────────────────────

// **************************************************************************
// Loads in memory all Model collections.
// This is useful when we are getting ready to create an Event from the Model
// **************************************************************************
public void ModelloEvento.loadAllCollections()
{
  // load riferimenti
  this.loadCollectionFromDB(Riferimenti, ...)
   
  // load riferimenti opzioni
  this.loadModelloEventoRiferimenti(...)
   
  // load Modello Evento CD Sections
  this.loadModelloEventoCDSections(...)
   
  // load modello Documenti Collegati
  base.loadDocCollegati()
   
  // load all modello evento disposizioni collections
  this.loadDisposizioniModello(...)
   
  // load wkf related things
  this.loadWorkflowCollections()
   
  // load all modello evento cost collections
  this.loadModelloEventoCosts(...)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void ModelloEvento.loadModelloEventoCDSections(
  optional boolean forceReloadFromDb = 1 // 
)
{
  if (forceReloadFromDb)
  {
    ModelloEventoCDSections.clear()
    ModelloEventoCDSections.loaded = false
  }
   
  IDCollection templateCDSections of ModelloEventoCDSection = new()
  IDMap alreadyInsertedCDSections = new()
  for each row (readonly)
  {
    select
      idTemplateCDSections = ModelloEventoSezioniDatiPersonalizzati.IDEVATEMPLCDATASECTION
    from 
      ModelloEventoSezioniDatiPersonalizzati // master table
      CDATASECTIONS                          // joined with Modello Evento Sezioni Dati Personalizzati using key FK_EVA_TEMPL_CDATA_SECTIONS_CDATA_SECTIONS
    where
      CDATASECTIONS.Active == Yes
      ModelloEventoSezioniDatiPersonalizzati.IDTEMPLATEEVENTO == IDTEMPLATEEVENTO
     
    ModelloEventoCDSection mesdp = ModelloEventoCDSection.get(idTemplateCDSections)
    alreadyInsertedCDSections.setValue(mesdp.IDCDATASEC, true)
    templateCDSections.add(mesdp)
  }
   
  for each row (readonly)
  {
    select
      idCDSection = CDATASECTIONS.IDCDATASEC
    from 
      CDATASECTIONS   // master table
      CDATAMODULETYPE // joined with CDATA SECTIONS using key FK_CDATA_MODULE_TYPE_ID_CDATA_SEC
    where
      CDATASECTIONS.KordApp == Eventi
      CDATASECTIONS.Active == Yes
      CDATAMODULETYPE.IDTIPO == IDCLASSE
     
    if (!(alreadyInsertedCDSections.containsKey(idCDSection)))
    {
      CdataSection cs = CdataSection.get(idCDSection)
      ModelloEventoCDSection mecds = ModelloEventoCDSection.create(cs, this)
      templateCDSections.add(mecds)
    }
  }
   
  // sort the template CD Section on Sequence and Name
  ModelloEventoCDSection modelloEventocdSection = new()
  int seqIndex = modelloEventocdSection.getPropertyIndex("Sequence", true, true, true, true)
  int nameIndex = modelloEventocdSection.getPropertyIndex("Sectionname", true, true, true, true)
  templateCDSections.addSortCriteria(seqIndex)
  templateCDSections.addSortCriteria(nameIndex)
  templateCDSections.doSort()
   
  ModelloEventoCDSections.addAll(templateCDSections, true)
   
  if (!(inserted))
  {
    ModelloEventoCDSections.setOriginal()
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void ModelloEvento.loadModelloEventoRiferimenti(
  optional boolean forceReloadFromDB = 1 // 
)
{
  if (forceReloadFromDB)
  {
    ModelloEventoRiferimenti.loaded = false
  }
   
  this.loadCollectionFromDB(ModelloEventoRiferimenti, ...)
}


// ──────────────────────────────────

// ****************************************************************
// This method load all workflow related collection.
// Is needed by Evento to create disposizioni and many other things
// ****************************************************************
public void ModelloEvento.loadWorkflowCollections()
{
  // load all modello evento workflow Step collections
  if (!(WKFSTEPS.loaded))
  {
    this.loadWKFSTEPS(..., 0)
  }
   
  // load all modello evento workflow-flow collections
  if (!(WKFFLOW.loaded))
  {
    this.loadWKFFLOW(..., 0)
  }
   
  // load all modello evento workflow results collections
  if (!(WKFRESULTS.loaded))
  {
    this.loadWKFRESULTS(..., 0)
  }
   
  if (!(MultipleStepTemplateDisposizioneCollection.loaded))
  {
    this.loadMultipleStepDisposizioniCollection(...)
  }
}


// ──────────────────────────────────

// ***********************************************************************
// this procedure loads Modello Evento Disposizione from Db and sorts it. 
// Then it sets the property Disposizioni Modello 
// ***********************************************************************
public void ModelloEvento.loadDisposizioniModello(
  optional boolean forceReloadFromDb = 1 // Write a comment for this parameter or press backspace to delete this comment
)
{
  if (forceReloadFromDb)
  {
    DisposizioniModello.loaded = false
  }
   
  this.loadCollectionFromDB(DisposizioniModello, ...)
   
  ModelloEventoDisposizione med = new()
  int nroRigaIndex = med.getPropertyIndex("NRORIGA", true, true, true, true)
  DisposizioniModello.addSortCriteria(nroRigaIndex)
  DisposizioniModello.doSort()
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void ModelloEvento.loadModelloEventoCosts(
  optional boolean forceReloadFromDb = 1 // Write a comment for this parameter or press backspace to delete this comment
)
{
   
  if (forceReloadFromDb)
  {
    Costi.loaded = false
    Costi.clear()
  }
   
  this.loadCollectionFromDB(Costi, ...)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void ModelloEvento.loadModelloEventoAnalisi(
  optional boolean forceReloadFromDb = 1 // Write a comment for this parameter or press backspace to delete this comment
)
{
  if (forceReloadFromDb)
  {
    ModelloEventoAnalisi.loaded = false
  }
   
  this.loadCollectionFromDB(ModelloEventoAnalisi, ...)
//   
  ModelloEventoAnalisi mea = new()
  int nroRigaIndex = mea.getPropertyIndex("NRORIGA", true, true, true, true)
   
  ModelloEventoAnalisi.addSortCriteria(nroRigaIndex)
  ModelloEventoAnalisi.doSort()
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void ModelloEvento.loadWKFFLOW(
  optional boolean forceReloadFromDb = 1 // Write a comment for this parameter or press backspace to delete this comment
  optional int childrenLevel = 9999      // Write a comment for this parameter or press backspace to delete this comment
)
{
  if (forceReloadFromDb)
  {
    WKFFLOW.loaded = false
  }
  this.loadCollectionFromDB(WKFFLOW, childrenLevel)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void ModelloEvento.loadWKFSTEPS(
  optional boolean forceReloadFromDb = 1 // Write a comment for this parameter or press backspace to delete this comment
  optional int childrenLevel = 9999      // Write a comment for this parameter or press backspace to delete this comment
)
{
  if (forceReloadFromDb)
  {
    WKFSTEPS.loaded = false
  }
  this.loadCollectionFromDB(WKFSTEPS, childrenLevel)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void ModelloEvento.loadWKFRESULTS(
  optional boolean forceReloadFromDb = 1 // Write a comment for this parameter or press backspace to delete this comment
  optional int childrenLevel = 9999      // Write a comment for this parameter or press backspace to delete this comment
)
{
  if (forceReloadFromDb)
  {
    WKFRESULTS.loaded = false
  }
  this.loadCollectionFromDB(WKFRESULTS, childrenLevel)
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public void ModelloEvento.loadMultipleStepDisposizioniCollection(
  optional boolean forceReloadFromDb = 1 // Write a comment for this parameter or press backspace to delete this comment
  optional int childrenLevel = 9999      // Write a comment for this parameter or press backspace to delete this comment
)
{
  if (forceReloadFromDb)
  {
    MultipleStepTemplateDisposizioneCollection.loaded = false
  }
  this.loadCollectionFromDB(MultipleStepTemplateDisposizioneCollection, childrenLevel)
   
  if (!(inserted))
  {
    MultipleStepTemplateDisposizioneCollection.setOriginal()
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean ModelloEvento.IsWKFFLOWCollectionAlreadyLoaded()
{
  return WKFFLOW.loaded
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean ModelloEvento.IsWKFSTEPSCollectionAlreadyLoaded()
{
  return WKFSTEPS.loaded
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean ModelloEvento.IsWKFRESULTSCollectionAlreadyLoaded()
{
  return WKFRESULTS.loaded
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean ModelloEvento.isMultipleStepsDisposizioniCollectionAlreadyLoaded()
{
  return MultipleStepTemplateDisposizioneCollection.loaded
}


// ──────────────────────────────────

// ***********************************************************
// Adds a WkfStep to WKFSTEPS. For unit testing purposes only.
// ***********************************************************
public void ModelloEvento.addWkfStepForUnitTest(
  WkfStep step // 
)
{
  WKFSTEPS.add(step)
}


// ──────────────────────────────────

// ********************************************************************
// Returns private WKFSTEPS collection. For unit testing purposes only.
// ********************************************************************
public IDCollection ModelloEvento.getWkfStepsForUnitTest()
{
  return WKFSTEPS
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static ModelloEvento ModelloEvento.duplicateModelloEvento(
  ModelloEvento sourceModelloEvento // 
)
{
  ClasseEvento ce = ClasseEvento.get(sourceModelloEvento.IDCLASSE)
  ModelloEvento me = ModelloEvento.create(ce, sourceModelloEvento.WORKFLOWTYPE)
   
  // duplicate only the main object of modello evento, so the main properties
  me.copyMainPropertiesFromSource(sourceModelloEvento)
   
  // duplicate additional properties without collections
  me.copyAdditionalPropertiesFromSource(sourceModelloEvento)
   
  // duplicate additional collections
  me.copyAdditionalCollectionsFromSource(sourceModelloEvento)
   
  // duplicate main module collection
  me.copyMainModuleDataFromSource(sourceModelloEvento, true, true)
   
  // if the source modello evento is workflow we duplicate also the specific WKF collections (results, steps, flows ecc..)
  if (sourceModelloEvento.isWorkflowModelloEvento())
  {
    me.copyWorkflowCollectionsFromSource(sourceModelloEvento)
  }
   
  return me
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void ModelloEvento.copyMainPropertiesFromSource(
  ModelloEvento sourceModelloEvento // 
)
{
  DESCRTEMPLATE = sourceModelloEvento.DESCRTEMPLATE + " (Copia)"
  IDCLASSE = sourceModelloEvento.IDCLASSE
  IDAMBITO = sourceModelloEvento.IDAMBITO
  IDTIPOLOGIA = sourceModelloEvento.IDTIPOLOGIA
  IDUTENTERESP = sourceModelloEvento.IDUTENTERESP
  DESCREVENTO = sourceModelloEvento.DESCREVENTO
  COSTOFISSO = sourceModelloEvento.COSTOFISSO
  ATTIVO = sourceModelloEvento.ATTIVO
  INSTRUCTIONS = sourceModelloEvento.INSTRUCTIONS
  DESCRTITOLO = sourceModelloEvento.DESCRTITOLO
  ISFUNCTION = sourceModelloEvento.ISFUNCTION
  IDTEMPLATE = sourceModelloEvento.IDTEMPLATE
  TEMPLATE = sourceModelloEvento.TEMPLATE
  IDPARENTTEMPLATEREMOTE = sourceModelloEvento.IDPARENTTEMPLATEREMOTE
  WORKFLOWTYPE = sourceModelloEvento.WORKFLOWTYPE
  WKFDIAGRAM = sourceModelloEvento.WKFDIAGRAM
  STARTFROMQMOBILE = sourceModelloEvento.STARTFROMQMOBILE
  LINKREVISION = sourceModelloEvento.LINKREVISION
  ALLOWQUICKSTART = sourceModelloEvento.ALLOWQUICKSTART
  SUPPORTPUBLICINFO = sourceModelloEvento.SUPPORTPUBLICINFO
  INSTRUCTIONSHTML = sourceModelloEvento.INSTRUCTIONSHTML
  DESCREVENTOHTML = sourceModelloEvento.DESCREVENTOHTML
  CUSTOMEXPORTEDDIAGRAMXML = sourceModelloEvento.CUSTOMEXPORTEDDIAGRAMXML
  DIAGRAMJSON = sourceModelloEvento.DIAGRAMJSON
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void ModelloEvento.copyAdditionalPropertiesFromSource(
  ModelloEvento sourceModelloEvento // 
)
{
  // modello evento ones
  MultiSourceResponsible = sourceModelloEvento.MultiSourceResponsible
  ModelloEventoReponsibleIDForDecodingInUI = sourceModelloEvento.ModelloEventoReponsibleIDForDecodingInUI
   
  Diagram = sourceModelloEvento.GetDiagram()
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void ModelloEvento.copyAdditionalCollectionsFromSource(
  ModelloEvento sourceModelloEvento // 
)
{
  // riferimenti opzioni
  if (!(sourceModelloEvento.ModelloEventoRiferimenti.loaded))
  {
    sourceModelloEvento.loadCollectionFromDB(sourceModelloEvento.ModelloEventoRiferimenti, ...)
  }
   
  ModelloEventoRiferimenti.clear()
   
  for each ModelloEventoRiferimento sourceRiferimenti in sourceModelloEvento.ModelloEventoRiferimenti
  {
    ReferenceType refType = ReferenceType.get(sourceRiferimenti.IDTipoRiferimento)
    ModelloEventoRiferimento newlyCreatedModelloEventoRiferimento = ModelloEventoRiferimento.create(this, refType, sourceRiferimenti.MANDATORYROWS, sourceRiferimenti.PREVIEWONCALENDAR)
     
    ModelloEventoRiferimenti.add(newlyCreatedModelloEventoRiferimento)
  }
   
  ModelloEventoRiferimenti.loaded = true
   
  // opzioni sections
  if (!(sourceModelloEvento.ModelloEventoCDSections.loaded))
  {
    sourceModelloEvento.loadCollectionFromDB(sourceModelloEvento.ModelloEventoCDSections, ...)
  }
  ModelloEventoCDSections.clear()
   
  for each ModelloEventoCDSection sourceModelloEventoCDSection in sourceModelloEvento.ModelloEventoCDSections
  {
    CdataSection sourceCdSection = CdataSection.get(sourceModelloEventoCDSection.IDCDATASEC)
    ModelloEventoCDSection mecds = ModelloEventoCDSection.create(sourceCdSection, this)
    mecds.VISIBLE = sourceModelloEventoCDSection.VISIBLE
     
    ModelloEventoCDSections.add(mecds)
  }
   
  ModelloEventoCDSections.loaded = true
   
  // costi
  if (!(sourceModelloEvento.Costi.loaded))
  {
    sourceModelloEvento.loadCollectionFromDB(sourceModelloEvento.Costi, ...)
  }
  Costi.clear()
   
  for each EventiCosto sourceCosto in sourceModelloEvento.Costi
  {
    EventiCosto ec = EventiCosto.create(..., this)
    ec.ORE = sourceCosto.ORE
    ec.PREZZO = sourceCosto.PREZZO
    ec.Importo = sourceCosto.Importo
    ec.NOTE = sourceCosto.NOTE
    ec.IDTIPOCOSTO = sourceCosto.IDTIPOCOSTO
     
    Costi.add(ec)
  }
   
  // disposizioni
  if (!(this.isWorkflowModelloEvento()))
  {
    if (!(sourceModelloEvento.DisposizioniModello.loaded))
    {
      sourceModelloEvento.loadCollectionFromDB(sourceModelloEvento.DisposizioniModello, ...)
    }
     
    DisposizioniModello.clear()
     
    for each ModelloEventoDisposizione sourceDisposizione in sourceModelloEvento.DisposizioniModello
    {
      ModelloEventoDisposizione med = ModelloEventoDisposizione.create(this)
      med.duplicateModelloEventoDisposizione(sourceDisposizione)
       
      DisposizioniModello.add(med)
    }
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void ModelloEvento.copyWorkflowCollectionsFromSource(
  ModelloEvento sourceModelloEvento // 
)
{
  if (!(sourceModelloEvento.IsWKFRESULTSCollectionAlreadyLoaded()))
  {
    sourceModelloEvento.loadWKFRESULTS(true, ...)
  }
  WKFRESULTS.clear()
  WKFRESULTS = sourceModelloEvento.getWkfResults()
   
  if (!(sourceModelloEvento.IsWKFFLOWCollectionAlreadyLoaded()))
  {
    sourceModelloEvento.loadWKFFLOW(true, ...)
  }
  WKFFLOW.clear()
  WKFFLOW = sourceModelloEvento.getWkfFlows()
   
  if (!(sourceModelloEvento.IsWKFSTEPSCollectionAlreadyLoaded()))
  {
    sourceModelloEvento.loadWKFSTEPS(true, ...)
  }
  WKFSTEPS.clear()
  WKFSTEPS = sourceModelloEvento.getWkfSteps()
   
  if (!(sourceModelloEvento.isMultipleStepsDisposizioniCollectionAlreadyLoaded()))
  {
    sourceModelloEvento.loadMultipleStepDisposizioniCollection(true, ...)
  }
  MultipleStepTemplateDisposizioneCollection.clear()
  MultipleStepTemplateDisposizioneCollection = sourceModelloEvento.getWkfDisposizioni()
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static ModelloEvento ModelloEvento.create(
  ClasseEvento classeEvento                        // 
  optional string:eventiBehaviours behaviour = "N" // 
)
{
  ModelloEvento me = new()
  me.init()
  me.WORKFLOWTYPE = behaviour
  me.IDCLASSE = classeEvento.IDCLASSE
   
  // creation of links between modello evento and custom data sections
  me.createSezioniDatiPersonalizzatiLinkedToModello()
   
  return me
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string ModelloEvento.getCaption(
  string additionalDescription // 
)
{
  return "Modelli evento " + "{{icon-fa-chevron-right}}"
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean ModelloEvento.isClosed()
{
  return ATTIVO == No
}


// ──────────────────────────────────

// ******************************************************************************************************************************
// Classifies the ModelloEvento's responsabile configuration for evento creation.
// No UI, no mutation — caller decides what to do based on Kind.
// 
// considering following cases 
// Case 1  — FunzioneNoActiveMember          ->block with error, no evento created 
// Case 2  — NotDefined                      -> open ambito picker (silent) 
// Case 3a — UtenteDirect                    -> proceed silently with modello's utente 
// Case 3b — UtenteDirectInactive            -> message "utente non attivo" + ambito picker 
// Case 3c — UtenteDirectNotLinked           -> message "utente non collegato a dipendente" + ambito picker 
// Case 3d — UtenteDirectNoAmbitoVisibility  -> message "utente non ha visibilità sull'ambito" + ambito picker 
// Case 4  — FunzioneOneMember               -> proceed silently with the one usable member (priority shortcut or single active) 
// Case 5  — FunzioneManyMembers             -> open function picker (members of that funzione only) 
// Case 6  — FunzioneScopeBasedOverride      -> open ambito picker (function ignored; scope-flag is on)
// 
// 
// considerPriorityMember parameter is alsways false when this method is been called from UI path, this is kept
// for future use when we will create evento based on checklist esito
// ******************************************************************************************************************************
public ModelloeventoResponsabileResolution ModelloEvento.resolveResponsabile(
  int idAmbitoEvento                                            // 
  optional boolean considerPriorityMember = 0                   // 
  optional boolean considerForcefullyAmbitoVisibiltyForTest = 0 // 
)
{
  ModelloeventoResponsabileResolution r = new()
   
   
  // Case 2: modello has no responsabile configured at all
  if (isNull(IDUTENTERESP) or IDUTENTERESP == 0)
  {
    r.Kind = ReponsibleNotDefined
    return r
  }
   
  if (ISFUNCTION == Yes)
  {
    r.IdFunzione = IDUTENTERESP
     
    // case 6 scope-based override — function is IGNORED, picker shows ambito-visible utenti
    TabParametri tp = TabParametri.getInstance()
    if (tp.ENABLESCOPEBASEDUSERVISIBILITY == Yes or considerForcefullyAmbitoVisibiltyForTest)
    {
      r.Kind = OverrideScopeBasedUserOnFunzione
      return r
    }
    Funzione f = Funzione.getFromDB(IDUTENTERESP, quickLoad)
     
    if (considerPriorityMember)
    {
      // case 4 via priority shortcut: if a usable priority member exists, that IS the responsabile.
      Utente priorityUtente = f.getPriorityMemberAsUtenti()
      if (priorityUtente != null)
      {
         r.ResolvedIdUtente = priorityUtente.IDUTENTE
         r.PriorityFilterApplied = true
         r.ActiveMembersCount = 1
         r.Kind = FunzioneHasOneMember
         return r
      }
    }
     
     
     
    // no priority member -> resolve from all active members
    IDCollection allUtentiOfFunction of Utente = f.getActiveMembersAsUtenti()
    r.ActiveMembersCount = allUtentiOfFunction.count()
     
    // case 1
    if (allUtentiOfFunction.count() == 0)
    {
      r.Kind = FunzioneNoActiveMember
      return r
    }
     
    // case 4 (single active member, no priority)
    if (allUtentiOfFunction.count() == 1)
    {
      allUtentiOfFunction.moveFirst()
      Utente oneUtenteInfunction = (Utente)allUtentiOfFunction.getAt()
      r.ResolvedIdUtente = oneUtenteInfunction.IDUTENTE
      r.Kind = FunzioneHasOneMember
      return r
    }
     
    // case 5
    r.Kind = FunzioneHasManyMembers
    return r
  }
   
  Utente utente = Utente.get(IDUTENTERESP)
   
  // case 3b : inactive utente
  if (utente == null or utente.ATTIVO != Yes)
  {
    r.Kind = UtenteIsInactive
    return r
  }
   
  // case 3c: utente has no linked dipendente
  Personale linkedPersonale = utente.getLinkedPersonale()
  if (linkedPersonale == null or linkedPersonale.IDDIPENDENTE <= 0)
  {
    r.Kind = UtenteIsNotLinkedWithPersonale
    return r
  }
   
   
  // case 3d: utente has no visibility on the evento's ambito
  if (!(utente.canSeeAmbito(idAmbitoEvento)))
  {
    r.Kind = UtenteHasNoAmbitoVisibility
    return r
  }
   
  // case 3a: valid
  r.ResolvedIdUtente = IDUTENTERESP
  r.Kind = UtenteIsValid
  return r
   
}


// ──────────────────────────────────

// ***************************************
// Descrivi lo scopo di questa funzione...
// ***************************************
public int ModelloEvento.getMainID()
{
  return IDTEMPLATEEVENTO
}


// ──────────────────────────────────

// ***************************************
// Descrivi lo scopo di questa funzione...
// ***************************************
public int ModelloEvento.getTypeID()
{
  return IDCLASSE
}


// ──────────────────────────────────

// ***************************************
// Descrivi lo scopo di questa funzione...
// ***************************************
public int ModelloEvento.getKordApp()
{
  return ModelliEventi
}


// ──────────────────────────────────

// ****************************************
// Describe the purpose of this function...
// ****************************************
public string ModelloEvento.getDescription()
{
  return DESCRTEMPLATE
}


// ──────────────────────────────────

// ****************************************
// Describe the purpose of this function...
// ****************************************
public void ModelloEvento.UpdateResponsiblePropertiesEndTransactionHandler()
{
  if (MultiSourceResponsible.MultiSourceResponsibleInterface)
  {
    if (ModelloEventoReponsibleIDForDecodingInUI != null and ModelloEventoReponsibleIDForDecodingInUI != "")
    {
      string:flagYN isFunctionValue = null
      int idResponsibleValue = null
       
      MultiSourceResponsible.decodeKey(ModelloEventoReponsibleIDForDecodingInUI, isFunctionValue, idResponsibleValue)
       
      ISFUNCTION = isFunctionValue
      IDUTENTERESP = idResponsibleValue
    }
    else 
    {
      ISFUNCTION = No
      IDUTENTERESP = null
    }
  }
   
   
}


// ──────────────────────────────────

// **************************************************************************************************************************************************************************
// this method must set the string (such as F103) used for decoding in panel, starting from the DB fields (such as IS_FUNCTION and ID_UTENTE RESP, in case of modello evento)
// **************************************************************************************************************************************************************************
public void ModelloEvento.MultiSourceResponsibleCreationHandler()
{
  string computedId = MultiSourceResponsible.prepareDecodingKey(ISFUNCTION, IDUTENTERESP, "U")
  ModelloEventoReponsibleIDForDecodingInUI = computedId
  MultiSourceResponsible.ID = computedId
}


// ──────────────────────────────────

// ****************************************************************************************************************************************************************************************************************
// this method is intentionally put in the interface to remember the developer that in the after load of the class that implments IMultiSourceResponsible it is mandatory to instantiate a multiSourceResponsbile
// object
// so in this object a line of code like
// multiSourceResponsible = multiSourceResponsible.create(this, funzioneOrDipendente) must be written
// ****************************************************************************************************************************************************************************************************************
public void ModelloEvento.AfterLoadMultiSourceResponsibleCreation()
{
  MultiSourceResponsible = MultiSourceResponsible.create(this, funzioneOrUtente)
}


// ──────────────────────────────────

// *******************************************
// get Tipologia object for given ID_Tipologia
// *******************************************
public static TipologiaEvento TipologiaEvento.GetTipologia(
  int IdTipologia // 
)
{
  TipologiaEvento ete = new()
  ete.IDTIPOLOGIA = IdTipologia
  try 
  {
    ete.loadFromDB(...)
  }
  return ete
}


// ──────────────────────────────────

// ********************************************
// get tipologia description given ID_Tipologia
// ********************************************
public static string TipologiaEvento.GetTipologiaDescription(
  int IdTipologia // 
)
{
  TipologiaEvento ete = TipologiaEvento.GetTipologia(IdTipologia)
   
  string description = ""
   
  if (ete)
  {
    description = ete.DESCRTIPOLOGIA
  }
   
  return description
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static TipologiaEvento TipologiaEvento.create(
  ClasseEvento classeEvento // 
  string code               // 
  string description        // 
)
{
  TipologiaEvento te = new()
  te.init()
  te.IDCLASSE = classeEvento.IDCLASSE
  te.CODTIPOLOGIA = code
  te.DESCRTIPOLOGIA = description
   
  return te
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static int TipologiaEvento.getTipologiaCountForClass(
  int idClasse // 
)
{
  int tipologiaCount = 0
  select into variables (found variable)
    set tipologiaCount = count(IDTIPOLOGIA)
  from 
    EVATIPOLOGIEEVENTO // master table
  where
    ATTIVA == Yes
    IDCLASSE == idClasse
   
  return tipologiaCount
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event TipologiaEvento.OnInit()
{
  IDTIPOLOGIA = Sequence.getNextSequence(EVAN_ID_TABELLA, ...)
  MESSAGGIO = No
  SEQUENZA = 1
  ATTIVA = Yes
  COSTOOBBLIGATORIO = Yes
  ATTIVITAOBBLIGATORIE = Yes
  ANALISIOBBLIGATORIA = No
  SENDMSG = No
   
}


// ──────────────────────────────────

// **************************************************************************
// Event called to customize the Smart Lookup procedure at the document level
// **************************************************************************
event TipologiaEvento.OnGetSmartLookup(
  Recordset RecordSet       // The recordset to be filled if the queries run during the Smart Lookup event are customized.
  inout int Level           // The Smart Lookup procedure performs multiple attempts, increasingly widening the search field. This integer parameter specifies the number of attempts made.
  inout boolean NullValue   // A boolean output parameter. If set to True, then NULL (no results found) will be returned to the panel.
  inout boolean Skip        // A boolean output parameter. If set to True, then the standard queries for the current level will not be executed.
  inout boolean Cancel      // A boolean output parameter. If set to True, the Smart Lookup procedure will be stopped.
  IDDocument CallerDocument // The document for which the smart lookup procedure is launched. It can be null.
)
{
  Skip = true
   
  boolean eventoISCallerDocument = Evento.isMyInstance(CallerDocument)
   
  if (eventoISCallerDocument)
  {
    Evento e = cast(CallerDocument)
     
    string queryString = nullValue(DESCRTIPOLOGIA, "")
    if (queryString == "*")
      queryString = ""
     
    Recordset rs = new()
    select into recordset (rs)
      IDTIPOLOGIA as IDTIPOLOGIA
      DESCRTIPOLOGIA as DESCRTIPOLOG
      SEQUENZA as SEQUENZA
    from 
      EVATIPOLOGIEEVENTO // master table
    where
      ATTIVA == Yes
      DESCRTIPOLOGIA like "%" + queryString + "%"
      IDCLASSE == e.IDCLASSE
    order by
      SEQUENZA
      DESCRTIPOLOGIA
     
    RecordSet.copyFrom(rs)
  }
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event ContatoreEvento.OnInit()
{
   
  // we do not want to always set to 0 since in retrieve() we call init after having already computed COUNTER so we do not want to lose the information
  if (COUNTER <= 0)
  {
    COUNTER = 0
  }
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
private void ContatoreEvento.CreateNewZeroCounterForAmbito(
  int idClasse // 
  int idAmbito // 
)
{
  ContatoreEvento ce = new()
  ce.init()
  ce.IDCLASSE = idClasse
  ce.IDAMBITO = idAmbito
  ce.COUNTER = 1
  ce.ANNO = year(today())
  ce.saveToDB(...)
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
private void ContatoreEvento.CreateNewZeroCounterForTipologia(
  int idClasse    // 
  int idTipologia // 
)
{
  ContatoreEvento ce = new()
  ce.init()
  ce.IDCLASSE = idClasse
  ce.IDTIPOLOGIA = idTipologia
  ce.COUNTER = 1
  ce.ANNO = year(today())
  ce.saveToDB(...)
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public int ContatoreEvento.GetCounterForAmbito(
  int idClasse // 
  int idAmbito // 
)
{
  int contatore = 0
  ContatoreEvento ce = new()
  ce.IDCLASSE = idClasse
  ce.IDAMBITO = idAmbito
  ce.ANNO = year(today())
   
  try 
  {
    ce.loadFromDB(...)
  }
  catch 
  {
    QappCore.DTTLogMessage("Row in EVA_COUNTERS not found", ...)
  }
   
  if (ce and (ce.ANNO < year(today())))
  {
    this.CreateNewZeroCounterForAmbito(idClasse, idAmbito)
    contatore = 1
  }
  else if (ce.loaded)
  {
    // if ce was loaded and anno is ok
    // 
    ce.COUNTER = ce.COUNTER + 1
    ce.saveToDB(...)
    contatore = ce.COUNTER
  }
  else 
  {
    // create new zero counter and save in eva_counters
    this.CreateNewZeroCounterForAmbito(idClasse, idAmbito)
    contatore = 1
  }
  return contatore
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public int ContatoreEvento.GetCounterForTipologia(
  int idClasse    // 
  int idTipologia // 
)
{
  int contatore = 0
  ContatoreEvento ce = new()
  ce.IDCLASSE = idClasse
  ce.IDTIPOLOGIA = idTipologia
  ce.ANNO = year(today())
   
  try 
  {
    ce.loadFromDB(...)
  }
  catch 
  {
    QappCore.DTTLogMessage("Row in EVA_COUNTERS not found", ...)
  }
   
  if (ce and (ce.ANNO < year(today())))
  {
    this.CreateNewZeroCounterForTipologia(idClasse, idTipologia)
    contatore = 1
  }
  else if (ce.loaded)
  {
    // if ce was loaded and anno is ok
    // 
    ce.init()
    contatore = ce.COUNTER + 1
    ce.COUNTER = contatore
    ce.saveToDB(...)
  }
  else 
  {
    // create new zero counter and save in eva_counters
    this.CreateNewZeroCounterForTipologia(idClasse, idTipologia)
    contatore = 1
  }
   
   
  // save to db alla fine
   
  return contatore
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static ContatoreEvento ContatoreEvento.retrieve(
  ClasseEvento classeEvento    // 
  optional int IdAmbito = 0    // 
  optional int IdTipologia = 0 // 
  optional int Year = -1       // 
)
{
  ContatoreEvento ec = new()
   
  ec.IDCLASSE = classeEvento.IDCLASSE
   
  switch (classeEvento.OPZIONICODICE)
  {
    case Ambito:
      if (IdAmbito != 0)
      {
         ec.IDAMBITO = IdAmbito
      }
      else 
      {
         QappCore.DTTLogMessage("ID_AMBITO should not be 0", ..., DTTError)
      }
    break
    case Tipologia:
      if (IdTipologia > 0)
      {
         ec.IDTIPOLOGIA = IdTipologia
      }
      else 
      {
         QappCore.DTTLogMessage("ID_TIPOLOGIA should not be 0", ..., DTTError)
      }
    break
  }
   
  ec.ANNO = if(Year == -1, year(today()), Year)
  try 
  {
    ec.loadFromDB(...)
  }
  catch 
  {
    QualibusDB.maxRows = 1
    try 
    {
      ec.loadFromDB(...)
      QappCore.DTTLogMessage("since the loadfromDb with maxrows=1 did not fail it means we have duplicates in the db so we need to delete duplicates and retrieve the counter with the highest COUTNER value", ...
         , DTTWarning)
       
    }
    catch 
    {
      QappCore.DTTLogMessage("Since even the query with maxRows=1 failed it means that a counter did not exist in the db so we need to init and create it", ..., DTTWarning)
    }
     
    int currentMax = this.getMaxCounterValueForDuplicateRecordsAndRemoveDuplicates(Year, classeEvento, IdAmbito, IdTipologia)
    ec.COUNTER = currentMax
     
    // we must call init because in this catch we will create a new counter (because if duplicates are found we delete all, if no duplicates it means counter is missing so we must create it)
    ec.init()
  }
  return ec
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void ContatoreEvento.increase()
{
  COUNTER = COUNTER + 1
}


// ──────────────────────────────────



// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private static int ContatoreEvento.getMaxCounterValueForDuplicateRecordsAndRemoveDuplicates(
  int Anno                     // 
  ClasseEvento classeEvento    // 
  optional int idAmbito = 0    // 
  optional int idTipologia = 0 // 
)
{
   
  // we set idAmbito and tipologia to 0 "in case OPZIONI CODICE are not about them"
  idAmbito = if(classeEvento.OPZIONICODICE == Ambito, idAmbito, 0)
  idTipologia = if(classeEvento.OPZIONICODICE == Tipologia, idTipologia, 0)
   
  int idClasse = classeEvento.IDCLASSE
   
  // retrieve the max coutner for the given set or parameters
  int retrievedValueForAnno = 0
  select into variables (found variable)
    set retrievedValueForAnno = max(COUNTER)
  from 
    EVACOUNTERS // master table
  where
    IDCLASSE == idClasse
    ANNO == Anno
    ((idAmbito == nullValue(IDAMBITO, 0) and idAmbito > 0) or (idTipologia == nullValue(IDTIPOLOGIA, 0) and idTipologia > 0)) or ((idAmbito == 0 and isNull(IDAMBITO)) and (idTipologia == 0
       and isNull(IDTIPOLOGIA)))
   
  delete from EVACOUNTERS
  where
    IDCLASSE == idClasse
    ANNO == Anno
    ((idAmbito == nullValue(IDAMBITO, 0) and idAmbito > 0) or (idTipologia == nullValue(IDTIPOLOGIA, 0) and idTipologia > 0)) or ((idAmbito == 0 and isNull(IDAMBITO)) and (idTipologia == 0
       and isNull(IDTIPOLOGIA)))
   
  int maxinPast = 0
   
  // we search for a max in past only in case in which codanno is false, so we want to read from old records, but if codanno is true we stick to one year only and so it will be zero
  if (retrievedValueForAnno == 0 and classeEvento.CODANNO == No)
  {
    select into variables (found variable)
      set maxinPast = max(COUNTER)
    from 
      EVACOUNTERS // master table
    where
      IDCLASSE == idClasse
      ANNO < Anno
      ((idAmbito == nullValue(IDAMBITO, 0) and idAmbito > 0) or (idTipologia == nullValue(IDTIPOLOGIA, 0) and idTipologia > 0)) or ((idAmbito == 0 and isNull(IDAMBITO)) and (idTipologia == 0
         and isNull(IDTIPOLOGIA)))
     
    // delete other records for same class/ambito/tipologia only in case codanno is false, so we have one only counter as required by c/s
    if (classeEvento.CODANNO == No)
    {
      delete from EVACOUNTERS
      where
         IDCLASSE == idClasse
         ANNO < Anno
         ((idAmbito == nullValue(IDAMBITO, 0) and idAmbito > 0) or (idTipologia == nullValue(IDTIPOLOGIA, 0) and idTipologia > 0)) or ((idAmbito == 0 and isNull(IDAMBITO)) and (idTipologia ==
           0 and isNull(IDTIPOLOGIA)))
    }
  }
   
   
   
  int result = if(retrievedValueForAnno == 0 and classeEvento.CODANNO == No, maxinPast, retrievedValueForAnno)
   
  return result
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event ModelloEventoDisposizione.OnInit()
{
  int nextID = Sequence.getNextSequence(EVAN_ID_ATT_TEMPLATE, ...)
  IDTEMPLATTIVITA = nextID
  STATOCKL = Assente
  GGDAINSEVENTO = 1
   
  // initialize start time 09:00 AM
  StartTime = #09:00:00#
  ORARIOPREVISTA = round(Tools.TimeToFloat(StartTime), 3)
   
  // initialize end time with 09:30 AM
  EndTime = #09:30:00#
  ORARIOFINE = round(Tools.TimeToFloat(EndTime), 3)
   
  float calculatedHours = ORARIOFINE - ORARIOPREVISTA
  ORE = Tools.FloatTimeToHours(calculatedHours)
  ISFUNCTION = No
   
  DevTools.ToBeReviewed("this is a minimalsitic implementation because for Gortani qapp there was thened to crate modelli evento programaticaly in tests, but ORE and checkilst would deserve more methods and 
        less hardcoding")
}


// ──────────────────────────────────

// ************************************************************
// Event raised by the document after loading from the database
// ************************************************************
event ModelloEventoDisposizione.AfterLoad(
  boolean AlreadyLoaded     // A boolean parameter that specifies whether the document was previously loaded.
  IDArray LoadedCollections // IDArray type parameter that contains the collections loaded by the framework. If the value of the Already Loaded parameter is False, the Loaded Collections parameter is NULL.
)
{
  if (!(NTFRECIPIENTS.loaded))
  {
    this.loadCollectionFromDB(NTFRECIPIENTS, 1)
  }
   
  if (!(ChecklistTemplate))
  {
    ChecklistTemplate = Checklist.factory(ModelloEventoDisposizione, this, ...)
  }
   
  this.computeAddittionalProperties()
  this.AfterLoadMultiSourceResponsibleCreation()
  this.setOriginal()
}


// ──────────────────────────────────

// ***********************************************************
// Event raised to the document when the transaction is closed
// ***********************************************************
event ModelloEventoDisposizione.OnEndTransaction()
{
  if (wasModified(ModelloEventoDisposizioniExecutorIDForDecodingInUI))
  {
    if (MultiSourceExecutor.MultiSourceResponsibleInterface)
    {
      MultiSourceExecutor.MultiSourceResponsibleInterface.updateResponsiblePropertiesEndTransactionHandler()
    }
  }
   
  if (wasModified(StartTime) or wasModified(EndTime))
  {
    this.encodeDateAndTimeProperties()
  }
   
  // Initialise all the day fields based on their selection
  if (wasModified(NOTIFYINADVANCE))
  {
    if (NOTIFYINADVANCE == Yes)
      NOTIFYADVANCEDAYS = 1
    else 
      NOTIFYADVANCEDAYS = 0
  }
  if (wasModified(NOTIFYDELAYS))
  {
    if (NOTIFYDELAYS == Yes)
      NOTIFYDELAYSDAYS = 1
    else 
      NOTIFYDELAYSDAYS = 0
  }
  if (wasModified(NOTIFYDELAYSCONTINUE))
  {
    if (NOTIFYDELAYSCONTINUE == Yes)
      NOTIFYDELAYSCONTINUEDAYS = 1
    else 
      NOTIFYDELAYSCONTINUEDAYS = 0
  }
   
  // if notifyDelays is unlselected, unselect the notifyDelaysContinue
  if (wasModified(NOTIFYDELAYS))
  {
    if (NOTIFYDELAYS == No)
    {
      NOTIFYDELAYSCONTINUE = No
      NOTIFYDELAYSCONTINUEDAYS = 0
    }
  }
   
  if (wasModified(NOTIFYDELAYSCONTINUE))
  {
    if (NOTIFYDELAYSCONTINUE == Yes and NOTIFYDELAYS == No)
    {
      NOTIFYDELAYSCONTINUE = No
      NOTIFYDELAYSCONTINUEDAYS = 0
    }
  }
   
  // update stato checklist field
  this.updateStatoCKL()
}


// ──────────────────────────────────

// ************************************************************
// Event raised to the document during the validation procedure
// ************************************************************
event ModelloEventoDisposizione.OnValidate(
  int:validateReasons Reason // An integer specifying the reason the document is being validated. The most common values are 0 to indicate complete validation before saving, or 1 to indicate a quick validati...
  inout boolean Error        // A boolean output parameter. If set to True, specifies an error condition and stops the validation immediately.
  inout boolean Skip         // A boolean output parameter. If set to True, does not check the rules for automatic validation of the document (see notes).
)
{
  if (!(deleted))
  {
    if (IDTIPOATTIVITA <= 0)
    {
      Error = true
      this.setPropertyError("è necessario impostare un valore", IDTIPOATTIVITA)
    }
    if (DESCRATTIVITA == "")
    {
      Error = true
      this.setPropertyError("è necessario impostare un valore", DESCRATTIVITA)
    }
     
    string errorMessage = ""
    boolean isValidRecipientAndNotificationType = this.validRecipientAndNotificationType(errorMessage)
    if (!(isValidRecipientAndNotificationType))
    {
      Error = true
      this.setPropertyError(errorMessage, 0)
    }
  }
   
}


// ──────────────────────────────────

// *************************************************************
// Event raised to the document at the end of the save procedure
// *************************************************************
event throws exception ModelloEventoDisposizione.AfterSave(
  inout boolean Cancel // Boolean output parameter that makes it possible to cancel the entire save. It is equivalent to the Cancel parameter of the BeforeSave event.
)
{
  if (ChecklistTemplate)
  {
    QappCore.DTTLogMessage(formatMessage("i checklist items sono |1", ChecklistTemplate.ChecklistInstanceItemCollection.count(), ...), ...)
    ChecklistTemplate.saveToDB(...)
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static ModelloEventoDisposizione ModelloEventoDisposizione.create(
  ModelloEvento modelloEvento // 
)
{
  ModelloEventoDisposizione med = new()
  med.init()
  med.IDTEMPLATEEVENTO = modelloEvento.IDTEMPLATEEVENTO
  int existingDisposizioniCount = modelloEvento.DisposizioniModello.count()
  med.NRORIGA = existingDisposizioniCount + 1
   
  med.MultiSourceExecutor = MultiSourceResponsible.create(med, funzioneOrDipendente)
  return med
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void ModelloEventoDisposizione.encodeDateAndTimeProperties()
{
  ORARIOPREVISTA = Tools.TimeToFloat(StartTime)
  ORARIOFINE = Tools.TimeToFloat(EndTime)
  ORE = (ORARIOFINE - ORARIOPREVISTA) * 24
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void ModelloEventoDisposizione.decodeDateAndTimeProperties()
{
  StartTime = Tools.FloatToTime(ORARIOPREVISTA)
  EndTime = Tools.FloatToTime(ORARIOFINE)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void ModelloEventoDisposizione.moveDown()
{
  if (this.canMoveDown())
  {
    IDCollection disposizioni of ModelloEventoDisposizione = parentCollection()
    ModelloEventoDisposizione nextDispsizione = null
    ModelloEventoDisposizione matchingDisposizione = null
     
    for each ModelloEventoDisposizione med in disposizioni
    {
      if (med.NRORIGA == NRORIGA + 1)
      {
         nextDispsizione = med
      }
      if (med.NRORIGA == NRORIGA)
      {
         matchingDisposizione = med
      }
    }
    matchingDisposizione.NRORIGA = matchingDisposizione.NRORIGA + 1
    nextDispsizione.NRORIGA = nextDispsizione.NRORIGA - 1
     
    // sort parent collection on NroRiga column because NroRiga is changed and at panel side it should be visible in correct order
    this.sortByNroRiga()
  }
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void ModelloEventoDisposizione.moveUp()
{
  if (this.canMoveUp())
  {
    IDCollection disposizioni of ModelloEventoDisposizione = parentCollection()
     
    ModelloEventoDisposizione previousDispsizione = null
    ModelloEventoDisposizione matchingDisposizione = null
     
    for each ModelloEventoDisposizione med in disposizioni
    {
      if (med.NRORIGA == NRORIGA - 1)
      {
         previousDispsizione = med
      }
      if (med.NRORIGA == NRORIGA)
      {
         matchingDisposizione = med
      }
    }
    matchingDisposizione.NRORIGA = matchingDisposizione.NRORIGA - 1
    previousDispsizione.NRORIGA = previousDispsizione.NRORIGA + 1
     
    // sort parent collection on NroRiga column because NroRiga is changed and at panel side it should be visible in correct order
    this.sortByNroRiga()
  }
}


// ──────────────────────────────────



// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean ModelloEventoDisposizione.canMoveDown()
{
  IDCollection disposizioni of ModelloEventoDisposizione = parentCollection()
  boolean canMoveDown = NRORIGA < disposizioni.count()
   
  return canMoveDown
}


// ──────────────────────────────────

// ****************************************
// sort parent collection on NroRiga column
// ****************************************
public void ModelloEventoDisposizione.sortByNroRiga()
{
  IDCollection disposizioni of ModelloEventoDisposizione = parentCollection()
   
  int nroRigaIndex = toPropertyIndex(NRORIGA)
  disposizioni.resetSortCriteria()
  disposizioni.addSortCriteria(nroRigaIndex)
  disposizioni.doSort()
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean ModelloEventoDisposizione.isAnyNotificationRecipientSelected()
{
  boolean recipientSelected = (NOTIFYRESPONSIBLE == Yes) or (NOTIFYEXECUTOR == Yes) or (NOTIFYINSUSER == Yes) or (NOTIFYPERREFERENCE == Yes) or (NOTIFYCLIFORREFERENCE == Yes) or (NOTIFYOTHERS != "")
  return recipientSelected
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean ModelloEventoDisposizione.isAnyNotificationTypeSelected()
{
  boolean notificationTypeSelected = (NOTIFYINADVANCE == Yes and NOTIFYADVANCEDAYS > 0) or (NOTIFYDELAYS == Yes and NOTIFYDELAYSDAYS > 0) or (NOTIFYDELAYSCONTINUE == Yes and NOTIFYDELAYSCONTINUEDAYS > 0) or
           (NOTIFYONEXECUTION == Yes) or (NOTIFYONCLOSE == Yes)
   
  return notificationTypeSelected
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean ModelloEventoDisposizione.validRecipientAndNotificationType(
  inout string errorMessage // 
)
{
  boolean isValid = true
  errorMessage = ""
   
  if (this.isAnyNotificationRecipientSelected() and !(this.isAnyNotificationTypeSelected()))
    errorMessage = "Nessun tipo di notifica selezionato, mentre uno o più destinatari sono selezionati"
   
  if (!(this.isAnyNotificationRecipientSelected()) and this.isAnyNotificationTypeSelected())
    errorMessage = "Nessun destinatario selezionato, mentre uno o più tipo di notifica sono selezionati"
   
  isValid = errorMessage == ""
   
  return isValid
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void ModelloEventoDisposizione.load()
{
}


// ──────────────────────────────────

// ***************************************************
// This method compute the text for notify other field
// 
// ***************************************************
public string ModelloEventoDisposizione.computeNotifyOthersText()
{
  string computedPersonaleText = ""
  string computedContattiText = ""
  string computedAltriText = ""
  IDCollection recipients of Ntfrecipient = NTFRECIPIENTS
  for each Ntfrecipient n in recipients
  {
    if (n.RECIPIENTTYPE == Personale)
    {
      Personale p = Personale.getFromDB(toInteger(n.RECPIENTVALUE), ...)
      if (p)
         computedPersonaleText = SH.Concat(computedPersonaleText, p.FullName, ",")
    }
    if (n.RECIPIENTTYPE == Contact)
      computedContattiText = SH.Concat(computedContattiText, n.EMAIL, ",")
    if (n.RECIPIENTTYPE == Others)
      computedAltriText = SH.Concat(computedAltriText, n.EMAIL, ",")
  }
   
  string contattiFormattedMsg = ""
  string AltriFromattedMsg = ""
   
  string computedNotifyOtherText = ""
  if (computedPersonaleText != "")
    computedNotifyOtherText = formatMessage("Personale (|1)", computedPersonaleText, ...)
   
  if (computedContattiText != "")
  {
    contattiFormattedMsg = formatMessage("Contatti (|1)", computedContattiText, ...)
    if (computedNotifyOtherText == "")
      computedNotifyOtherText = contattiFormattedMsg
    else 
      computedNotifyOtherText = computedNotifyOtherText + ", " + contattiFormattedMsg
  }
   
  if (computedAltriText != "")
  {
    AltriFromattedMsg = formatMessage("Altri (|1)", computedAltriText, ...)
    if (computedNotifyOtherText == "")
      computedNotifyOtherText = AltriFromattedMsg
    else 
      computedNotifyOtherText = computedNotifyOtherText + ", " + AltriFromattedMsg
  }
   
  if (length(computedNotifyOtherText) > 200)
    computedNotifyOtherText = mid(computedNotifyOtherText, 1, 200)
  return computedNotifyOtherText
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void ModelloEventoDisposizione.computeAddittionalProperties()
{
  this.decodeDateAndTimeProperties()
}


// ──────────────────────────────────



// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public Checklist ModelloEventoDisposizione.getChecklist()
{
  if (ChecklistTemplate == null)
  {
    QappCore.DTTLogMessage("Checklist Factory...", ...)
    ChecklistTemplate = Checklist.factory(ModelloEventoDisposizione, this, ...)
  }
  return ChecklistTemplate
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public string ModelloEventoDisposizione.GetChecklistCssClassForIcon()
{
  this.updateStatoCKL()
   
  string cssClass = "chkl-iconNotCreated"
   
  switch (STATOCKL)
  {
    case Creata:
      cssClass = "chkl-iconCreated"
    break
  }
   
  return cssClass
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public string ModelloEventoDisposizione.GetChecklistTextIconForIcon()
{
  this.updateStatoCKL()
   
  string icon = "{{icon-fa-list}}"
   
  switch (STATOCKL)
  {
    case Creata:
      icon = "{{icon-fa-list}}"
    break
  }
   
  return icon
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void ModelloEventoDisposizione.duplicateModelloEventoDisposizione(
  ModelloEventoDisposizione source // 
)
{
  this.init()
   
  NRORIGA = source.NRORIGA
  IDTIPOATTIVITA = source.IDTIPOATTIVITA
  DESCRATTIVITA = source.DESCRATTIVITA
  GGDAINSEVENTO = source.GGDAINSEVENTO
  IDRESPATTIVITA = source.IDRESPATTIVITA
  ORARIOPREVISTA = source.ORARIOPREVISTA
  ORARIOFINE = source.ORARIOFINE
  ORE = source.ORE
  STATOCKL = source.STATOCKL
  ISFUNCTION = source.ISFUNCTION
  NOTIFICA = source.NOTIFICA
  NOTIFYRESPONSIBLE = source.NOTIFYRESPONSIBLE
  NOTIFYEXECUTOR = source.NOTIFYEXECUTOR
  NOTIFYOTHERS = source.NOTIFYOTHERS
  NOTIFYINADVANCE = source.NOTIFYINADVANCE
  NOTIFYADVANCEDAYS = source.NOTIFYADVANCEDAYS
  NOTIFYONEXECUTION = source.NOTIFYONEXECUTION
  NOTIFYONCLOSE = source.NOTIFYONCLOSE
  NOTIFYDELAYS = source.NOTIFYDELAYS
  NOTIFYDELAYSDAYS = source.NOTIFYDELAYSDAYS
  DELAYDAYS = source.DELAYDAYS
  NOTIFYINSUSER = source.NOTIFYINSUSER
  NOTIFYDELAYSCONTINUE = source.NOTIFYDELAYSCONTINUE
  NOTIFYDELAYSCONTINUEDAYS = source.NOTIFYDELAYSCONTINUEDAYS
  NOTIFYPERREFERENCE = source.NOTIFYPERREFERENCE
  NOTIFYCLIFORREFERENCE = source.NOTIFYCLIFORREFERENCE
   
  ChecklistTemplate = new()
  ChecklistTemplate = Checklist.factory(ModelloEventoDisposizione, source, ...)
  ChecklistTemplate.ChecklistInstanceItemCollection.clear()
   
  StartTime = source.StartTime
  EndTime = source.EndTime
  MultiSourceExecutor = source.MultiSourceExecutor
  ModelloEventoDisposizioniExecutorIDForDecodingInUI = source.ModelloEventoDisposizioniExecutorIDForDecodingInUI
   
  if (source.ChecklistTemplate)
  {
    if (source.ChecklistTemplate.ChecklistInstanceItemCollection)
    {
      if (source.ChecklistTemplate.ChecklistInstanceItemCollection.count() > 0)
      {
         IDCollection sourceCheckListItemsCollection of ChecklistItem = source.ChecklistTemplate.ChecklistInstanceItemCollection
         for each ChecklistItem sourceChecklistItem in sourceCheckListItemsCollection
         {
           int nroRiga = 0
            
           // we create the main class item for the collection, but we cast it to the modelli eventi one because we want to duplicate the properties as NOTE, DESCRIPTION ecc...
           ChecklistItem parentNewChecklistItem = ChecklistItem.Factory(ModelloEventoDisposizione, nroRiga, this, 0)
            
           ModelloEventoDisposizioneChecklistTemplateItem newItem = cast(parentNewChecklistItem)
           ModelloEventoDisposizioneChecklistTemplateItem sourceItem = cast(sourceChecklistItem)
            
           newItem.RIFERIMENTI = sourceItem.RIFERIMENTI
           newItem.DESCRDOMANDA = sourceItem.DESCRDOMANDA
           newItem.IDCESPITE = sourceItem.IDCESPITE
           newItem.IDPROGOPERAZIONE = sourceItem.IDPROGOPERAZIONE
           newItem.IDSTEP = sourceItem.IDSTEP
           newItem.MANDATORY = sourceItem.MANDATORY
           newItem.IDCDATAFLD = sourceItem.IDCDATAFLD
           newItem.NOTE = sourceItem.NOTE
            
           ChecklistTemplate.ChecklistInstanceItemCollection.add(newItem)
            
           nroRiga = nroRiga + 1
         }
      }
    }
  }
}


// ──────────────────────────────────

// ****************************************
// Describe the purpose of this function...
// ****************************************
public void ModelloEventoDisposizione.UpdateResponsiblePropertiesEndTransactionHandler()
{
  if (MultiSourceExecutor.MultiSourceResponsibleInterface)
  {
    if (ModelloEventoDisposizioniExecutorIDForDecodingInUI != null and ModelloEventoDisposizioniExecutorIDForDecodingInUI != "")
    {
      string:flagYN isFunctionValue = null
      int idResponsibleValue = null
       
      MultiSourceResponsible.decodeKey(ModelloEventoDisposizioniExecutorIDForDecodingInUI, isFunctionValue, idResponsibleValue)
       
      ISFUNCTION = isFunctionValue
      IDRESPATTIVITA = idResponsibleValue
    }
    else 
    {
      ISFUNCTION = No
      IDRESPATTIVITA = null
    }
  }
   
}


// ──────────────────────────────────

// **************************************************************************************************************************************************************************
// this method must set the string (such as F103) used for decoding in panel, starting from the DB fields (such as IS_FUNCTION and ID_UTENTE RESP, in case of modello evento)
// **************************************************************************************************************************************************************************
public void ModelloEventoDisposizione.MultiSourceResponsibleCreationHandler()
{
  string computedId = MultiSourceExecutor.prepareDecodingKey(ISFUNCTION, IDRESPATTIVITA, "P")
  ModelloEventoDisposizioniExecutorIDForDecodingInUI = computedId
  MultiSourceExecutor.ID = computedId
}


// ──────────────────────────────────

// ****************************************************************************************************************************************************************************************************************
// this method is intentionally put in the interface to remember the developer that in the after load of the class that implments IMultiSourceResponsible it is mandatory to instantiate a multiSourceResponsbile
// object
// so in this object a line of code like
// multiSourceResponsible = multiSourceResponsible.create(this, funzioneOrDipendente) must be written
// ****************************************************************************************************************************************************************************************************************
public void ModelloEventoDisposizione.AfterLoadMultiSourceResponsibleCreation()
{
  MultiSourceExecutor = MultiSourceResponsible.create(this, funzioneOrDipendente)
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event ModelloEventoCDSection.OnInit()
{
  VISIBLE = No
}


// ──────────────────────────────────

// ***********************************************************
// Event raised to the document when the transaction is closed
// ***********************************************************
event ModelloEventoCDSection.OnEndTransaction()
{
   
  // modello evento caches the actual object so it is retrieved only once
  if (ModelloEvento == null)
  {
    ModelloEvento = ModelloEvento.getFromDB(IDTEMPLATEEVENTO, ...)
  }
   
  // for non workflow we return
  if (ModelloEvento.WORKFLOWTYPE != Workflow)
    return 
  if (wasModified(VISIBLE) and VISIBLE == No)
  {
    IDArray stepsWithError = this.getStepsWhereSectionFieldIsSelectedInWorkflowResultCustomData(IDCDATASEC)
    string completeErrorMessage1 = this.buildValidationMessageOnVisibilityChanges(stepsWithError, "<strong><span style="color:red">Non puoi rimuovere la visibilità a sezioni utilizzate nel Workflow. 
             </span></strong><br>", "Step workflow: ")
    if (stepsWithError.length() > 0)
    {
       
      VISIBLE = Yes
      X.MessageBox(completeErrorMessage1, "Errore", ...)
    }
  }
}


// ──────────────────────────────────



// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static ModelloEventoCDSection ModelloEventoCDSection.get(
  int ID // 
)
{
  ModelloEventoCDSection mecds = new()
  mecds.IDEVATEMPLCDATASECTION = ID
  try 
  {
    mecds.loadFromDB(0)
    mecds.loadAddtionalProperties()
  }
  catch 
  {
    QappCore.DTTLogMessage("Cannot load ModelloEventoCDSection", ...)
  }
  return mecds
}


// ──────────────────────────────────

// ******************************************************************************************************************
// this method loads the addtional properties Sequence and Sectionname
// These two properties is been used (for now) to sort the modello evento CDSections based on seqence and sectionname
// 
// CAUTION: Do not call this method on "After load" 
// event because addtional properties are not utilized anywhere else other that sorting at panel
// ******************************************************************************************************************
public void ModelloEventoCDSection.loadAddtionalProperties()
{
  if (IDCDATASEC > 0)
  {
    CdataSection cs = CdataSection.get(IDCDATASEC)
    if (cs)
    {
      Sequence = cs.Sequence
      Sectionname = cs.Name
    }
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void ModelloEventoCDSection.setModelloEvento(
  ModelloEvento modelloEvento // 
)
{
  ModelloEvento = modelloEvento
}


// ──────────────────────────────────

// ************************************************************************************************************************************
// Passing idModelloEvento and idCdataSections this methods returns an array of WFKSteps where we use that cdata section in the worklow
// Done with query fro efficinecy
// ************************************************************************************************************************************
public IDArray ModelloEventoCDSection.getWFKStepsWhereCdataSectionsAreUsedInWorkflow()
{
  if (IDTEMPLATEEVENTO <= 0 or IDCDATASEC <= 0)
  {
    QappCore.DTTLogMessage("IdModelloEvento or idCdataSection missing", ..., DTTError)
    return null
  }
   
  IDArray wfkStepsNamesArray = new()
   
//  // first version of query kept for reference(in the IDE query written below we added also BLOCKTYPE condition to have the same result, not clear why)
//  string query = formatMessage("SELECT DISTINCT WS.STEP_DESCRIPTION FROM EVA_TEMPL_TESTATA T INNER JOIN WKF_STEPS WS ON T.ID_TEMPLATE_EVENTO = WS.ID_TEMPLATE_EVENTO INNER JOIN WKF_RESULTS WRES ON WRES.ID_STEP 
//           = WS.ID_STEP AND T.ID_TEMPLATE_EVENTO = WRES.ID_TEMPLATE_EVENTO INNER JOIN CDATA_WKF_RESULT W ON W.ID_RESULT = WRES.ID_RESULT INNER JOIN CDATA_FIELDS F ON W.ID_CDATA_FLD = F.ID_CDATA_FLD where T.
//           ID_TEMPLATE_EVENTO = |1 AND F.ID_CDATA_SEC = |2", IDTEMPLATEEVENTO, IDCDATASEC, ...)
   
  Recordset rs = new()
  select into recordset distinct (rs)
    WKFSTEPS.STEPDESCRIPTION as STEP_DESCRIPTION
  from 
    ModelliEvento   // master table
    WKFSTEPS        // joined with Modelli Evento using key FK_WKF_STEPS_EVA_TEMPL_TESTATA
    WKFRESULTS      // joined with Modelli Evento using key FK_WKF_RESULTS_EVA_TEMPL_TESTATA
    CDATAWKFRESULT  // joined with WKF RESULTS using key FK_CDATA_WKF_RESULT_WKF_RESULTS
    CDATAFIELDSINFO // joined with CDATA WKF RESULT using key FK_CDATA_WKF_RESULT_CDATA_FIELDS
  where
    WKFSTEPS.BLOCKTYPE == Step
    ModelliEvento.IDTEMPLATEEVENTO == IDTEMPLATEEVENTO
    CDATAFIELDSINFO.IDCDATASEC == IDCDATASEC
   
   
  rs.moveFirst()
   
  while (!(rs.EOF()))
  {
    string wfkStepName = rs.getFieldValueIdx(1)
     
    if (length(wfkStepName) > 0)
    {
      wfkStepsNamesArray.addValue(wfkStepName)
    }
     
    rs.moveNext()
  }
   
  return wfkStepsNamesArray
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string ModelloEventoCDSection.buildValidationMessageOnVisibilityChanges(
  IDArray wkfStepsArray    // 
  string firstMessagePart  // 
  string secondMessagePart // 
)
{
  string errorString = ""
  for (int i = 0; i < wkfStepsArray.length(); i = i + 1)
  {
    errorString = errorString + "<br>- " + wkfStepsArray.getValue(i)
  }
   
  string completeErrorMessage = formatMessage("|1|2|3", firstMessagePart, secondMessagePart, errorString, ...)
   
  return completeErrorMessage
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public IDArray ModelloEventoCDSection.getStepsWhereSectionFieldIsSelectedInWorkflowResultCustomData(
  int idSection // 
)
{
  IDArray stepsWithError = new()
  if (ModelloEvento)
  {
    IDMap fieldsIdsInSection = new()
     
    CdataSection cs0 = CdataSection.get(idSection)
     
    if (cs0)
    {
      cs0.loadCollectionFromDB(cs0.CDATAFIELDSINFO, ...)
       
      for each MainModuleDatoPersonalizzatoInfo mmdpi in cs0.CDATAFIELDSINFO
      {
         if (mmdpi and mmdpi.IDCDATAFLD > 0)
         {
           fieldsIdsInSection.setValue(mmdpi.IDCDATAFLD, true)
         }
      }
    }
     
    Diagram d = ModelloEvento.GetDiagram()
    IDCollection steps of WkfStep = d.GetSteps()
     
     
    for each WkfStep ws in steps
    {
      for each WkfResult wr1 in ws.WKFRESULTS
      {
         for each CdataWkfResultLink cwrl in wr1.CdataWkfResultLinks
         {
           if (fieldsIdsInSection.containsKey(cwrl.IDCDATAFLD))
           {
             QappCore.DTTLogMessage(formatMessage("section |1 in uso", cwrl.IDCDATAFLD, ...), ...)
             string stepName = ws.STEPDESCRIPTION
             int index = stepsWithError.findValue(stepName, false)
              
             // if this step is found in array we do not add it again in array
             if (index < 0)
             {
                stepsWithError.addValue(stepName)
                break 
             }
           }
         }
      }
    }
  }
   
  return stepsWithError
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static ModelloEventoRiferimento ModelloEventoRiferimento.create(
  ModelloEvento modello                          // 
  ReferenceType referenceType                    // 
  optional int mandatoryRows = 0                 // 
  optional string:flagYN previewOnCalendar = "N" // 
)
{
  ModelloEventoRiferimento mer = new()
  mer.init()
  mer.IDTEMPLATEEVENTO = modello.IDTEMPLATEEVENTO
  mer.IDTipoRiferimento = referenceType.IDTipoRiferimento
  mer.KORDAPP = referenceType.SourceKordapp
  mer.MANDATORYROWS = mandatoryRows
  mer.PREVIEWONCALENDAR = previewOnCalendar
   
  return mer
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void ModelloEventoRiferimento.unselectPreviewOfCalendarOnOtherRecords()
{
   
  // We do not allow to select "preview on Calendar" field in multiple records
  IDCollection allAvaialableModelloReferences of ModelloEventoRiferimento = parentCollection()
  for each ModelloEventoRiferimento mer in allAvaialableModelloReferences
  {
    if (mer.IDEVATEMPLREFERENCE != IDEVATEMPLREFERENCE)
    {
      if (mer.PREVIEWONCALENDAR == Yes)
      {
         mer.PREVIEWONCALENDAR = No
      }
    }
  }
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event ModelloEventoRiferimento.OnInit()
{
  IDEVATEMPLREFERENCE = Sequence.getNextSequence(EVAN_ID_EVA_TEMPL_REFERENCE, ...)
  MANDATORYROWS = 0
  PREVIEWONCALENDAR = No
}


// ──────────────────────────────────

// ***********************************************************
// Event raised to the document when the transaction is closed
// ***********************************************************
event ModelloEventoRiferimento.OnEndTransaction()
{
  if (wasModified(PREVIEWONCALENDAR))
  {
    if (PREVIEWONCALENDAR = Yes)
    {
      this.unselectPreviewOfCalendarOnOtherRecords()
    }
  }
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event EventoChecklistRow.OnInit()
{
  IDDOMANDA = Sequence.getNextSequence(EVAN_ID_DOMANDA_EVENTO, ...)
  MANDATORY = No
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static EventiCosto EventiCosto.create(
  optional Evento evento         // 
  optional ModelloEvento modello // 
)
{
  EventiCosto ec = new()
  ec.init()
   
  if (evento)
    ec.IDEVENTO = evento.IDEVENTO
   
  if (modello)
    ec.IDTEMPLATEEVENTO = modello.IDTEMPLATEEVENTO
   
  Personale p = Personale.getPersonaleLinkedToUtente(QappCore.Loggeduser)
  ec.IDDIPENDENTE = p.IDDIPENDENTE
   
  return ec
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void EventiCosto.computeImporto()
{
  Importo = ORE * PREZZO
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean EventiCosto.canModifyArticolo()
{
  string canModifyArticolo = ""
  select into variables (found variable)
    set canModifyArticolo = ARTICOLO
  from 
    EVATIPICOSTO // master table
  where
    IDTIPOCOSTO == IDTIPOCOSTO
   
  return canModifyArticolo == Yes
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean EventiCosto.canModifyQuantity()
{
  string canModifyQuantity = ""
  select into variables (found variable)
    set canModifyQuantity = GESTORE
  from 
    EVATIPICOSTO // master table
  where
    IDTIPOCOSTO == IDTIPOCOSTO
   
  return canModifyQuantity == Yes
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void EventiCosto.computeArticoloDescrForUI()
{
  if (IDARTICOLO > 0)
  {
    Articolo a = Articolo.getFromDB(IDARTICOLO, quickLoad)
    ArticoloDescrForUI = a.CODARTICOLO + " " + a.DESCRARTICOLO
    CODUM = a.CODUMVENDITA
    PREZZO = a.PZZOACQUISTO
  }
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event EventiCosto.OnInit()
{
  IDCOSTO = Sequence.getNextSequence(EVAN_ID_COSTO, ...)
  ORE = 1
  PREZZO = 0
  Importo = 0
  DATARIGA = now()
  DESCRRIGA = ""
  IDUTENTEINS = QappCore.Loggeduser.IDUTENTE
  IDUTENTEULTMOD = QappCore.Loggeduser.IDUTENTE
  DATAINS = now()
  DATAULTIMAMOD = now()
  DATA = now()
}


// ──────────────────────────────────

// ************************************************************
// Event raised by the document after loading from the database
// ************************************************************
event EventiCosto.AfterLoad(
  boolean AlreadyLoaded     // A boolean parameter that specifies whether the document was previously loaded.
  IDArray LoadedCollections // IDArray type parameter that contains the collections loaded by the framework. If the value of the Already Loaded parameter is False, the Loaded Collections parameter is NULL.
)
{
  this.computeImporto()
  this.computeArticoloDescrForUI()
  this.setOriginal()
}


// ──────────────────────────────────

// ***********************************************************
// Event raised to the document when the transaction is closed
// ***********************************************************
event EventiCosto.OnEndTransaction()
{
  if (wasModified(ORE) or wasModified(PREZZO))
  {
    this.computeImporto()
  }
   
  if (Evento.isMyInstance(parent))
  {
    if (wasModified(IDTIPOCOSTO))
    {
      IDARTICOLO = null
      ArticoloDescrForUI = ""
      CODUM = ""
      if (IDTIPOCOSTO > 0)
      {
         TipiCosto tc = TipiCosto.get(IDTIPOCOSTO)
         if (tc)
         {
           PREZZO = tc.COSTOUNITARIO
           CODUM = tc.CODUM
            
           if (tc.GESTORE == Yes)
           {
             ORE = 1
           }
            
           this.computeImporto()
         }
      }
    }
     
    if (wasModified(IDTIPOCOSTO) or wasModified(ORE) or wasModified(PREZZO) or wasModified(NOTE) or wasModified(IDARTICOLO) or wasModified(CODUM) or wasModified(IDDIPENDENTE) or wasModified(DATA))
    {
      DATAULTIMAMOD = now()
      IDUTENTEULTMOD = QappCore.Loggeduser.IDUTENTE
    }
  }
}


// ──────────────────────────────────

// ************************************************************
// Event raised to the document during the validation procedure
// ************************************************************
event EventiCosto.OnValidate(
  int:validateReasons Reason // An integer specifying the reason the document is being validated. The most common values are 0 to indicate complete validation before saving, or 1 to indicate a quick validati...
  inout boolean Error        // A boolean output parameter. If set to True, specifies an error condition and stops the validation immediately.
  inout boolean Skip         // A boolean output parameter. If set to True, does not check the rules for automatic validation of the document (see notes).
)
{
  if (deleted)
  {
    return 
  }
   
  if (IDTIPOCOSTO <= 0)
  {
    Error = true
    this.setPropertyError("E' necessario impostare un Tipo di costo.", IDTIPOCOSTO)
  }
  if (PREZZO <= 0)
  {
    Error = true
    this.setPropertyError("Il prezzo deve essere superiore a 0.", PREZZO)
  }
  if (IDARTICOLO <= 0 or isNull(IDARTICOLO))
  {
    if (this.canModifyArticolo())
    {
      // only in case of eventi we want to set the Articolo, not in case of template evento
      if (isNull(IDTEMPLATEEVENTO) or IDTEMPLATEEVENTO == 0)
      {
         Error = true
         this.setPropertyError("E' necessario impostare un Articolo.", IDARTICOLO)
      }
    }
  }
   
  this.refreshUserInterface()
}


// ──────────────────────────────────

// **************************************************************
// Event raised to the document when the Updated property changes
// **************************************************************
event EventiCosto.OnUpdating()
{
  if (wasModified(ORE) or wasModified(PREZZO))
  {
    this.computeImporto()
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static TipiCosto TipiCosto.get(
  int idTipoCosto // 
)
{
  TipiCosto tc = new()
  tc.IDTIPOCOSTO = idTipoCosto
   
  try 
  {
    tc.loadFromDB(...)
  }
  catch 
  {
    tc = null
    QappCore.DTTLogMessage("Tipo Costo not on DB.", ..., DTTError)
  }
   
  return tc
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event EventoEsiti.OnInit()
{
  IDGRAVITA = Sequence.getNextSequence(EVAN_ID_TABELLA, ...)
  SEQUENZA = 1
   
}


// ──────────────────────────────────

// **************************************************************************
// Event called to customize the Smart Lookup procedure at the document level
// **************************************************************************
event EventoEsiti.OnGetSmartLookup(
  Recordset RecordSet       // The recordset to be filled if the queries run during the Smart Lookup event are customized.
  inout int Level           // The Smart Lookup procedure performs multiple attempts, increasingly widening the search field. This integer parameter specifies the number of attempts made.
  inout boolean NullValue   // A boolean output parameter. If set to True, then NULL (no results found) will be returned to the panel.
  inout boolean Skip        // A boolean output parameter. If set to True, then the standard queries for the current level will not be executed.
  inout boolean Cancel      // A boolean output parameter. If set to True, the Smart Lookup procedure will be stopped.
  IDDocument CallerDocument // The document for which the smart lookup procedure is launched. It can be null.
)
{
  Skip = true
   
  if (WkfFlow.isMyInstance(CallerDocument))
  {
    WkfFlow wf = (WkfFlow)CallerDocument
     
    ModelloEvento me = wf.ModelloEvento
     
    if (me == null)
    {
      me = ModelloEvento.getFromDB(wf.IDTEMPLATEEVENTO, quickLoad)
    }
    Recordset modifiedRS = new()
    select into recordset (modifiedRS)
      EventoEsiti.IDGRAVITA as IDGRAVITA
      EventoEsiti.DESCRGRAVITA as DESCRGRAVITA
    from 
      EventoEsiti            // master table
      EventiEsitoClasseLinks // joined with Evento Esiti using key FK_EVA_GRAVITA_CLASSI02
      EVACLASSIEVENTO        // joined with Eventi Esito Classe Links using key FK_EVA_GRAVITA_CLASSI01
    where
      EVACLASSIEVENTO.IDCLASSE == me.IDCLASSE
     
    if (modifiedRS.recordCount() > 0)
    {
      RecordSet.copyFrom(modifiedRS)
    }
    else 
    {
      Cancel = true
    }
  }
  else 
  {
    Cancel = true
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static EventoEsiti EventoEsiti.create(
  string description // 
)
{
  EventoEsiti ege = new()
  ege.init()
  ege.DESCRGRAVITA = description
   
  return ege
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static EventoEsiti EventoEsiti.get(
  int idGravita // 
)
{
  EventoEsiti ee = new()
  ee.IDGRAVITA = idGravita
  try 
  {
    ee.loadFromDB(...)
  }
  catch 
  {
    ee = null
    QappCore.DTTLogMessage(formatMessage("The get method unable to load Evento Esiti for IdGravita: |1", idGravita, ...), ..., DTTError)
  }
  return ee
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static RefTypeToDestinationModuleTypeLink RefTypeToDestinationModuleTypeLink.create(
  ReferenceType refType // 
  ClasseEvento classe   // 
)
{
  RefTypeToDestinationModuleTypeLink erct = new()
  erct.init()
  erct.IDTipoRiferimento = refType.IDTipoRiferimento
  erct.IDCLASSE = classe.IDCLASSE
   
  return erct
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static EventoPublicInfo EventoPublicInfo.getByGuid(
  string GUID // 
)
{
  EventoPublicInfo evapublicinfo = new()
  evapublicinfo.GUID = GUID
  try 
  {
    evapublicinfo.loadFromDB(...)
  }
  catch 
  {
    evapublicinfo = null
    QappCore.DTTLogMessage(formatMessage("Unable to load EvaPublic object for GUID: |1", GUID, ...), ..., DTTError)
  }
  return evapublicinfo
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static EventoPublicInfo EventoPublicInfo.getByEvento(
  Evento evento // 
)
{
  EventoPublicInfo evapublicinfo = new()
  evapublicinfo.IDEVENTO = evento.IDEVENTO
  try 
  {
    evapublicinfo.loadFromDB(...)
  }
  catch 
  {
    evapublicinfo = null
    QappCore.DTTLogMessage(formatMessage("Unable to load EvaPublic object for Evento: |1", evento.IDEVENTO, ...), ..., DTTInfo)
  }
  return evapublicinfo
}


// ──────────────────────────────────

// *****************************************************************************************
// url to check for publicinfo is returned, this link will provide query for a specific guid
// *****************************************************************************************
public string EventoPublicInfo.getUrl()
{
  string codelessPublicStatusUrl = this.getCodelessPublicInfoUrl()
   
  string urlWithSpecificGuid = formatMessage("|1&CODE=|2", codelessPublicStatusUrl, GUID, ...)
   
  return urlWithSpecificGuid
}


// ──────────────────────────────────

// ***************************************************************************************************
// Returns the fixed part of the url for PUBLIC_INFO CMD, to this later CODE url param can be appended
// ***************************************************************************************************
public string EventoPublicInfo.getCodelessPublicInfoUrl()
{
  string baseUrl = X.GetApplicationUrl(...)
   
  int idModelloEvento = 0
  select into variables (found variable)
    set idModelloEvento = IDTEMPLATEEVENTO
  from 
    Eventi // master table
  where
    IDEVENTO == IDEVENTO
   
   
  string codelessPublicStatusUrl = formatMessage("|1?CMD=SEARCH_PUBLIC_INFO&MODELLO_ID=|2", baseUrl, idModelloEvento, ...)
   
  return codelessPublicStatusUrl
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string EventoPublicInfo.getSuccessCaptionForPublicInfoForm()
{
  Evento e = Evento.getFromDB(IDEVENTO, ...)
   
   
  string successCaption = null
  if (e)
  {
    string rifEvento = e.NROEVENTO
    successCaption = formatMessage("Informazioni pubbliche trovate correttamente (rif: |1)", rifEvento, ...)
    int i = e.IDTEMPLATEEVENTO
     
    UrlToken ut = new()
    ut.MAINID = i
    ut.loadFromDB(...)
     
    string s = ut.ConsultazionePublicInfoDescription
    s = replace(s, "[NRO_EVENTO]", rifEvento)
    successCaption = s
     
  }
  else 
  {
    successCaption = "N/A"
  }
   
   
   
  return successCaption
}


// ──────────────────────────────────



// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event EventoPublicInfo.OnInit()
{
  GUID = SH.createGUID()
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static EventoPublicStato EventoPublicStato.get(
  int idPublicStato // 
)
{
  EventoPublicStato eps = new()
  eps.IdEventoPublicStato = idPublicStato
   
  try 
  {
    eps.loadFromDB(...)
  }
  catch 
  {
    eps = null
    QappCore.DTTLogMessage(formatMessage("EventoPublicStato not found for id |1", idPublicStato, ...), ..., DTTError)
  }
  return eps
}


// ──────────────────────────────────

// **************************************************************************
// Event called to customize the Smart Lookup procedure at the document level
// **************************************************************************
event EventoPublicStato.OnGetSmartLookup(
  Recordset RecordSet       // The recordset to be filled if the queries run during the Smart Lookup event are customized.
  inout int Level           // The Smart Lookup procedure performs multiple attempts, increasingly widening the search field. This integer parameter specifies the number of attempts made.
  inout boolean NullValue   // A boolean output parameter. If set to True, then NULL (no results found) will be returned to the panel.
  inout boolean Skip        // A boolean output parameter. If set to True, then the standard queries for the current level will not be executed.
  inout boolean Cancel      // A boolean output parameter. If set to True, the Smart Lookup procedure will be stopped.
  IDDocument CallerDocument // The document for which the smart lookup procedure is launched. It can be null.
)
{
  Skip = true
   
  if (WkfFlow.isMyInstance(CallerDocument))
  {
    WkfFlow wf = (WkfFlow)CallerDocument
     
    ModelloEvento me = ModelloEvento.getFromDB(wf.IDTEMPLATEEVENTO, quickLoad)
     
    Recordset record = new()
    select into recordset (record)
      EventoPublicStati.IdEventoPublicStato as IDEVENPUBSTA
      EventoPublicStati.Description as DESCRIPTION
    from 
      EventoPublicStati      // master table
      EVAPUBLICSTATUSCLASSES // joined with Evento Public Stati using key FK_EVA_PUBLIC_STATUS_CLASSES_EVA_PUBLIC_STATUSES
    where
      EVAPUBLICSTATUSCLASSES.IDCLASSE == me.IDCLASSE
     
    if (record.recordCount() > 0)
    {
      RecordSet.copyFrom(record)
    }
    else 
    {
      Cancel = true
    }
  }
  else 
  {
    Cancel = true
  }
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event EventiEsitoClasseLinks.OnInit()
{
  ATTIVO = Yes
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static EventiEsitoClasseLinks EventiEsitoClasseLinks.create(
  ClasseEvento classe // 
  EventoEsiti gravita // 
)
{
  EventiEsitoClasseLinks egc = new()
  egc.init()
  egc.IDCLASSE = classe.IDCLASSE
  egc.IDGRAVITA = gravita.IDGRAVITA
   
  return egc
}


// ──────────────────────────────────

// *****************************************
// get code tipologia for given id tipologia
// *****************************************
private string ClasseEvento.GetTipologia(
  int IdTipologia // 
)
{
  string Result = ""
  TipologiaEvento e = new()
  e.IDTIPOLOGIA = IdTipologia
  try 
  {
    e.loadFromDB(...)
  }
  Result = e.CODTIPOLOGIA
  return Result
}


// ──────────────────────────────────

// ***********************************
// get ambito code for given id ambito
// ***********************************
private string ClasseEvento.GetAmbitoCode(
  int IdAmbito // 
)
{
  string Result = ""
  AmbitoEvento e = new()
  e.IDAMBITO = IdAmbito
  try 
  {
    e.loadFromDB(...)
    Result = e.CODAMBITO
  }
  catch 
  {
    Result = null
  }
  return Result
}


// ──────────────────────────────────

// ************************************
// get DESCR_CLASSE for given ID_CLASSE
// ************************************
public static string ClasseEvento.GetClassDescription(
  int IdClasse // 
)
{
  string Result = ""
  ClasseEvento ece = new()
  ece.IDCLASSE = IdClasse
  try 
  {
    Result = ece.DESCRCLASSE
  }
  return Result
}


// ──────────────────────────────────

// *************************************
// get Classe object for given ID_CLASSE
// *************************************
public static ClasseEvento ClasseEvento.get(
  int IdClasse // 
)
{
  ClasseEvento ece = new()
  ece.IDCLASSE = IdClasse
  try 
  {
    ece.loadFromDB(...)
  }
  return ece
}


// ──────────────────────────────────

// ********************************************************************************************************************************************************************************************
// create nro evento based on Class,Ambito and Tipologia
// 
// computeOnly true means counter is not increased, so the method is called just to know the theoretical NROEVENTO that would be computed with the current value of the matching evento counter
// ********************************************************************************************************************************************************************************************
public string ClasseEvento.CreateNRO(
  optional string:createNROModes mode = "updateCounter" // 
  optional int IdAmbito = 0                             // 
  optional int IdTipologia = 0                          // 
  optional int Year = -1                                // 
)
{
  boolean computeOnly = mode == computeOnly
   
  if (Year == -1)
  {
    Year = year(today())
  }
   
  ContatoreEvento ec = ContatoreEvento.retrieve(this, IdAmbito, IdTipologia, Year)
   
   
  // init variables
  string codiceAnno = ""
  string codiceTipologia = ""
  string codiceAmbito = ""
  string resultNroEventoString = ""
   
   
  if (CODANNO == Yes)
  {
    // update il codice anno
    // 
    codiceAnno = toString(Year)
  }
   
  switch (OPZIONICODICE)
  {
    case Ambito:
      if (IdAmbito != 0)
      {
         AmbitoEvento ae = AmbitoEvento.GetAmbito(IdAmbito)
         codiceAmbito = ae.CODAMBITO
      }
    break
    case Tipologia:
      if (IdTipologia != 0)
      {
         TipologiaEvento te = TipologiaEvento.GetTipologia(IdTipologia)
         codiceTipologia = te.CODTIPOLOGIA
      }
    break
  }
   
  if (!(computeOnly))
  {
    ec.ANNO = Year
    ec.increase()
    ec.saveToDB(0, ...)
  }
   
  resultNroEventoString = this.BuildStringNroevento(codiceTipologia, codiceAmbito, codiceAnno, ec.COUNTER)
   
   
  return resultNroEventoString
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public string ClasseEvento.BuildStringNroevento(
  string codiceTipologia // 
  string codiceAmbito    // 
  string CodiceAnno      // 
  int CurrentCounter     // 
)
{
  if (codiceTipologia != "" and codiceAmbito != "")
    QappCore.DTTLogMessage("Tipologia and Ambito cannot be both defined!", ..., DTTError)
   
  // build the result
  string resultString = ""
  resultString = CODCLASSE
  if (codiceAmbito != "")
  {
    resultString = SH.Concat(resultString, codiceAmbito, " ")
  }
   
  if (codiceTipologia != "")
  {
    resultString = SH.Concat(resultString, codiceTipologia, " ")
  }
   
  if (CodiceAnno != "")
  {
    resultString = SH.Concat(resultString, CodiceAnno, " ")
  }
   
  resultString = resultString + "/"
  resultString = resultString + format(CurrentCounter, "0000", ...)
  return resultString
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static ClasseEvento ClasseEvento.create(
  string code        // 
  string description // 
  string descrAmbito // 
)
{
  ClasseEvento ce = new()
  ce.init()
  ce.CODCLASSE = code
  ce.DESCRCLASSE = description
  ce.DESCRAMBITO = descrAmbito
   
  return ce
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean ClasseEvento.canUserCreateEvento(
  Utente user // 
)
{
  boolean userCanCreateEvento = false
  if (user and user.IDUTENTE > 0)
  {
    DevTools.ToBeReviewed("ExpressLoginLevel and ExpressLoginUser are part of the License keys , so for now we are not considering these variables")
//    boolean userHasExpressLogin = false
//    int expressLoginLevel = 4
     
    boolean userHasPers1 = user.hasSpecificPrivilege(Eventi, Pers1)
    boolean userIsAdminEventi = userHasPers1
    boolean userHasInsertionRoleOnClasse = EventoClasseRole.hasInsertRole(user, this)
     
    userCanCreateEvento = userIsAdminEventi or userHasInsertionRoleOnClasse
  }
  return userCanCreateEvento
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static IDCollection ClasseEvento.getVisibleClassesBasedOnUserRole(
  Utente user                         // 
  string:eventiClassesRolesTypes role // 
)
{
  DevTools.ToBeReviewed("this method should be unit tested")
   
  IDCollection availableClassiEvento of ClasseEvento = new()
   
  // specific check for Pers1 in Eventi
  boolean userIsPers1InEventi = user.hasSpecificPrivilegeForKordApp(Eventi, Pers1)
   
  if (userIsPers1InEventi)
  {
    IDCollection allclasses of ClasseEvento = new()
    select into collection (allclasses)
    from 
      ClasseEvento // master table
    where
      HIDDEN == No
      ATTIVA == Yes
    order by
      SEQUENZA
      DESCRCLASSE
     
     
    availableClassiEvento.addAll(allclasses, ...)
  }
  else 
  {
    availableClassiEvento = EventoClasseRole.getClasses(user, role)
  }
   
  return availableClassiEvento
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static IDCollection ClasseEvento.getClassesBasedOnUserRole(
  Utente user                         // 
  string:eventiClassesRolesTypes role // 
)
{
  IDCollection resultingClassiEvento of ClasseEvento = EventoClasseRole.getClasses(user, role)
  return resultingClassiEvento
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean ClasseEvento.shouldTabBeVisible(
  string:moduleSubformTypes subformType // 
)
{
  boolean result = false
  QappCore.DTTLogMessage(subformType, ..., DTTInfo)
   
  switch (subformType)
  {
    case EventiDetailSubform:
    case ModelloEventoDefinizioneSubform:
       
      // The definizione tab cannot be hidden in Qualibus web: unlike C/S — where the
      // header (numero, descrizione, classe, titolo, ...) sat outside the tabs — here
      // the header lives inside the first tab, so hiding it would hide the header
      result = true
    break
    case CustomDataSubform:
      result = if(SHOWDATTIP == Yes, true, false)
    break
    case RiferimentiSubform:
      result = if(SHOWRIFERMENTI == Yes, true, false)
    break
    case PromemoriaSubform:
      result = if(SHOWPROMEMORIA == Yes, true, false)
    break
    case DocumentiSubform:
      result = if(SHOWDOCUMENTI == Yes, true, false)
    break
    case ModelliDisposizioniSubform:
    case DisposizioniSubform:
      result = if(SHOWDISPOSIZIONI == Yes, true, false)
    break
    case ModelliAndEventiCostiSubform:
      result = if(SHOWCOSTI == Yes, true, false)
    break
    case InstructionsSubform:
      result = if(SHOWISTRUZIONI == Yes, true, false)
    break
    case OpzioniModelliSubform:
      result = true
    break
    case WorkflowDiagramSubform:
      result = true
    break
    default:
      QappCore.DTTLogMessage(formatMessage("Tab not supported: '|1', it will be set at visible", subformType, ...), ..., DTTInfo)
      result = true
    break
  }
   
   
  return result
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static IDCollection ClasseEvento.getFilteredClassiCollectionForSearch(
  string filter                                // 
  IDCollection availableClassi of ClasseEvento // 
)
{
  QappCore.DTTLoggedLoops = 9999
  filter = upper(filter)
  IDCollection results of ClasseEvento = new()
   
  if (filter = "")
  {
    results.addAll(availableClassi, ...)
  }
  else 
  {
    for each ClasseEvento classe in availableClassi
    {
      if (find(upper(classe.CODCLASSE), filter, ...) > 0)
      {
         results.addRef(classe)
         continue 
      }
      if (find(upper(classe.DESCRCLASSE), filter, ...) > 0)
      {
         results.addRef(classe)
         continue 
      }
    }
  }
   
  return results
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event ClasseEvento.OnInit()
{
  IDCLASSE = Sequence.getNextSequence(EVAN_ID_TABELLA, ...)
  SEQUENZA = 1
  ATTIVA = Yes
  CODANNO = Yes
  COLORE = 16777215
  SHOWRIFERMENTI = Yes
  SHOWANALISI = Yes
  SHOWPROMEMORIA = Yes
  SHOWDISPOSIZIONI = Yes
  SHOWCOSTI = Yes
  SHOWDOCUMENTI = Yes
  HIDDEN = No
  OPZIONICODICE = No
  MENUSEQUENZA = 1
  OPZIONICOLORE = "C"
  SHOWISTRUZIONI = Yes
  SHOWDEFINIZIONE = Yes
}


// ──────────────────────────────────

********************************************************* opens the document in browser and writes the document log *********************************************************

// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static boolean EventoClasseRole.userHasClassVisibilityRoles(
  Evento evento // 
  Utente user   // 
)
{
  int vCount = 0
  select into variables (found variable)
    set vCount = count(...)
  from 
    EventoClasseRoles // master table
  where
    VISIBILITA == Yes
    IDCLASSE == evento.IDCLASSE
    IDUTENTE == user.IDUTENTE
  boolean userHasClassVisibilityRoles = vCount > 0
  return userHasClassVisibilityRoles
}


// ──────────────────────────────────

// ********************************************************************************
// We retrieve the Classes that are visibile for the Ambito, actives and not Hidden
// This procedure filters the results basing on the role & User shared as parameter
// ********************************************************************************
public static IDCollection EventoClasseRole.getClasses(
  Utente user                         // 
  string:eventiClassesRolesTypes role // 
)
{
  boolean insertion = role == insertion
  boolean analysis = role == analysis
  boolean disposizioni = role == disposizioni
  boolean costs = role == costs
  boolean closure = role == closure
  boolean message = role == message
  boolean visibilty = role == visibility
  boolean documents = role == documents
   
  IDCollection resultingClassiEvento of ClasseEvento = new()
  for each row (readonly)
  {
    select distinct
      idClasse = EVACLASSIEVENTO.IDCLASSE
      Sequenza = EVACLASSIEVENTO.SEQUENZA
      DescrClasse = EVACLASSIEVENTO.DESCRCLASSE
    from 
      EventoClasseRoles       // master table
      EventiAmbitiClasseLinks // joined with EVA CLASSI EVENTO using key FK_EVA_AMBITO_CLASSI_EVA_CLASSI_EVENTO
      EVACLASSIEVENTO         // joined with Evento Classe Roles using key FK_EVA_RUOLI_EVENTO02
    where
      EVACLASSIEVENTO.ATTIVA == Yes and EVACLASSIEVENTO.HIDDEN == No
      EventoClasseRoles.IDUTENTE == user.IDUTENTE
      (EventoClasseRoles.IMMISSIONE == Yes and insertion == true) or insertion == false
      (EventoClasseRoles.ANALISI == Yes and analysis == true) or analysis == false
      (EventoClasseRoles.DISPOSIZIONI == Yes and disposizioni == true) or disposizioni == false
      (EventoClasseRoles.COSTI == Yes and costs == true) or costs == false
      (EventoClasseRoles.CHIUSURA == Yes and closure == true) or closure == false
      (EventoClasseRoles.MESSAGGIO == Yes and message == true) or message == false
      (EventoClasseRoles.VISIBILITA == Yes and visibilty == true) or visibilty == false
      (EventoClasseRoles.DOCUMENTI == Yes and documents == true) or documents == false
    order by
      EVACLASSIEVENTO.SEQUENZA
      EVACLASSIEVENTO.DESCRCLASSE
     
     
    ClasseEvento ce = ClasseEvento.get(idClasse)
    resultingClassiEvento.add(ce)
  }
   
  return resultingClassiEvento
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static boolean EventoClasseRole.userHasSpecificRoleOnClass(
  Utente user                         // 
  string:eventiClassesRolesTypes role // 
  ClasseEvento classe                 // 
)
{
  boolean insertion = role == insertion
  boolean analysis = role == analysis
  boolean disposizioni = role == disposizioni
  boolean costs = role == costs
  boolean closure = role == closure
  boolean message = role == message
  boolean visibilty = role == visibility
  boolean documents = role == documents
   
  int vCount = 0
  select into variables (found variable)
    set vCount = count(EventoClasseRoles.IDCLASSE)
  from 
    EventoClasseRoles       // master table
    EventiAmbitiClasseLinks // manually joined, see where clauses
    EventoAmbitoRoles       // manually joined, see where clauses
  where
    EventiAmbitiClasseLinks.IDCLASSE == classe.IDCLASSE
    EventiAmbitiClasseLinks.IDAMBITO == EventoAmbitoRoles.IDAMBITO
    EventiAmbitiClasseLinks.ASSIGNED == Yes
    EventoAmbitoRoles.VISUALIZZA == Yes and EventoAmbitoRoles.IDUTENTE == user.IDUTENTE
    EventoClasseRoles.IDCLASSE == classe.IDCLASSE and EventoClasseRoles.IDUTENTE == user.IDUTENTE
    (EventoClasseRoles.IMMISSIONE == Yes and insertion == true) or insertion == false
    (EventoClasseRoles.ANALISI == Yes and analysis == true) or analysis == false
    (EventoClasseRoles.DISPOSIZIONI == Yes and disposizioni == true) or disposizioni == false
    (EventoClasseRoles.COSTI == Yes and costs == true) or costs == false
    (EventoClasseRoles.CHIUSURA == Yes and closure == true) or closure == false
    (EventoClasseRoles.MESSAGGIO == Yes and message == true) or message == false
    (EventoClasseRoles.VISIBILITA == Yes and visibilty == true) or visibilty == false
    (EventoClasseRoles.DOCUMENTI == Yes and documents == true) or documents == false
    EventoClasseRoles.RESTRICTOPEN == No and EventoAmbitoRoles.RESTRICTOPEN == No
   
  boolean userHasSpecificRoleOnClasse = vCount == 1
  return userHasSpecificRoleOnClasse
}


// ──────────────────────────────────

// *************************************
// get Ambito object for given ID_Ambito
// *************************************
public static AmbitoEvento AmbitoEvento.GetAmbito(
  int IdAmbito // 
)
{
  AmbitoEvento eae = new()
  eae.IDAMBITO = IdAmbito
  try 
  {
    eae.loadFromDB(...)
  }
  return eae
}


// ──────────────────────────────────

// **************************************
// get ambito description given ID_Ambito
// **************************************
public static string AmbitoEvento.GetAmbitoDescription(
  int IdAmbito // 
)
{
  AmbitoEvento evaambitievento = AmbitoEvento.GetAmbito(IdAmbito)
   
  string description = ""
   
  if (evaambitievento)
  {
    description = evaambitievento.DESCRAMBITO
  }
   
  return description
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static AmbitoEvento AmbitoEvento.create(
  string code        // 
  string description // 
)
{
  AmbitoEvento ae = new()
  ae.init()
  ae.CODAMBITO = code
  ae.DESCRAMBITO = description
  return ae
}


// ──────────────────────────────────

// *************************************************************************************************************
// this method consider ambito roles and ambito/classe links and query the result based on IdClasse and IdUtente
// *************************************************************************************************************
public static Recordset AmbitoEvento.getVisibleAmbitiRecordset(
  int idClasse       // 
  int idUtente       // 
  string queryString // 
)
{
  Recordset visibleAmbiti = new()
  select into recordset (visibleAmbiti)
    EVAAMBITIEVENTO.IDAMBITO as IDAMBITO
    EVAAMBITIEVENTO.DESCRAMBITO as DESCRAMBITO
    EVAAMBITIEVENTO.SEQUENZA as SEQUENZA
  from 
    EVAAMBITIEVENTO         // master table
    EventiAmbitiClasseLinks // joined with EVA AMBITI EVENTO using key FK_EVA_AMBITO_CLASSI_EVA_AMBITI_EVENTO
    EventoAmbitoRoles       // joined with EVA AMBITI EVENTO using key FK_EVA_RUOLI_AMBITO_EVA_AMBITI_EVENTO
  where
    EVAAMBITIEVENTO.DESCRAMBITO like "%" + queryString + "%"
    EventiAmbitiClasseLinks.IDCLASSE == idClasse
    EVAAMBITIEVENTO.ATTIVO == Yes
    EventoAmbitoRoles.VISUALIZZA == Yes
    EventoAmbitoRoles.RESTRICTOPEN == No
    EventoAmbitoRoles.IDUTENTE == idUtente
  order by
    EVAAMBITIEVENTO.SEQUENZA
    EVAAMBITIEVENTO.DESCRAMBITO
   
  return visibleAmbiti
}


// ──────────────────────────────────



// ──────────────────────────────────

// **************************************************************************
// Event called to customize the Smart Lookup procedure at the document level
// **************************************************************************
event AmbitoEvento.OnGetSmartLookup(
  Recordset RecordSet       // The recordset to be filled if the queries run during the Smart Lookup event are customized.
  inout int Level           // The Smart Lookup procedure performs multiple attempts, increasingly widening the search field. This integer parameter specifies the number of attempts made.
  inout boolean NullValue   // A boolean output parameter. If set to True, then NULL (no results found) will be returned to the panel.
  inout boolean Skip        // A boolean output parameter. If set to True, then the standard queries for the current level will not be executed.
  inout boolean Cancel      // A boolean output parameter. If set to True, the Smart Lookup procedure will be stopped.
  IDDocument CallerDocument // The document for which the smart lookup procedure is launched. It can be null.
)
{
  Skip = true
   
  boolean eventoISCallerDocument = Evento.isMyInstance(CallerDocument)
  boolean modelloEventoIsCallerDocument = ModelloEvento.isMyInstance(CallerDocument)
   
  int idClasse = 0
   
  if (eventoISCallerDocument)
  {
    Evento e = cast(CallerDocument)
    idClasse = e.IDCLASSE
  }
  else if (modelloEventoIsCallerDocument)
  {
    ModelloEvento me = cast(CallerDocument)
    idClasse = me.IDCLASSE
  }
   
  if (eventoISCallerDocument or modelloEventoIsCallerDocument)
  {
    string queryString = nullValue(DESCRAMBITO, "")
    if (queryString == "*")
      queryString = ""
     
    Recordset resultsRecordset = this.getVisibleAmbitiRecordset(idClasse, QappCore.Loggeduser.IDUTENTE, queryString)
     
    RecordSet.copyFrom(resultsRecordset)
  }
  else 
  {
    Cancel = true
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static EventiAmbitiClasseLinks EventiAmbitiClasseLinks.create(
  ClasseEvento classe // 
  AmbitoEvento ambtio // 
)
{
  EventiAmbitiClasseLinks evaambitoclassi = new()
  evaambitoclassi.init()
  evaambitoclassi.IDCLASSE = classe.IDCLASSE
  evaambitoclassi.IDAMBITO = ambtio.IDAMBITO
  evaambitoclassi.ASSIGNED = Yes
   
  return evaambitoclassi
}


// ──────────────────────────────────

// ****************************************************************
// get ambito count of for given idclasse in EVA_AMBITI_CLASS table
// ****************************************************************
public static int EventiAmbitiClasseLinks.getAmbitoCountForClass(
  int idClasse // 
)
{
  int ambitoCount = 0
  select into variables (found variable)
    set ambitoCount = count(IDEVAAMBITOCLASSI)
  from 
    EventiAmbitiClasseLinks // master table
  where
    IDCLASSE == idClasse
    ASSIGNED == Yes
   
  return ambitoCount
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static boolean EventoAmbitoRole.userhasAmbitoVisibilityRole(
  Evento evento // 
  Utente utente // 
)
{
  int vCount = 0
  select into variables (found variable)
    set vCount = count(...)
  from 
    EventoAmbitoRoles // master table
  where
    VISUALIZZA == Yes
    IDUTENTE == utente.IDUTENTE
    IDAMBITO == evento.IDAMBITO
   
  boolean userHasVisulizaRole = vCount > 0
   
  return userHasVisulizaRole
}


// ──────────────────────────────────

// **************************************************************************
// Event called to customize the Smart Lookup procedure at the document level
// **************************************************************************
event EVARESULTSTYPE.OnGetSmartLookup(
  Recordset RecordSet       // The recordset to be filled if the queries run during the Smart Lookup event are customized.
  inout int Level           // The Smart Lookup procedure performs multiple attempts, increasingly widening the search field. This integer parameter specifies the number of attempts made.
  inout boolean NullValue   // A boolean output parameter. If set to True, then NULL (no results found) will be returned to the panel.
  inout boolean Skip        // A boolean output parameter. If set to True, then the standard queries for the current level will not be executed.
  inout boolean Cancel      // A boolean output parameter. If set to True, the Smart Lookup procedure will be stopped.
  IDDocument CallerDocument // The document for which the smart lookup procedure is launched. It can be null.
)
{
  Skip = true
  Cancel = true
  Recordset records = new()
   
  // the result are based on a specific Classe Evento
  int idClasseEvento = 0
   
  // find out evento based on parent -----Evento->Disposizione->Checklist->CheckListIem
  if (EventoDisposizioneChecklistItem.isMyInstance(CallerDocument))
  {
    EventoDisposizioneChecklistItem checkListItem = cast(CallerDocument)
    if (checkListItem.parent)
    {
      if (EventoDisposizioneChecklist.isMyInstance(checkListItem.parent))
      {
         EventoDisposizioneChecklist checklist = (EventoDisposizioneChecklist)checkListItem.parent
          
         Disposizione disposizione = checklist.DisposizioneEvento
         if (disposizione)
         {
           if (Evento.isMyInstance(disposizione.parent))
           {
             Evento evento = (Evento)disposizione.parent
             if (evento)
                idClasseEvento = evento.IDCLASSE
           }
         }
      }
    }
  }
   
   
  if (idClasseEvento > 0)
  {
    select into recordset (records)
      EVARESULTSTYPE.IDEVARESULTSTYPE as IDEVARESUTYP
      EVARESULTSTYPE.DESCREVARESULTSTYPE as DESEVARESTYP
    from 
      EVARESULTSTYPE       // master table
      EVARESULTSTYPECLASSI // joined with EVA RESULTS TYPE using key FK_EVA_RESULTS_TYPE_CLASSI02
    where
      EVARESULTSTYPECLASSI.IDCLASSE = idClasseEvento
      isNull(EVARESULTSTYPE.IDTEMPLATE)
     
     
    RecordSet.copyFrom(records)
  }
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static EVARESULTSTYPE EVARESULTSTYPE.get(
  int idEvaResultsType // 
)
{
  EVARESULTSTYPE ert = new()
  ert.IDEVARESULTSTYPE = idEvaResultsType
  try 
  {
    ert.loadFromDB(...)
  }
  catch 
  {
    ert = null
    QappCore.DTTLogMessage("not found", ..., DTTError)
  }
  return ert
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean EVARESULTSTYPE.hasMandatoryNotes()
{
  return MandatoryNotes == Yes
}


// ──────────────────────────────────

********************************************************* opens the document in browser and writes the document log *********************************************************

// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static IDCollection DisposizioneClosureContext.createCollectionForMultiple(
  Evento evento                  // 
  WkfStep nextStep               // 
  WkfFlow flowPointingToNextStep // 
  date previousDisposizioneDate  // 
)
{
  IDCollection multipleNextDisposizioni of WkfDisposizioneDraft = new()
//   
  if (!(nextStep.MultipleStepTemplateDisposizioni.loaded))
  {
    nextStep.loadCollectionFromDB(nextStep.MultipleStepTemplateDisposizioni, ...)
  }
  nextStep.MultipleStepTemplateDisposizioni.resetSortCriteria()
  nextStep.MultipleStepTemplateDisposizioni.addSortCriteria(MultipleStepTemplateDisposizione.SEQ)
  nextStep.MultipleStepTemplateDisposizioni.addSortCriteria(MultipleStepTemplateDisposizione.IDMultipleStepTemplateDisposizione)
  nextStep.MultipleStepTemplateDisposizioni.doSort()
//   
  boolean isFirstDisposizioniInCollection = true
  date lastComputedDisposizioniDate = #1899/12/30#
  for each MultipleStepTemplateDisposizione mstd in nextStep.MultipleStepTemplateDisposizioni
  {
     
    date disposizioniDate = this.computeDisposizioneDate(flowPointingToNextStep, isFirstDisposizioniInCollection, mstd.DELAYDAYS, previousDisposizioneDate, lastComputedDisposizioniDate)
    isFirstDisposizioniInCollection = false
     
    WkfDisposizioneDraft nd = this.createMultipleTypeDisposizione(evento, nextStep, flowPointingToNextStep, disposizioniDate, mstd)
    lastComputedDisposizioniDate = disposizioniDate
    multipleNextDisposizioni.add(nd)
  }
   
   
  return multipleNextDisposizioni
}


// ──────────────────────────────────

// **************************************************************
// Computes the execution date for a single disposizione,
// // based on whether it is the first in the collection 
// // (anchored to today or previousDisposizioneDate via the 
// // flow's DATECALCULATIONTYPE) or a subsequent one 
// // (anchored to the last computed date).
// // A global flow-level delay (DELAYDAYS) is always added last.
// This method is indrectly tested by CreateCollection
// **************************************************************
public static date DisposizioneClosureContext.computeDisposizioneDate(
  WkfFlow flowPointingToTargetStep    // 
  boolean isFirstDisposizione         // 
  int DelaydaysFromTemplate           // 
  date PreviousDisposizioneDate       // 
  date lastDisposizioniDateCalculated // 
)
{
  date DisposizioneDate = #1899/12/30#
  if (isFirstDisposizione)
  {
    if (flowPointingToTargetStep.DATECALCULATIONTYPE == DaDataEsecuzione)
      DisposizioneDate = dateAdd(Day, DelaydaysFromTemplate, today())
    else 
      DisposizioneDate = dateAdd(Day, DelaydaysFromTemplate, PreviousDisposizioneDate)
  }
  else 
  {
    DisposizioneDate = dateAdd(Day, DelaydaysFromTemplate, lastDisposizioniDateCalculated)
  }
  DisposizioneDate = dateAdd(Day, flowPointingToTargetStep.DELAYDAYS, DisposizioneDate)
  return DisposizioneDate
}


// ──────────────────────────────────

// **********************************************************************************
// create the multiple disposizioni from template and initialize essential properties
// This method is indrectly tested by CreateCollection
// **********************************************************************************
public static WkfDisposizioneDraft DisposizioneClosureContext.createMultipleTypeDisposizione(
  Evento evento                         // 
  WkfStep targetedStep                  // 
  WkfFlow flowPointingToTargetedStep    // 
  date disposizioneDate                 // 
  MultipleStepTemplateDisposizione mstd // 
)
{
   
  WkfDisposizioneDraft wdd = new()
  wdd.init()
  wdd.IDTIPOATTIVITA = targetedStep.IDTIPOATTIVITA
  wdd.IDSTEP = targetedStep.IDSTEP
  wdd.ExecutionDate = disposizioneDate
  wdd.STARTTIME = mstd.STARTTIME
  wdd.ENDTIME = mstd.ENDTIME
  wdd.ComputeAdditionalProperties()
  wdd.ISFUNCTION = mstd.ISFUNCTION
  wdd.IDEXECUTORDIPENDENTE = mstd.IDEXECUTORDIPENDENTE
  wdd.IDEXECUTORDIPENDENTE = targetedStep.resolveExecutorId(mstd.IDEXECUTORDIPENDENTE, evento)
  wdd.DESCRIPTION = mstd.DESCRIPTION
  wdd.IsDateEditable = flowPointingToTargetedStep.ISDATEEDITABLE == Yes
  wdd.IsExecutorEditable = targetedStep.EXECUTORTYPE == Manuale
   
  wdd.AfterLoadMultiSourceResponsibleCreation()
   
  return wdd
}


// ──────────────────────────────────

// ***********************************************
// returns the only element in Eva Parametri table
// ***********************************************
public static EvaParametri EvaParametri.getInstance()
{
   
  IDCollection evaParametriCollection of EvaParametri = new()
  select into collection (evaParametriCollection)
  from 
    EvaParametri // master table
  where
    LOCK == "X"
   
  if (evaParametriCollection.count() != 1)
  {
    QappCore.DTTLogMessage(formatMessage("unexpected number (|1) of  eva Parametri records found, this should not happen", evaParametriCollection.count(), ...), ..., DTTError)
  }
   
  return cast(CH.getAtPosition(evaParametriCollection, 0))
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static MultiSourceResponsible MultiSourceResponsible.create(
  IDDocument callerDocument              // 
  string:multiSourceResponsibleType type // 
)
{
  MultiSourceResponsible mer = new()
   
  switch (type)
  {
    case funzioneOrUtente:
      FunctionOrUtenteResponsible four = new()
      mer = four
    break
    case funzioneOrDipendente:
      FunctionOrDipendenteResponsible fodr = new()
      mer = fodr
    break
  }
   
  mer.init()
  mer.MultiSourceResponsibleInterface = (IMultiSourceResponsible)callerDocument
   
  mer.MultiSourceResponsibleInterface.multiSourceResponsibleCreationHandler()
  return mer
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public Recordset MultiSourceResponsible.computeRecordsetForOnGetSmartLookupEvent(
  string queryString // 
)
{
  Recordset rs = new()
  RecordsetMetaData rmd = new()
  rmd.setColumnCount(2)
   
   
   
  string fieldNameForID = this.getFieldNameForId()
  string fieldNameForDescription = this.getFieldNameForDescription()
   
  rmd.setFieldName(1, fieldNameForID)
  rmd.setFieldType(1, Character)
  rmd.setFieldName(2, fieldNameForDescription)
  rmd.setFieldType(2, Character)
  rs.setMetaData(rmd)
   
  IDCollection funzioni of Funzione = QappCore.Loggeduser.getFunzioniUtenteCanSee(queryString)
   
  for each Funzione f in funzioni
  {
    string completeDescription = f.CODFUNZIONE + " - " + f.DESCRFUNZIONE
     
    Tools.addRowToRecordset(rs, "F" + toString(f.IDFUNZIONE), completeDescription)
  }
   
  string prefix = this.getPrefix()
  IDCollection mapOfIDandDescr of MapElement = this.getIDDescriptionMapElements(queryString)
  for each MapElement me in mapOfIDandDescr
  {
    Tools.addRowToRecordset(rs, prefix + toString(me.Key), me.Value)
  }
   
  // sort ascending (positive number) on second field (2)
  rs.addSortCriteria(2)
  rs.doSort()
   
  return rs
}


// ──────────────────────────────────

// ************************************************************************************************************************************
// field Name defined at UI side for ID - initially was made to be extended, now we use on get smart lookup and we hardcode the result 
// ************************************************************************************************************************************
private string MultiSourceResponsible.getFieldNameForId()
{
  return "ID"
}


// ──────────────────────────────────

// *********************************************************************************************************************************************
// field Name defined at UI side for DESCRIPTION - initially was made to be extended, now we use on get smart lookup and we hardcode the result 
// *********************************************************************************************************************************************
private string MultiSourceResponsible.getFieldNameForDescription()
{
  return "DESCRIPTION"
}


// ──────────────────────────────────

// ***********************************************************************************************
// prefix used at UI side to decode the correct type of lookup (P123 = Personale 123, P is prefix)
// ***********************************************************************************************
public string MultiSourceResponsible.getPrefix()
{
  QappCore.DTTLogMessage("ABSTRACT", ..., DTTError)
  return null
}


// ──────────────────────────────────

// ****************************************************************************************************************************************************************************************************************
// return a collection of MapElement that hold ID/Description as key/Value of all the source elements (note: utenti or dipendenti at the time of writing are the 2 possible cases, while funzioni is always present
// and handled separately) 
// ****************************************************************************************************************************************************************************************************************
public IDCollection MultiSourceResponsible.getIDDescriptionMapElements(
  optional string queryString = "%" // 
)
{
  QappCore.DTTLogMessage("ABSTRACT", ..., DTTError)
  return null
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string MultiSourceResponsible.getSpecificDescriptionById(
  int ID // 
)
{
  QappCore.DTTLogMessage("ABSTRACT", ..., DTTError)
  return null
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string MultiSourceResponsible.getDescription()
{
  string computedDescription = ""
  if (length(ID) <= 1)
  {
    computedDescription = ""
  }
  else 
  {
    if (left(ID, 1) == "F")
    {
      // we first decode the function...
       
      string idAsString = replace(ID, "F", "")
      if (!(isNumber(idAsString)))
      {
         QappCore.DTTLogMessage("it shuold be a number", ..., DTTError)
      }
      int idFunzione = toInteger(idAsString)
       
      Funzione f = Funzione.getFromDB(idFunzione, ...)
       
      computedDescription = formatMessage("|1 - |2", f.CODFUNZIONE, f.DESCRFUNZIONE, ...)
    }
    else 
    {
      // ... and then the specialized part (at the moment of writing Utente or Dipendente)
       
      string specificPrefix = this.getPrefix()
      string idAsString = replace(ID, specificPrefix, "")
      if (!(isNumber(idAsString)))
      {
         QappCore.DTTLogMessage("it should be a number", ..., DTTError)
      }
      int idAsInteger = toInteger(idAsString)
       
      computedDescription = this.getSpecificDescriptionById(idAsInteger)
    }
  }
   
  return computedDescription
}


// ──────────────────────────────────

// *************************************************************************************************************************************************************
// centralized method to avoid code duplication to create a key such as U103 from values like No for ISFunction, 103 for id and "U" for "non is function" prefix
// *************************************************************************************************************************************************************
public string MultiSourceResponsible.prepareDecodingKey(
  string:flagYN isFunctionValue // 
  int idResponsibleFieldValue   // 
  string nonFunctionPrefix      // 
)
{
  if (isNull(idResponsibleFieldValue) || idResponsibleFieldValue <= 0)
  {
    return null
  }
   
  if (isNull(nonFunctionPrefix) && isFunctionValue == No)
  {
    return null
  }
   
  if (nonFunctionPrefix == "" && isFunctionValue == No)
  {
    return null
  }
   
  string firstLetter = if(isFunctionValue == Yes, "F", nonFunctionPrefix)
  string idAsString = toString(idResponsibleFieldValue)
  string computedId = firstLetter + idAsString
  return computedId
}


// ──────────────────────────────────

// *******************************************************************************************
// centralized method to minimize duplicated code, it returns void but sets 2 inout parameters
// from key "F103" it sets yes to is FunctionValue and 103 to idResponsibleFieldValue
// *******************************************************************************************
public static void MultiSourceResponsible.decodeKey(
  string key                          // 
  inout string:flagYN isFunctionValue // 
  inout int idResponsibleFieldValue   // 
)
{
  if (isNull(key) || key == "")
  {
    isFunctionValue = No
    idResponsibleFieldValue = null
    return 
  }
   
  string uniquecode = key
  string firstLetterOfUniqueCode = left((uniquecode), 1)
   
  // to catch the first letter is missing and the key contains only the number
  if (isNumber(firstLetterOfUniqueCode))
  {
    isFunctionValue = No
    idResponsibleFieldValue = null
    return 
  }
   
  string idAsString = replace(uniquecode, firstLetterOfUniqueCode, "")
  isFunctionValue = if(firstLetterOfUniqueCode == "F", Yes, No)
  idResponsibleFieldValue = toInteger(idAsString)
   
}


// ──────────────────────────────────

// **************************************************************************
// Event raised to the document when a panel requests execution of a decoding
// **************************************************************************
event MultiSourceResponsible.BeforeLookup(
  inout boolean Skip   // A boolean output parameter. If set to True, the framework will not execute the lookup via a query.
  inout boolean Cancel // A boolean output parameter. If set to True, the decoding procedure ends immediately. The AfterLookup event will not be called.
)
{
  Skip = true
   
  Description = this.getDescription()
}


// ──────────────────────────────────



// ──────────────────────────────────

// ***********************************************************************************************
// prefix used at UI side to decode the correct type of lookup (P123 = Personale 123, P is prefix)
// ***********************************************************************************************
public string FunctionOrDipendenteResponsible.getPrefix()
{
  return "P"
}


// ──────────────────────────────────



// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string FunctionOrDipendenteResponsible.getSpecificDescriptionById(
  int ID // 
)
{
  Personale personale = Personale.getFromDB(ID, ...)
  return personale.FullName
}


// ──────────────────────────────────

// ***********************************************************************************************
// prefix used at UI side to decode the correct type of lookup (P123 = Personale 123, P is prefix)
// ***********************************************************************************************
public string FunctionOrUtenteResponsible.getPrefix()
{
  return "U"
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public IDCollection FunctionOrUtenteResponsible.getIDDescriptionMapElements(
  optional string queryString = "%" // 
)
{
  IDCollection userVisibleToLoggedinUser of Utente = QappCore.Loggeduser.getUsersUtenteCanSee(queryString)
  IDCollection idDescriptionMapElements of MapElement = new()
  for each Utente u in userVisibleToLoggedinUser
  {
    MapElement me = MapElement.create(toString(u.IDUTENTE), u.DESCRUTENTE)
    idDescriptionMapElements.add(me)
  }
   
  return idDescriptionMapElements
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string FunctionOrUtenteResponsible.getSpecificDescriptionById(
  int ID // 
)
{
  if (ID > 0)
  {
    Utente utente = Utente.get(ID)
    if (utente)
      return utente.DESCRUTENTE
  }
  return ""
}


// ──────────────────────────────────

// ****************************************************************************************************************
// create a new Link document for given document and Idrevision and add in to collection of Personale doc collegati
// ****************************************************************************************************************
public void Funzione.AddDocCollegati(
  Documento Document // 
  int IdRevision     // 
)
{
  FUNDOCUMENTI fd = new()
  fd.init()
  fd.IDFUNZIONE = IDFUNZIONE
  fd.IDDOCUMENTO = Document.IDDOCUMENTO
  fd.IDREVISIONE = null
  if (IdRevision <> 0)
  {
    fd.IDREVISIONE = IdRevision
  }
  FUNDOCUMENTI.add(fd)
}


// ──────────────────────────────────

// **********************************************************************************************************
// Check if Document link already exists in collegati collection
// this method is require to validate/check when we create Document link to prevent Duplicate error on saving
// **********************************************************************************************************
public boolean Funzione.DocumentLinkAlreadyExists(
  Documento document // 
)
{
  boolean sameDocumentLinkAlreadyExists = false
  this.loadCollectionFromDB(FUNDOCUMENTI, 0)
  for each FUNDOCUMENTI fundocumenti in FUNDOCUMENTI
  {
    if (fundocumenti.IDDOCUMENTO == document.IDDOCUMENTO)
    {
      sameDocumentLinkAlreadyExists = true
      break 
    }
  }
  return sameDocumentLinkAlreadyExists
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public int Funzione.IDfromDescription(
  string description // 
)
{
   
  string cleanDescription = replace(upper(description), " ", "")
   
  QualibusDB.maxRows = 1
  int matchingID = 0
  select into variables (found variable)
    set matchingID = IDFUNZIONE
  from 
    Funzioni // master table
  where
    (upper(replace(DESCRFUNZIONE, " ", "")) = cleanDescription) or (upper(replace(CODFUNZIONE, " ", "")) = cleanDescription)
   
  return matchingID
   
   
}


// ──────────────────────────────────

// **********************************************************************************************
// it returns the number of active users indirectly contained in the MSQ PERS FUNZIONI collection
// **********************************************************************************************
public int Funzione.getActiveMemmbersCount()
{
  this.loadFunzioneMsqPersFunzioniColl()
   
  int activeMembersCount = 0
   
  for each FunzioneMembers fm in MSQPERSFUNZIONI
  {
    Personale p = Personale.getFromDB(fm.IDDIPENDENTE, quickLoad)
    if (p.IDUTENTE > 0)
    {
      Utente u = Utente.get(p.IDUTENTE)
      if (u.ATTIVO == Yes)
      {
         activeMembersCount = activeMembersCount + 1
      }
    }
  }
   
  return activeMembersCount
}


// ──────────────────────────────────

// *******************************************************************************************************************
// it returns the property index of the property linked to the main image field of the class (e.g. ART_ANAGRAFICA.FOTO
// *******************************************************************************************************************
public int Funzione.PROTECTEDgetMainImagePropertyIndex()
{
  return null
}


// ──────────────────────────────────

// ****************************************************************************************************************************
// it returns the property index of the property linked to the second image field of the class (e.g. ART_ANAGRAFICA.FOTO_SECOND
// ****************************************************************************************************************************
public int Funzione.PROTECTEDgetSecondImagePropertyIndex()
{
  return null
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string Funzione.getFullDescription()
{
  return formatMessage("|1 |2", CODFUNZIONE, DESCRFUNZIONE, ...)
}


// ──────────────────────────────────

// ***************************************************************
// this procedure returns true if it contains the personale in arg
// ***************************************************************
public boolean Funzione.containsPersonale(
  Personale employee // 
)
{
  boolean containsPersonale = false
  for each FunzioneMembers fm in MSQPERSFUNZIONI
  {
    if (fm.IDDIPENDENTE == employee.IDDIPENDENTE)
    {
      containsPersonale = true
      break 
    }
  }
  return containsPersonale
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public Recordset Funzione.createSmartLookupRecordset()
{
  string queryString = if(FullDescription == "*", "", FullDescription)
   
  IDCollection collectionOfFunzioniUserCanSee of Funzione = QappCore.Loggeduser.getFunzioniUtenteCanSee(queryString)
   
  Recordset r = new()
  RecordsetMetaData rmd = new()
   
  rmd.setColumnCount(2)
  rmd.setFieldName(1, "IDFUNZIONE")
  rmd.setFieldName(2, "FULLDESCRIPTION")
  rmd.setFieldType(1, Character)
  rmd.setFieldType(2, Character)
  r.setMetaData(rmd)
   
  for each Funzione funzioneUserCanSee in collectionOfFunzioniUserCanSee
  {
    Tools.addRowToRecordset(r, toString(funzioneUserCanSee.IDFUNZIONE), funzioneUserCanSee.FullDescription)
  }
  r.addSortCriteria(2)
  r.doSort()
   
  return r
}


// ──────────────────────────────────



// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean Funzione.isLocked()
{
  return false
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean Funzione.isClosed()
{
  return ATTIVA == No
}


// ──────────────────────────────────

// ************************************************************************************************
// Returns the active utenti (NGT_UTENTE) of this Funzione, walking
// MSQ_PERS_FUNZIONI -> PER_ANAGRAFICA -> NGT_UTENTE. Only utenti with ATTIVO=Yes are included, and
// only members whose Personale row is actually linked to a NGT_UTENTE (IDUTENTE > 0).
// 
// Returned as IDCollection of Utente so the caller (picker form) can attach it directly to a
// panel without column mapping.
// ************************************************************************************************
public IDCollection Funzione.getActiveMembersAsUtenti()
{
  IDCollection activeUtenti of Utente = new()
  if (!(MSQPERSFUNZIONI.loaded))
    this.loadCollectionFromDB(MSQPERSFUNZIONI, ...)
   
  for each FunzioneMembers fm in MSQPERSFUNZIONI
  {
    Personale p = Personale.getFromDB(fm.IDDIPENDENTE, quickLoad)
    if (p.IDUTENTE > 0 and p.STATO == Attivo)
    {
      Utente u = Utente.get(p.IDUTENTE)
      if (u != null and u.ATTIVO == Yes)
      {
         activeUtenti.add(u)
      }
    }
  }
   
  return activeUtenti
}


// ──────────────────────────────────

// ***********************************************************************************************
//  Returns the function's priority member as a Utente, or null when no priority member is defined
// or the priority row's utente is not usable (inactive, unlinked, or link inactive).
// 
// By domain rule a function has AT MOST ONE PRIORITY_MEMBER = Yes row. This is the priority
// shortcut Delphi uses: when a usable priority member exists, it IS the responsabile.
// ***********************************************************************************************
public Utente Funzione.getPriorityMemberAsUtenti()
{
  if (!(MSQPERSFUNZIONI.loaded))
    this.loadCollectionFromDB(MSQPERSFUNZIONI, ...)
   
  for each FunzioneMembers fm in MSQPERSFUNZIONI
  {
    if (fm.PRIORITYMEMBER == Yes)
    {
      Personale p = Personale.getFromDB(fm.IDDIPENDENTE, quickLoad)
      if (p.IDUTENTE > 0 and p.STATO == Attivo)
      {
         Utente u = Utente.get(p.IDUTENTE)
         if (u != null and u.ATTIVO == Yes)
         {
           return u
         }
      }
      break 
    }
  }
   
  return null
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Funzione.loadFunzioneMsqPersFunzioniColl()
{
  if (!(MSQPERSFUNZIONI.loaded))
  {
    this.loadCollectionFromDB(MSQPERSFUNZIONI, ...)
  }
}


// ──────────────────────────────────

// ***************************************
// Descrivi lo scopo di questa funzione...
// ***************************************
public int Funzione.getMainID()
{
  return IDFUNZIONE
}


// ──────────────────────────────────



// ──────────────────────────────────

// ***************************************
// Descrivi lo scopo di questa funzione...
// ***************************************
public int Funzione.getKordApp()
{
  return Funzioni
}


// ──────────────────────────────────

// ****************************************
// Describe the purpose of this function...
// ****************************************
public string Funzione.getDescription()
{
  return DESCRFUNZIONE
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static Funzione Funzione.getFromDB(
  int mainId                                                             // 
  optional string:getFromDBLoadingModes loadingMode = "normalZeroLevels" // 
)
{
  Funzione f = null
   
  try 
  {
    f = cast(MainModule.retrieve(Funzioni, mainId, loadingMode))
  }
  catch 
  {
    f = null
  }
   
  return f
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event Funzione.OnInit()
{
  IDFUNZIONE = Sequence.getNextSequence(MSQN_ID_FUNZIONE, ...)
  CODFUNZIONE = "TEST"
  DESCRFUNZIONE = "FUNZIONE TEST"
  ATTIVA = Yes
  PROFID = 0
  COLORE = 16777215
  FORMA = 0
  TEAM = 1
  VISUALIZZAMEMBRI = Yes
}


// ──────────────────────────────────

// **************************************************************************************
// Event raised to the document during the initial steps of the document saving procedure
// **************************************************************************************
event throws exception Funzione.BeforeSave(
  inout boolean Skip         // A boolean output parameter. If set to True, tells the framework not to perform operations on the database, because they will be managed independently.
  inout boolean Cancel       // A boolean output parameter. If set to True, tells the framework to cancel the save procedure due to errors that occurred. Since the entire document is saved in the same transa...
  int:beforeSavePhases Phase // An integer from 0 to 3 that specifies the progress status of the save procedure as detailed at the beginning of the article.
)
{
  if (deleted)
  {
    this.loadFunzioneMsqPersFunzioniColl()
     
    for each FunzioneMembers msqpersfunzioni in MSQPERSFUNZIONI
    {
      msqpersfunzioni.deleted = true
      msqpersfunzioni.saveToDB(...)
    }
  }
}


// ──────────────────────────────────

// ************************************************************
// Event raised by the document after loading from the database
// ************************************************************
event Funzione.AfterLoad(
  boolean AlreadyLoaded     // A boolean parameter that specifies whether the document was previously loaded.
  IDArray LoadedCollections // IDArray type parameter that contains the collections loaded by the framework. If the value of the Already Loaded parameter is False, the Loaded Collections parameter is NULL.
)
{
  base.AfterLoad(AlreadyLoaded, LoadedCollections)
   
  boolean IsQuickLoadRequested = base.quickLoadRequested()
   
  if (IsQuickLoadRequested)
  {
    return 
  }
   
  // implement here code that would be skipped by QuickLoad
   
  FullDescription = CODFUNZIONE + " - " + DESCRFUNZIONE
}


// ──────────────────────────────────

// **************************************************************************
// Event called to customize the Smart Lookup procedure at the document level
// **************************************************************************
event Funzione.OnGetSmartLookup(
  Recordset RecordSet       // The recordset to be filled if the queries run during the Smart Lookup event are customized.
  inout int Level           // The Smart Lookup procedure performs multiple attempts, increasingly widening the search field. This integer parameter specifies the number of attempts made.
  inout boolean NullValue   // A boolean output parameter. If set to True, then NULL (no results found) will be returned to the panel.
  inout boolean Skip        // A boolean output parameter. If set to True, then the standard queries for the current level will not be executed.
  inout boolean Cancel      // A boolean output parameter. If set to True, the Smart Lookup procedure will be stopped.
  IDDocument CallerDocument // The document for which the smart lookup procedure is launched. It can be null.
)
{
  Skip = true
  Cancel = true
   
  Recordset r = this.createSmartLookupRecordset()
   
  RecordSet.copyFrom(r)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static FunzioneMembers FunzioneMembers.create(
  int idDipendente // 
  int idFunzione   // 
)
{
  FunzioneMembers fm = new()
  fm.init()
   
  fm.IDDIPENDENTE = idDipendente
  fm.IDFUNZIONE = idFunzione
  fm.computeAdditionalProperties()
   
  return fm
}


// ──────────────────────────────────

// *************************************
// Load from DB the dipendente FULL NAME
// *************************************
public void FunzioneMembers.computeAdditionalProperties()
{
  if (IDDIPENDENTE > 0)
  {
    if (Personale == null or (Personale.IDDIPENDENTE == 0))
    {
      Personale = Personale.getFromDB(IDDIPENDENTE, ...)
    }
     
    if (Personale)
    {
      DipendenteFullName = Personale.FullName
      DipendenteIDUTENTE = Personale.IDUTENTE
      this.setOriginalValue(toPropertyIndex(DipendenteFullName), DipendenteFullName)
    }
  }
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event FunzioneMembers.OnInit()
{
  PROFID = 0
  PRIORITYMEMBER = No
}


// ──────────────────────────────────

// ************************************************************
// Event raised by the document after loading from the database
// ************************************************************
event FunzioneMembers.AfterLoad(
  boolean AlreadyLoaded     // A boolean parameter that specifies whether the document was previously loaded.
  IDArray LoadedCollections // IDArray type parameter that contains the collections loaded by the framework. If the value of the Already Loaded parameter is False, the Loaded Collections parameter is NULL.
)
{
  this.computeAdditionalProperties()
  this.setOriginal()
}


// ──────────────────────────────────

// ************************************************************
// Event raised to the document during the validation procedure
// ************************************************************
event FunzioneMembers.OnValidate(
  int:validateReasons Reason // An integer specifying the reason the document is being validated. The most common values are 0 to indicate complete validation before saving, or 1 to indicate a quick validati...
  inout boolean Error        // A boolean output parameter. If set to True, specifies an error condition and stops the validation immediately.
  inout boolean Skip         // A boolean output parameter. If set to True, does not check the rules for automatic validation of the document (see notes).
)
{
  // you can set priority member to true only if the personale is a valid user
  if (PRIORITYMEMBER == Yes)
  {
    if (DipendenteIDUTENTE == null or DipendenteIDUTENTE == 0)
    {
      string errorMessage = formatMessage("|1 non ha un utente corrispondente, scegli un altro personale o aggiungine uno con utente alla funzione", upper(DipendenteFullName), ...)
      this.setPropertyError(errorMessage, PRIORITYMEMBER)
      Error = true
    }
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean FunzioneModuleType.shouldTabBeVisible(
  string subformType // 
)
{
  // funzione has no type, so we show all tabs. could be implemented the visibility based on the roles of the user
   
  QappCore.DTTLogMessage("funzione has no type, so we show all tabs. could be implemented the visibility based on the roles of the user", ...)
  return true
}


// ──────────────────────────────────

// ********************************************************************************************************************
// given a CAP it returns a string like
// 33090 (ARBA, CASTELNOVO DEL FRIULI, CLAUZETTO, SEQUALS, TRAMONTI DI SOPRA, TRAMONTI DI SOTTO, TRAVESIO, VITO D'ASIO)
// ********************************************************************************************************************
public string Comune.GetCompleteCapDescription()
{
  IDCollection allComuni of Comune = new()
  select into collection distinct (allComuni)
  from 
    Comune // master table
  where
    CAP == CAP
   
  allComuni.addSortCriteria(toPropertyIndex(DESCRCOMUNE))
  allComuni.doSort()
   
  string concatenatedComuni = ""
   
  for each Comune c in allComuni
  {
    concatenatedComuni = SH.Concat(concatenatedComuni, c.DESCRCOMUNE, ", ")
  }
   
  string completeString = formatMessage("|1 (|2)", CAP, concatenatedComuni, ...)
   
  return completeString
}


// ──────────────────────────────────

// ****************************************************************
// given a CAP a collection with all Comuni of that CAP is returned
// ****************************************************************
public static IDCollection Comune.getComuniByCAP(
  string CAP // 
)
{
  IDCollection foundComuni of Comune = new()
  select into collection (foundComuni)
  from 
    Comune // master table
  where
    CAP == CAP
   
  return foundComuni
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static string TabParametri.getQualibusMajorVersion()
{
  QualibusDB.maxRows = 1
  string vQualibusMajorVersion = ""
  select into variables (found variable)
    set vQualibusMajorVersion = QUALIBUSMAJORVERSION
  from 
    TABPARAMETRI1 // master table
   
  return vQualibusMajorVersion
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static TabParametri TabParametri.getInstance()
{
  IDCollection tabParametriCollection of TabParametri = new()
  select into collection (tabParametriCollection)
  from 
    TabParametri // master table
   
  if (tabParametriCollection.count() > 1)
  {
    QappCore.DTTLogMessage("more than 1 record found in TabParametri1", ..., DTTError)
  }
   
  tabParametriCollection.moveFirst()
   
  return tabParametriCollection.getAt()
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static void TabParametri.createQualibusWebBaseUrlFieldIfNeeded()
{
  string ifNotFieldExistsSqlCommand = "IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'TAB_PARAMETRI_1' AND  COLUMN_NAME = 'QUALIBUS_WEB_BASE_URL') ALTER TABLE TAB_PARAMETRI_1 ADD 
           QUALIBUS_WEB_BASE_URL nvarchar(256) NULL"
   
  try 
  {
    QappCore.DTTLogMessage("executing check on TAB_PARAMETRI_1.QUALIBUS_WEB_BASE_URL field", ..., DTTInfo)
    QualibusDB.SQLExecute(ifNotFieldExistsSqlCommand)
  }
  catch 
  {
    QappCore.DTTLogMessage("Unable to execute the script that adds QUALIBUS_WEB_BASE_URL to TAB_PARAMETRI_1", ..., DTTError)
  }
   
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static string TabParametri.getDbAdminUsername()
{
  QualibusDB.maxRows = 1
  string vAdminUsername = ""
  select into variables (found variable)
    set vAdminUsername = ADMINUSERNAME
  from 
    TABPARAMETRI1 // master table
   
  return vAdminUsername
}


// ──────────────────────────────────

// ******************
// get anonymous user
// ******************
public static Utente TabParametri.getAnonymousUser()
{
  int idUtenteAnonymous = 0
  select into variables (found variable)
    set idUtenteAnonymous = IDUTENTEANONYMOUS
  from 
    TABPARAMETRI1 // master table
   
  Utente user = new()
  user.IDUTENTE = idUtenteAnonymous
  try 
  {
    user.loadFromDB(...)
  }
  catch 
  {
    user = null
    QappCore.DTTLogMessage("The anonymous user not found", ..., DTTError)
  }
  return user
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static void TabParametri.createIdUtenteAnonymousFieldIfNeeded()
{
  string ifNotFieldExistsSqlCommand = "IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'TAB_PARAMETRI_1' AND COLUMN_NAME = 'ID_UTENTE_ANONYMOUS') BEGIN ALTER TABLE TAB_PARAMETRI_1 
           ADD ID_UTENTE_ANONYMOUS int NULL ALTER TABLE TAB_PARAMETRI_1 WITH CHECK ADD CONSTRAINT FK_TAB_PARAMETRI_ID_UTENTE_ANONYMOUS FOREIGN KEY(ID_UTENTE_ANONYMOUS) REFERENCES NGT_UTENTI (ID_UTENTE) ALTER 
           TABLE TAB_PARAMETRI_1 CHECK CONSTRAINT FK_TAB_PARAMETRI_ID_UTENTE_ANONYMOUS declare @ID_UTENTE_ANONYMOUS INT set @ID_UTENTE_ANONYMOUS = (SELECT TOP 1 ID_UTENTE FROM NGT_UTENTI NU WHERE COD_UTENTE lik­
           e '%anonimo%') if @ID_UTENTE_ANONYMOUS iS NULL BEGIN declare @LastId int set @LastId = (select LAST_ID from SW9_SEQUENCES where SEQ_NAME = 'NGTN_UTENTI') declare @IdReparto int set @IdReparto = 
           (select top 1 ID_REPARTO from NGT_REPARTI where DESCR_REPARTO like '%virtual%') if @IdReparto IS NULL BEGIN set @IdReparto = (select top 1 ID_REPARTO from NGT_REPARTI) END INSERT INTO NGT_UTENTI 
           ([ID_UTENTE],[COD_UTENTE],[DESCR_UTENTE],[TIPO_UTENTE],[DESIGN_REPORT],[ID_REPARTO],[ATTIVO], [SHARED_ABSOLUTE],[PROF_ID],[CAN_LOGIN], [IS_PROF_ADMIN],[EMAIL_NOTIFICATIONS],[TEMPLATES],[CAN_LOGIN_TAG­
           APP],[LIGHT_LOGIN],[FORCE_CHANGE_PASSWORD],[PASSWORD_CHANGE_DATETIME]) VALUES (@LastId+1, 'anonimo', 'utente anonimo',0,'N',@IdReparto,'Y', 'N',0,'Y', 'N','N','Y','N','N','N', GETDATE()) UPDATE 
           SW9_SEQUENCES SET LAST_ID = @LastId+1 WHERE SEQ_NAME = 'NGTN_UTENTI' END END "
   
  try 
  {
    QappCore.DTTLogMessage("executing check on TAB_PARAMETRI_1.ID_UTENTE_ANONYMOUS field", ..., DTTInfo)
    QualibusDB.SQLExecute(ifNotFieldExistsSqlCommand)
  }
  catch 
  {
    QappCore.DTTLogMessage("Unable to execute the script that adds ID_UTENTE_ANONYMOUS to TAB_PARAMETRI_1", ..., DTTError)
  }
   
  string updateIdUtenteAnonymous = "UPDATE TAB_PARAMETRI_1 SET ID_UTENTE_ANONYMOUS = (SELECT TOP 1 ID_UTENTE FROM NGT_UTENTI NU WHERE COD_UTENTE like '%anonimo%' and ATTIVO = 'Y')"
  try 
  {
    QualibusDB.SQLExecute(updateIdUtenteAnonymous)
  }
  catch 
  {
    QappCore.DTTLogMessage("Unable to set the value of ID_UTENTE_ANONYMOUS", ..., DTTError)
  }
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static string TabParametri.extractPrintLogoToImageFolder(
  string filenameWithoutExtension // 
)
{
  TabParametri tp = TabParametri.getInstance()
   
  string fileExtension = "undefined"
  string filename = filenameWithoutExtension + "." + fileExtension
  string imageFolder = QappCore.path() + FH.getSeparator() + "images"
   
  string storedblobpath = saveBlobFile(tp.PRINTLOGO, imageFolder, filename)
  fileExtension = tp.computeExetnsion()
   
  string filenameInImagesFolder = replace(filename, "undefined", fileExtension)
  string filepathWithCorrectExtension = replace(storedblobpath, "undefined", fileExtension)
  QappCore.deleteFile(filepathWithCorrectExtension)
  QappCore.renameFile(storedblobpath, filepathWithCorrectExtension)
  QappCore.addTempFile(filepathWithCorrectExtension)
  return filenameInImagesFolder
}


// ──────────────────────────────────

// ********************************************************************************
// the imagefile extension of the file contained in the PRINT_LOGO blob is computed
// ********************************************************************************
private string TabParametri.computeExetnsion()
{
   
  IDDatabase idd = cast(QualibusDB.me())
  string copmutedExtension = Tools.extractFileExtension(idd, "TAB_PARAMETRI_1", "PRINT_LOGO", ...)
  return copmutedExtension
}


// ──────────────────────────────────

// **********************************************************
// returns "ldap://IP:Port" using the data in Tab Parametri 1
// 
// in java ldap:// in .net LDAP:// (result of empirical test)
// **********************************************************
public string TabParametri.getLdapCompleteAddress()
{
  string ldapString = "ldap"
  dotNet code
  {
    ldapString = "LDAP"
  }
   
  return formatMessage("|1://|2:|3", ldapString, LDAPIPADDRESS, LDAPPORT, ...)
}


// ──────────────────────────────────

// ****************
// get bacheca user
// ****************
public static Utente TabParametri.getBachecaUser()
{
  int idUtenteBacheca = 0
  select into variables (found variable)
    set idUtenteBacheca = IDUTENTEBACHECA
  from 
    TABPARAMETRI1 // master table
   
  Utente user = new()
  user.IDUTENTE = idUtenteBacheca
  try 
  {
    user.loadFromDB(...)
  }
  catch 
  {
    user = null
    QappCore.DTTLogMessage("The bacheca user not found", ..., DTTError)
  }
  return user
}


// ──────────────────────────────────

********************************************************* opens the document in browser and writes the document log *********************************************************

// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public IDCollection Provincia.getCliFor()
{
  IDCollection givenProvinciaCliFor of Clifor = new()
  select into collection (givenProvinciaCliFor)
  from 
    Clifor // master table
  where
    CODPROVINCIA == CODPROVINCIA
   
  return givenProvinciaCliFor
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static UrlToken UrlToken.createByINfoContentOLD(
  string infoContent // 
)
{
  UrlToken ut = new()
  ut.init()
  ut.INFOCONTENT = infoContent
  ut.generateGUID()
   
  return ut
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void UrlToken.generateGUID()
{
  GUID = docIDToGuid(newDocID())
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static UrlToken UrlToken.getByGuid(
  string GUID // 
)
{
  UrlToken ut = new()
  ut.GUID = GUID
   
  try 
  {
    ut.loadFromDB(...)
  }
  catch 
  {
    ut = null
    QappCore.DTTLogMessage("URLToken not found", ..., DTTError)
  }
  return ut
}


// ──────────────────────────────────

// *********************************************
// if found in db is retrieved otherwise created
// *********************************************
public static UrlToken UrlToken.create(
  int mainID                  // 
  string:tokenTypes tokenType // 
)
{
  UrlToken ut = this.retrieve(mainID, tokenType)
  if (ut == null)
  {
    ut = new()
    ut.init()
    ut.MAINID = mainID
    ut.TOKENTYPE = tokenType
  }
   
  return ut
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static UrlToken UrlToken.retrieve(
  int mainID                  // 
  string:tokenTypes tokenType // 
)
{
  UrlToken ut = new()
  ut.MAINID = mainID
  ut.TOKENTYPE = tokenType
  try 
  {
    DevTools.ToBeReviewed("Since sometimes this query returns more than 1 value and we dont know the exact reason, we set max rows = 1 as a work around so the catch will fire only when there are no values in 
          the query")
    QualibusDB.maxRows = 1
     
    ut.loadFromDB(...)
  }
  catch 
  {
    ut = null
    QappCore.DTTLogMessage(formatMessage("The url token for mainid: |1 and tokenType: |2 not found.", mainID, tokenType, ...), ...)
  }
  return ut
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void UrlToken.prepareUrlGenerator()
{
  switch (TOKENTYPE)
  {
    case modelloEventoBased:
    break
    case classEventoBased:
    break
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void UrlToken.computeURL()
{
  string startEventoUrl = formatMessage("|1?CMD=|2&INFO=|3", X.GetApplicationUrl(...), startEventoWizard, GUID, ...)
   
  Url = startEventoUrl
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void UrlToken.ComputeAll()
{
  if (CustomDataViewMode == null)
    QappCore.DTTLogMessage("CustomDataViewMode is not defined", ..., DTTError)
   
  this.computeInfoContent()
  this.computeURL()
}


// ──────────────────────────────────

// **************************************************************************************************************************************************************************************************
// this method is needed to handle the non set cases, it happens when a link created with an old version of Qualibus does not have a value for one of the properties and so we handle them explicitly
// **************************************************************************************************************************************************************************************************
private void UrlToken.setDefaultValueToProperties()
{
  // check the case 0 == null that is true for INDE
  if (AllowChangeDate == null and AllowChangeDate != 0)
  {
    AllowChangeDate = true
  }
  if (AnonymousLogin == null)
  {
    AnonymousLogin = false
  }
  if (HideTipologia == null)
  {
    HideTipologia = false
  }
  if (HideAmbito == null)
  {
    HideAmbito = false
  }
  if (HideTitle == null)
  {
    HideTitle = false
  }
  if (AllowDocumentsUpload == null)
  {
    AllowDocumentsUpload = false
  }
  if (SbloccaCampiReadonly == null)
  {
    SbloccaCampiReadonly = false
  }
  if (PositionButtonCreaEvento == null)
  {
    PositionButtonCreaEvento = Down
  }
  if (VisualizzaIstruzioni == null)
  {
    VisualizzaIstruzioni = false
  }
  if (NascondiDisposizioni == null)
  {
    NascondiDisposizioni = false
  }
  if (ConfirmationMessageTitle == null)
  {
    ConfirmationMessageTitle = this.getDefaultConfirmationMessageTitle()
  }
  if (ConfirmationEventoCreatedDescription == null)
  {
    ConfirmationEventoCreatedDescription = this.getDefaultConfirmationEventoCreatedDescripion()
  }
  if (CreateEventoButtonCaption == null)
  {
    CreateEventoButtonCaption = this.getDefaultCreateEventoButtonLabel()
  }
  if (GUIDInfoDescription == null)
  {
    GUIDInfoDescription = this.getDefaultGUIDInfoDesription()
  }
  if (FirstWizardPageTitle == null)
  {
    FirstWizardPageTitle = this.getDefaultFirstWizardPageTitle()
  }
  if (SecondWizardPageTitle == null)
  {
    SecondWizardPageTitle = this.getDefaultSecondWizardPageTitle()
  }
  if (ConsultazionePublicInfoTitle == null)
  {
    ConsultazionePublicInfoTitle = this.getDefaultConsultazionePublicInfoTitle()
  }
  if (ConsultazionePublicInfoDescription == null)
  {
    ConsultazionePublicInfoDescription = this.getDefaultConsultazionePublicInfoDescription()
  }
  if (CustomDataWording == null)
  {
    CustomDataWording = this.getDefaultCustomDataWording()
  }
   
  if (AnonymousLogin)
  {
    NascondiDisposizioni = true
  }
  if (ChiusuraAutomatica == null)
  {
    ChiusuraAutomatica = false
    EventoClosureEsito = ""
  }
   
}


// ──────────────────────────────────

// *************************************************************************
// this procedure Computes infoContent and humanredableInfo (that are same?)
// *************************************************************************
public void UrlToken.computeInfoContent()
{
  IDMap content = new()
  content.setValue(MAIN_ID, MAINID)
  content.setValue(CUSTOMDATA_VIEW_MODE, CustomDataViewMode)
  content.setValue(ANONYMOUS_LOGIN, AnonymousLogin)
  content.setValue(ALLOW_CHANGE_DATE, AllowChangeDate)
  content.setValue(HIDE_TIPOLOGIA, HideTipologia)
  content.setValue(HIDE_AMBITO, HideAmbito)
  content.setValue(HIDE_TITOLO, HideTitle)
  content.setValue(VISUALIZZA_ISTRUZIONI, VisualizzaIstruzioni)
  content.setValue(HIDE_DISPOSIZIONI, NascondiDisposizioni)
  content.setValue(ALLOW_DOCUMENTS_UPLOAD, AllowDocumentsUpload)
  content.setValue(ALLOW_EDIT_READONLY_FIELDS, SbloccaCampiReadonly)
  content.setValue(POSITION_BUTTON_CREA_EVENTO, PositionButtonCreaEvento)
  content.setValue(TITOLO_MESSAGGIO_CONFERMA, ConfirmationMessageTitle)
  content.setValue(CONTENUTO_MESSAGGIO_CONFERMA, ConfirmationEventoCreatedDescription)
  content.setValue(CREA_EVENTO_BUTTON_CAPTION, CreateEventoButtonCaption)
  content.setValue(GUID_INFO_DESCRIPTION_TEXT, GUIDInfoDescription)
  content.setValue(FIRST_WIZARD_STEP_TITLE, FirstWizardPageTitle)
  content.setValue(SECOND_WIZARD_STEP_TITLE, SecondWizardPageTitle)
  content.setValue(CONSULTAZIONE_PUBLIC_INFO_TITLE, ConsultazionePublicInfoTitle)
  content.setValue(CONSULTAZIONE_PUBLIC_INFO_DESCRIPTION, ConsultazionePublicInfoDescription)
  content.setValue(CUSTOM_DATA_WORDING, CustomDataWording)
  content.setValue(REDIRECT_URL, RedirectUrlAfterEventoInsertion)
  content.setValue(DISPLAY_LOGO, DisplayLogo)
  content.setValue(CHIUSURAAUTOMATICA, ChiusuraAutomatica)
  content.setValue(EVENTOCLOSUREESITO, EventoClosureEsito)
   
  string infoContent = JSON.stringify(content)
  INFOCONTENT = infoContent
  HumanReadableInfo = infoContent
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void UrlToken.readDataFromInfoContent()
{
  if (INFOCONTENT != "")
  {
    // reading json from db...
    object obj = JSON.parse(INFOCONTENT)
    IDMap idm = cast(obj)
     
    // ... and setting the properties
    CustomDataViewMode = idm.getValue(CUSTOMDATA_VIEW_MODE)
    AnonymousLogin = idm.getValue(ANONYMOUS_LOGIN)
    AllowChangeDate = idm.getValue(ALLOW_CHANGE_DATE)
    HideTipologia = idm.getValue(HIDE_TIPOLOGIA)
    HideAmbito = idm.getValue(HIDE_AMBITO)
    HideTitle = idm.getValue(HIDE_TITOLO)
    AllowDocumentsUpload = idm.getValue(ALLOW_DOCUMENTS_UPLOAD)
    SbloccaCampiReadonly = idm.getValue(ALLOW_EDIT_READONLY_FIELDS)
    PositionButtonCreaEvento = idm.getValue(POSITION_BUTTON_CREA_EVENTO)
    VisualizzaIstruzioni = idm.getValue(VISUALIZZA_ISTRUZIONI)
    NascondiDisposizioni = idm.getValue(HIDE_DISPOSIZIONI)
    ConfirmationMessageTitle = idm.getValue(TITOLO_MESSAGGIO_CONFERMA)
    ConfirmationEventoCreatedDescription = idm.getValue(CONTENUTO_MESSAGGIO_CONFERMA)
    CreateEventoButtonCaption = idm.getValue(CREA_EVENTO_BUTTON_CAPTION)
    GUIDInfoDescription = idm.getValue(GUID_INFO_DESCRIPTION_TEXT)
    FirstWizardPageTitle = idm.getValue(FIRST_WIZARD_STEP_TITLE)
    SecondWizardPageTitle = idm.getValue(SECOND_WIZARD_STEP_TITLE)
    ConsultazionePublicInfoTitle = idm.getValue(CONSULTAZIONE_PUBLIC_INFO_TITLE)
    ConsultazionePublicInfoDescription = idm.getValue(CONSULTAZIONE_PUBLIC_INFO_DESCRIPTION)
    CustomDataWording = idm.getValue(CUSTOM_DATA_WORDING)
    RedirectUrlAfterEventoInsertion = idm.getValue(REDIRECT_URL)
    DisplayLogo = idm.getValue(DISPLAY_LOGO)
    ChiusuraAutomatica = idm.getValue(CHIUSURAAUTOMATICA)
    EventoClosureEsito = idm.getValue(EVENTOCLOSUREESITO)
     
     
    // if any of the above are null we set an hardcoded meaningful default in this method
    this.setDefaultValueToProperties()
  }
}


// ──────────────────────────────────

// *****************************************************************
// centralized defautl value for Confirmation Message Title property
// 
// *****************************************************************
private string UrlToken.getDefaultConfirmationMessageTitle()
{
  return "Evento salvato"
}


// ──────────────────────────────────

// *********************************************************
// centralized defautl value for Created Desciption property
// 
// <NRO_EVENTO> must be used to force the replacing
// 
// *********************************************************
private string UrlToken.getDefaultConfirmationEventoCreatedDescripion()
{
  return "Evento [NRO_EVENTO] creato con successo"
}


// ──────────────────────────────────

// *****************************************************************
// centralized defautl value for Confirmation Message Title property
// 
// *****************************************************************
private string UrlToken.getDefaultCreateEventoButtonLabel()
{
  return "Crea evento"
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string UrlToken.getCompleteCreaEventoCaption()
{
  return "{{icon-fa-plus}} " + CreateEventoButtonCaption
}


// ──────────────────────────────────

// *****************************************************************
// centralized defautl value for Confirmation Message Title property
// 
// *****************************************************************
private string UrlToken.getDefaultGUIDInfoDesription()
{
  return "Il codice univoco collegato a questo evento è [GUID], premi sul pulsante per copiarlo negli appunti e usarlo in seguito per consultazione"
}


// ──────────────────────────────────

// *****************************************************************
// centralized defautl value for Confirmation Message Title property
// 
// *****************************************************************
private string UrlToken.getDefaultFirstWizardPageTitle()
{
  return "Inserimento '[CLASSE_EVENTO]'"
}


// ──────────────────────────────────

// *****************************************************************
// centralized defautl value for Confirmation Message Title property
// 
// *****************************************************************
private string UrlToken.getDefaultSecondWizardPageTitle()
{
  return "Inserimento ‘[CLASSE_EVENTO]' - modello: '[MODELLO]'"
}


// ──────────────────────────────────

// *****************************************************************
// centralized defautl value for Confirmation Message Title property
// 
// *****************************************************************
private string UrlToken.getDefaultConsultazionePublicInfoTitle()
{
  return "Consultazione stato pubblico eventi"
}


// ──────────────────────────────────



// ──────────────────────────────────

// *****************************************************************
// centralized defautl value for Confirmation Message Title property
// 
// *****************************************************************
private string UrlToken.getDefaultCustomDataWording()
{
  return "Dati personalizzati"
}


// ──────────────────────────────────

// ***********************
// setter to pass the GUID
// ***********************
public void UrlToken.setGuid(
  string value // 
)
{
  GUID = value
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void UrlToken.setEsitiMap(
  IDMap esitiMap // 
)
{
  EsitiMap = esitiMap
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event UrlToken.OnInit()
{
  TIMESTAMP = now()
  CustomDataViewMode = All
  this.setDefaultValueToProperties()
  this.generateGUID()
  ConfirmationMessageTitle = this.getDefaultConfirmationMessageTitle()
  ConfirmationEventoCreatedDescription = this.getDefaultConfirmationEventoCreatedDescripion()
  CreateEventoButtonCaption = this.getDefaultCreateEventoButtonLabel()
  GUIDInfoDescription = this.getDefaultGUIDInfoDesription()
}


// ──────────────────────────────────

// ************************************************************
// Event raised by the document after loading from the database
// ************************************************************
event UrlToken.AfterLoad(
  boolean AlreadyLoaded     // A boolean parameter that specifies whether the document was previously loaded.
  IDArray LoadedCollections // IDArray type parameter that contains the collections loaded by the framework. If the value of the Already Loaded parameter is False, the Loaded Collections parameter is NULL.
)
{
  this.readDataFromInfoContent()
  this.setOriginal()
}


// ──────────────────────────────────

// ***********************************************************
// Event raised to the document when the transaction is closed
// ***********************************************************
event UrlToken.OnEndTransaction()
{
   
  DevTools.ToBeReviewed("this event handler is used instead of onvalidate, this is a quick and dirty solution done in past and onValidate should be used instead")
   
  if (wasModified(AnonymousLogin) and AnonymousLogin)
  {
    string errorMessageForAnonymous = getTag("anonymousError")
    if (errorMessageForAnonymous != "")
    {
      AnonymousLogin = false
      QappCore.messageBox(errorMessageForAnonymous)
      return 
    }
  }
  else if (wasModified(ConfirmationEventoCreatedDescription))
  {
    boolean NroEventoContained = find(ConfirmationEventoCreatedDescription, "[NRO_EVENTO]", ...) > 0
    if (!(NroEventoContained))
    {
      QappCore.messageBox("È necessario incluedere il tag [NRO_EVENTO] nel testo!")
      return 
    }
  }
  else if (wasModified(GUIDInfoDescription))
  {
    boolean GUIDcontained = find(GUIDInfoDescription, "[GUID]", ...) > 0
    if (!(GUIDcontained))
    {
      QappCore.messageBox("È necessario incluedere il tag [GUID] nel testo!")
      return 
    }
  }
  else if (wasModified(RedirectUrlAfterEventoInsertion) and RedirectUrlAfterEventoInsertion != "")
  {
    boolean startsWithHttp = left(RedirectUrlAfterEventoInsertion, 4) == "http"
    if (!(startsWithHttp))
    {
      QappCore.messageBox("L'url Deve iniziare per 'http'")
      return 
    }
  }
  else if (wasModified(ChiusuraAutomatica) and ChiusuraAutomatica)
  {
    string chiusuraAutomaticaError = getTag("chiusuraAutomaticaError")
    if (chiusuraAutomaticaError != "")
    {
      ChiusuraAutomatica = false
      EventoClosureEsito = ""
      QappCore.messageBox(chiusuraAutomaticaError)
      return 
    }
  }
   
  // eventoClosureEsito is validated on OnValidate Event
  // here we call validate and collect error message
  // In case of error we do not allow to save the UrlToken
  boolean ok = validate(...)
  if (!(ok))
  {
    if (ChiusuraAutomatica and EventoClosureEsito == "")
    {
      string errorMessage = getPropertyError(EventoClosureEsito)
      QappCore.messageBox(errorMessage)
      return 
    }
  }
   
  // after end transaction we save, because if anything has been changed in the form (where all fields are Active), endTransaction is fired and we save
  this.ComputeAll()
  this.saveToDB(...)
}


// ──────────────────────────────────

// ************************************************************
// Event raised to the document during the validation procedure
// ************************************************************
event UrlToken.OnValidate(
  int:validateReasons Reason // An integer specifying the reason the document is being validated. The most common values are 0 to indicate complete validation before saving, or 1 to indicate a quick validati...
  inout boolean Error        // A boolean output parameter. If set to True, specifies an error condition and stops the validation immediately.
  inout boolean Skip         // A boolean output parameter. If set to True, does not check the rules for automatic validation of the document (see notes).
)
{
  if (ChiusuraAutomatica and EventoClosureEsito == "")
  {
    Error = true
    this.setPropertyError("L'esito per la chiusura automatica deve essere definito!", EventoClosureEsito)
  }
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event Indicator.OnInit()
{
  IdIndicator = Sequence.getNextSequence(INDN_ID_INDICATOR, ...)
  Active = Yes
  CreationDate = now()
  HasRange = No
  Scheduled = No
}


// ──────────────────────────────────

// ************************************************************
// Event raised to the document during the validation procedure
// ************************************************************
event Indicator.OnValidate(
  int:validateReasons Reason // An integer specifying the reason the document is being validated. The most common values are 0 to indicate complete validation before saving, or 1 to indicate a quick validati...
  inout boolean Error        // A boolean output parameter. If set to True, specifies an error condition and stops the validation immediately.
  inout boolean Skip         // A boolean output parameter. If set to True, does not check the rules for automatic validation of the document (see notes).
)
{
  if (Reason == Quick)
    return 
   
  if (IdResponsabile <= 0)
  {
    this.setPropertyError("E' necessario impostare un responsabile.", IdResponsabile)
    Error = true
  }
   
  if (length(Description) <= 0)
  {
    this.setPropertyError("E' necessario impostare una descrizione.", Description)
    Error = true
  }
   
  if (length(CodUm) <= 0)
  {
    this.setPropertyError("E' necessario impostare un'unità di misura.", CodUm)
    Error = true
  }
   
  if (DataProviderType == "" or isNull(DataProviderType))
  {
    this.setPropertyError("E' necessario impostare la tipologia dell'indicatore.", DataProviderType)
    Error = true
  }
   
  this.refreshUserInterface()
}


// ──────────────────────────────────

// ***********************************************************
// Event raised to the document when the transaction is closed
// ***********************************************************
event Indicator.OnEndTransaction()
{
  if (wasModified(DataProviderType))
  {
    if (DataProviderType == Qualibus)
    {
      IndicatorDataProvider = IndicatorDataProvider.factory(this, Qualibus)
    }
    if (DataProviderType == Manual)
    {
      IndicatorDataProvider = IndicatorDataProvider.factory(this, Manual)
    }
    if (UIForm)
    {
      UIForm.sendMessage("refreshDataProviderSubForm", ...)
    }
  }
   
   
  this.refreshUserInterface()
}


// ──────────────────────────────────

// **************************************************************************************
// Event raised to the document during the initial steps of the document saving procedure
// **************************************************************************************
event throws exception Indicator.BeforeSave(
  inout boolean Skip         // A boolean output parameter. If set to True, tells the framework not to perform operations on the database, because they will be managed independently.
  inout boolean Cancel       // A boolean output parameter. If set to True, tells the framework to cancel the save procedure due to errors that occurred. Since the entire document is saved in the same transa...
  int:beforeSavePhases Phase // An integer from 0 to 3 that specifies the progress status of the save procedure as detailed at the beginning of the article.
)
{
  if (Phase == PreSave)
  {
    if (deleted)
    {
      // deleting child values
       
      for each IndicatorValue iv in INDICATORVALUES
      {
         iv.deleted = true
         iv.saveToDB(...)
      }
       
      IndicatorDataProvider.deleted = true
      IndicatorDataProvider.saveToDB(...)
    }
     
     
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static Indicator Indicator.create(
  string:indicatorDataProviderTypes type // Write a comment for this parameter or press backspace to delete this comment
  string codUM               // Write a comment for this parameter or press backspace to delete this comment
  Personale responsible      // Write a comment for this parameter or press backspace to delete this comment
  boolean hasRange           // Write a comment for this parameter or press backspace to delete this comment
  string description         // Write a comment for this parameter or press backspace to delete this comment
  boolean isScheduled        // Write a comment for this parameter or press backspace to delete this comment
  optional string code = "0" // 
)
{
  Indicator indicator = new()
  indicator.init()
   
  indicator.DataProviderType = type
   
  IndicatorDataProvider idp = IndicatorDataProvider.factory(indicator, type)
   
  if (idp)
  {
    indicator.IndicatorDataProvider = idp
  }
  else 
  {
    QappCore.DTTLogMessage("Data provider not instantiated", ..., DTTError)
  }
   
  indicator.Description = description
  if (code != "")
  {
    indicator.Code = code
  }
  indicator.CodUm = codUM
  indicator.IdResponsabile = responsible.IDDIPENDENTE
  indicator.HasRange = if(hasRange, Yes, No)
  indicator.Scheduled = if(isScheduled, Yes, No)
   
  return indicator
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static Indicator Indicator.createEmpty()
{
  Indicator i = new()
   
  i.init()
  i.IndicatorDataProvider = IndicatorDataProvider.factory(i, Manual)
   
  return i
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static Indicator Indicator.getFromDb(
  int idIndicator // 
)
{
  Indicator i = new()
   
  if (idIndicator > 0)
  {
    i.IdIndicator = idIndicator
    try 
    {
      i.loadFromDB(...)
    }
    catch 
    {
      QappCore.DTTLogMessage("Impossibile caricare l'indicatore dal db.", ...)
      return null
    }
  }
  else 
  {
    QappCore.DTTLogMessage("Impossibile caricare l'indicatore dal db.", ...)
    return null
  }
   
  return i
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string Indicator.saveIndicator(
  boolean inUnitTest // Write a comment for this parameter or press backspace to delete this comment
)
{
  string errorMessage = ""
   
  if (IdIndicator > 0)
  {
    if (validate(...))
    {
      if (!(inUnitTest))
      {
         this.saveToDB(...)
          
//         for each IndicatorValue iv in INDICATORVALUES
//         {
//           iv.saveToDB(...)
//         }
      }
       
      if (!(IndicatorDataProvider.saveDataProvider()))
      {
         errorMessage = "Errore nel salvataggio del data provider."
      }
    }
    else 
    {
      if (IdResponsabile <= 0)
      {
         errorMessage = "E' necessario impostare un Responsabile."
      }
      else if (length(Description) <= 0)
      {
         errorMessage = "La descrizione non può essere vuota."
      }
      else if (length(CodUm) <= 0)
      {
         errorMessage = "E' necessario impostare un'unità di misura."
      }
      else 
      {
         errorMessage = "L'indicatore non è valido."
      }
    }
  }
  else 
  {
    errorMessage = "L'ID dell'indicatore è nullo."
  }
   
  return errorMessage
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean Indicator.deleteIndicatore()
{
  boolean indicatoreHasBeenDeletedDeleted = true
   
  try 
  {
    this.deleted = true
    this.saveToDB(...)
  }
  catch 
  {
    indicatoreHasBeenDeletedDeleted = false
  }
   
  return indicatoreHasBeenDeletedDeleted
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Indicator.generateInstantValue()
{
  float iv = IndicatorDataProvider.getValue(...)
   
  if (iv != null)
  {
    IndicatorValue indicatorValueObject = IndicatorValue.create(this, iv, now(), ...)
    if (indicatorValueObject)
    {
      INDICATORVALUES.add(indicatorValueObject)
    }
     
    if (IndicatorDataProviderSubform)
    {
      IndicatorDataProviderSubform.sendMessage("lockQueryField", ...)
    }
  }
  else 
  {
    QappCore.DTTLogMessage("Null value retrieved likely when we are on the Manual provider case.", ..., DTTInfo)
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Indicator.setQualibusDataProviderQuery(
  string:qualibusDataProviderQueries query // Write a comment for this parameter or press backspace to delete this comment
)
{
  if (decode(query, QualibusDataProviderQueries) != "")
  {
    if (DataProviderType == Qualibus)
    {
      QualibusDataProvider qdp = cast(IndicatorDataProvider)
       
      if (qdp)
      {
         qdp.setQuery(query)
         IndicatorDataProvider = cast(qdp)
      }
    }
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Indicator.addIndicatorValueToCollection(
  IndicatorValue valueToBeAdded // Write a comment for this parameter or press backspace to delete this comment
)
{
  if (valueToBeAdded)
  {
    if (valueToBeAdded.IdIndicator == IdIndicator)
    {
      INDICATORVALUES.add(valueToBeAdded)
    }
    else 
    {
      QappCore.DTTLogMessage("Error: you are trying to add a value that is not from this indicator", ..., DTTError)
    }
  }
   
  this.refreshUserInterface()
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Indicator.setUiForm(
  IDForm idForm // Write a comment for this parameter or press backspace to delete this comment
)
{
  UIForm = idForm
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean Indicator.fieldNeedsToBeEnabled(
  int:indicatorFieldTypes fieldType // 
)
{
  boolean fieldNeedsToBeEnabled = false
   
  switch (fieldType)
  {
    case Range:
      fieldNeedsToBeEnabled = HasRange == Yes
    break
    case HasRange:
      fieldNeedsToBeEnabled = INDICATORVALUES.count() <= 0
    break
    case DataProviderType:
      fieldNeedsToBeEnabled = INDICATORVALUES.count() <= 0 and HasRange == No
    break
    default:
      QappCore.DTTLogMessage("Type not supported", ..., DTTError)
      fieldNeedsToBeEnabled = false
    break
  }
   
  return fieldNeedsToBeEnabled
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static IndicatorValue IndicatorValue.create(
  Indicator indicator                    // 
  float value                            // 
  date creationDate                      // 
  optional date rangeFrom = #1899/12/30# // 
  optional date rangeTo = #1899/12/30#   // 
)
{
  IndicatorValue iv = new()
  iv.init()
   
  iv.CreationDate = creationDate
  iv.Indicator = indicator
  iv.IdIndicator = indicator.IdIndicator
  iv.Value = value
   
  if (rangeFrom != toDate(1899, 12, 30))
  {
    iv.RangeFrom = rangeFrom
  }
  else 
  {
    iv.RangeFrom = null
  }
   
  if (rangeTo != toDate(1899, 12, 30))
  {
    iv.RangeTo = rangeTo
  }
  else 
  {
    iv.RangeTo = null
  }
   
  return iv
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public static IndicatorValue IndicatorValue.createEmpty(
  Indicator indicator // 
  date creationDate   // 
)
{
  IndicatorValue iv = new()
   
  iv.Indicator = indicator
  iv.IdIndicator = indicator.IdIndicator
  iv.CreationDate = creationDate
   
  iv.init()
   
  return iv
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event IndicatorValue.OnInit()
{
  IdIndicatorValue = Sequence.getNextSequence(INDN_ID_INDICATOR_VALUE, ...)
}


// ──────────────────────────────────

// ************************************************************
// Event raised to the document during the validation procedure
// ************************************************************
event IndicatorValue.OnValidate(
  int:validateReasons Reason // An integer specifying the reason the document is being validated. The most common values are 0 to indicate complete validation before saving, or 1 to indicate a quick validati...
  inout boolean Error        // A boolean output parameter. If set to True, specifies an error condition and stops the validation immediately.
  inout boolean Skip         // A boolean output parameter. If set to True, does not check the rules for automatic validation of the document (see notes).
)
{
  if (Indicator)
  {
    if (Indicator.HasRange == Yes)
    {
      if (RangeFrom == null or RangeFrom == toDate(1899, 12, 30))
      {
         this.setPropertyError("Per questo tipo di indicatore la data di inizio della rilevazione va impostata.", RangeFrom)
         Error = true
      }
      if (RangeTo == null or RangeTo == toDate(1899, 12, 30))
      {
         this.setPropertyError("Per questo tipo di indicatore la data di fine della rilevazione va impostata.", RangeTo)
         Error = true
      }
       
      if (RangeFrom >= RangeTo)
      {
         this.setPropertyError("La data di fine rilevazione deve essere maggiore della data di inizio rilevazione.", RangeTo)
         Error = true
      }
    }
  }
   
  if (Value == null)
  {
    this.setPropertyError("E' necessario impostare un valore per la rilevazione.", Value)
    Error = true
  }
   
  this.refreshUserInterface()
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event IndicatorDataProvider.OnInit()
{
  IdIndicatorDataProvider = Sequence.getNextSequence(INDN_ID_INDICATOR_DP, ...)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static IndicatorDataProvider IndicatorDataProvider.factory(
  Indicator indicator                    // 
  string:indicatorDataProviderTypes type // 
)
{
  IndicatorDataProvider createdObject = new()
  createdObject.init()
   
  createdObject.IdIndicator = indicator.IdIndicator
   
  switch (type)
  {
    case Manual:
      ManualDataProvider mdp = ManualDataProvider.create(createdObject)
      createdObject = mdp
    break
    case Qualibus:
      QualibusDataProvider qdp = QualibusDataProvider.create(createdObject)
      createdObject = qdp
    break
  }
   
  createdObject.Indicator = indicator
   
  return createdObject
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static IndicatorDataProvider IndicatorDataProvider.getFromDb(
  Indicator indicator // 
)
{
  IndicatorDataProvider idp = new()
   
  switch (indicator.DataProviderType)
  {
    case Manual:
      ManualDataProvider mdp = ManualDataProvider.getManualDataProviderFromDb(indicator)
      idp = mdp
    break
    case Qualibus:
      QualibusDataProvider qdp = QualibusDataProvider.getQualibusDataProviderFromDb(indicator)
      idp = qdp
    break
    default:
      idp = null
    break
  }
   
  idp.Indicator = indicator
  indicator.IndicatorDataProvider = idp
   
  return idp
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public float IndicatorDataProvider.getValue(
  optional GetValueInformation information // 
)
{
  QappCore.DTTLogMessage("ABSTRACT", ..., DTTError)
   
  return null
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public IDForm IndicatorDataProvider.getSubform()
{
  QappCore.DTTLogMessage("ABSTRACT", ..., DTTError)
  return null
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean IndicatorDataProvider.saveDataProvider()
{
  QappCore.DTTLogMessage("ABSTRACT", ..., DTTError)
   
  return false
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public float ManualDataProvider.getValue(
  optional GetValueInformation information // 
)
{
  if (information)
  {
    this.createManualRequest(information.FromRange, information.ToRange)
  }
  else 
  {
    this.createManualRequest(...)
  }
   
  // for the manual provider we only create a request, we dont return a value
  return null
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public IDForm ManualDataProvider.getSubform()
{
  ManualDataProviderForm mdplf = ManualDataProviderForm.newInstance(SubForm, ...)
  return mdplf
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean ManualDataProvider.saveDataProvider()
{
  if (this.saveToDB(0, ...))
  {
    for each IndicatorManualRequest imr in INDICATORSMANUALREQUESTS
    {
      if (!(imr.saveToDB(...)))
      {
         QappCore.DTTLogMessage("Errore nel salvataggio di una delle richieste.", ..., DTTError)
         return false
      }
    }
  }
  else 
  {
    QappCore.DTTLogMessage("Errore nel salvataggio del Manual Data Provider.", ..., DTTError)
    return false
     
  }
   
  return true
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static ManualDataProvider ManualDataProvider.create(
  IndicatorDataProvider dataProvider // 
)
{
  ManualDataProvider mdp = new()
  mdp.init()
   
  mdp.IdIndicatorDataProvider = dataProvider.IdIndicatorDataProvider
  mdp.IdIndicator = dataProvider.IdIndicator
   
  return mdp
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public IndicatorManualRequest ManualDataProvider.createManualRequest(
  optional date rangeFrom = #1899/12/30# // Write a comment for this parameter or press backspace to delete this comment
  optional date rangeTo = #1899/12/30#   // Write a comment for this parameter or press backspace to delete this comment
)
{
  IndicatorManualRequest imr = IndicatorManualRequest.create(this, now(), Indicator, rangeFrom, rangeTo)
   
  if (imr)
  {
    INDICATORSMANUALREQUESTS.add(imr)
  }
   
  return imr
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public IDCollection ManualDataProvider.getNotCompletedManualRequests()
{
  IDCollection notCompletedManualRequests of IndicatorManualRequest = new()
   
  for each IndicatorManualRequest imr in INDICATORSMANUALREQUESTS
  {
    if (imr.Completed == No)
    {
      notCompletedManualRequests.addRef(imr)
    }
  }
   
  return notCompletedManualRequests
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static ManualDataProvider ManualDataProvider.getManualDataProviderFromDb(
  Indicator indicator // 
)
{
  ManualDataProvider mdp = new()
  mdp.IdIndicator = indicator.IdIndicator
   
  try 
  {
    mdp.loadFromDB(...)
  }
  catch 
  {
    mdp = null
    QappCore.DTTLogMessage("Error on loading Manual Data Provider", ..., DTTError)
  }
   
  return mdp
}


// ──────────────────────────────────

// **************************************************************************************
// Event raised to the document during the initial steps of the document saving procedure
// **************************************************************************************
event throws exception ManualDataProvider.BeforeSave(
  inout boolean Skip         // A boolean output parameter. If set to True, tells the framework not to perform operations on the database, because they will be managed independently.
  inout boolean Cancel       // A boolean output parameter. If set to True, tells the framework to cancel the save procedure due to errors that occurred. Since the entire document is saved in the same transa...
  int:beforeSavePhases Phase // An integer from 0 to 3 that specifies the progress status of the save procedure as detailed at the beginning of the article.
)
{
  if (deleted and Phase == PreSave)
  {
    for each IndicatorManualRequest imr in INDICATORSMANUALREQUESTS
    {
      imr.deleted = true
      imr.saveToDB(...)
    }
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public float QualibusDataProvider.getValue(
  optional GetValueInformation information // 
)
{
  float result = 0
   
  switch (Query)
  {
    case CompanyNumberOfFemale:
      int companyFemaleCount = 0
      select into variables (found variable)
         set companyFemaleCount = count(...)
      from 
         Personale // master table
      where
         STATO == Attivo
         SESSO == "F"
       
      result = toFloat(companyFemaleCount)
    break
    case CompanyNumberOfMale:
      int companyMaleCount = 0
      select into variables (found variable)
         set companyMaleCount = count(...)
      from 
         Personale // master table
      where
         STATO == Attivo
         SESSO == "M"
       
      result = toFloat(companyMaleCount)
    break
    case Company%OfFemale:
      int percentageCompanyFemale = 0
      select into variables (found variable)
         set percentageCompanyFemale = sum(if(SESSO == "F", 1, 0) * 100) / count(...)
      from 
         Personale // master table
      where
         STATO == Attivo
       
      result = percentageCompanyFemale
    break
    case Company%OfMale:
      int percentageCompanyMale = 0
      select into variables (found variable)
         set percentageCompanyMale = sum(if(SESSO == "M", 1, 0) * 100) / count(...)
      from 
         Personale // master table
      where
         STATO == Attivo
       
      result = percentageCompanyMale
    break
    default:
      QappCore.DTTLogMessage("Query not supported", ..., DTTError)
    break
  }
   
  return result
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public IDForm QualibusDataProvider.getSubform()
{
  QualibusDataProviderForm qdpf = QualibusDataProviderForm.newInstance(SubForm, ...)
  return qdpf
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean QualibusDataProvider.saveDataProvider()
{
  return saveToDB(...)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static QualibusDataProvider QualibusDataProvider.create(
  IndicatorDataProvider dataProvider // 
)
{
  QualibusDataProvider qdp = new()
  qdp.init()
   
  qdp.IdIndicatorDataProvider = dataProvider.IdIndicatorDataProvider
  qdp.IdIndicator = dataProvider.IdIndicator
   
  return qdp
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void QualibusDataProvider.setQuery(
  string:qualibusDataProviderQueries query // Write a comment for this parameter or press backspace to delete this comment
)
{
  boolean isValidQuery = decode(query, QualibusDataProviderQueries) != ""
   
  if (isValidQuery)
  {
    Query = query
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static QualibusDataProvider QualibusDataProvider.getQualibusDataProviderFromDb(
  Indicator indicator // 
)
{
  QualibusDataProvider qdp = new()
   
  if (indicator.IdIndicator > 0)
  {
    qdp.IdIndicator = indicator.IdIndicator
     
    try 
    {
      qdp.loadFromDB(0)
    }
    catch 
    {
      QappCore.DTTLogMessage("Impossibile caricare la query dal db.", ..., DTTError)
      return null
    }
  }
  else 
  {
    QappCore.DTTLogMessage("L'id indicatore passato non è valido.", ..., DTTError)
    return null
  }
   
  return qdp
}


// ──────────────────────────────────

// ***********************************************************
// Event raised to the document when the transaction is closed
// ***********************************************************
event QualibusDataProvider.OnEndTransaction()
{
  if (wasModified(Query))
  {
    QappCore.DTTLogMessage(Query, 11111, ...)
  }
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event IndicatorManualRequest.OnInit()
{
  IdRequest = Sequence.getNextSequence(INDN_ID_MANUAL_REQ, ...)
  Completed = No
  CompletedOn = null
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static IndicatorManualRequest IndicatorManualRequest.create(
  ManualDataProvider manualDataProvider  // 
  date referenceDate                     // 
  Indicator indicator                    // 
  optional date rangeFrom = #1899/12/30# // 
  optional date rangeTo = #1899/12/30#   // 
)
{
  IndicatorManualRequest imr = new()
  imr.init()
   
  imr.IdManualDataProvider = manualDataProvider.IdIndicatorDataProvider
  imr.ReferenceDate = referenceDate
  imr.Indicator = indicator
   
  string requestDescription = "Rilevazione "
  if (rangeFrom > toDate(1899, 12, 30) and rangeTo > toDate(1899, 12, 30))
  {
    imr.RangeTo = rangeTo
    imr.RangeFrom = rangeFrom
    requestDescription = requestDescription + "dal " + toString(rangeFrom) + " al " + toString(rangeTo) + " da compilare."
  }
  else 
  {
    requestDescription = requestDescription + "del " + toString(referenceDate) + " da compilare."
  }
   
  imr.Description = requestDescription
   
  return imr
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void IndicatorManualRequest.closeRequest()
{
  CompletedOn = now()
  Completed = Yes
  Description = replace(Description, "da compilare.", "compilata.")
   
  this.refreshUserInterface()
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static Scheduler Scheduler.create(
  Indicator indicator // 
)
{
  Scheduler s = new()
  s.init()
   
  s.IdIndicator = indicator.IdIndicator
   
   
  return s
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean Scheduler.valueShouldBeCreated()
{
  date now = toDate(year(now()), month(now()), day(now()))
  date nextDate = toDate(year(NextScheduledExecutionDate), month(NextScheduledExecutionDate), day(NextScheduledExecutionDate))
  return now >= nextDate
}


// ──────────────────────────────────

// ************************************************************************
// Event raised when a form sends a message using the SendMessage procedure
// ************************************************************************
event QualibusDataProviderForm.OnSendMessage(
  string Message // Indicates the name of the message
  IDForm Sender  // Identifies the form that sent the message. IDForm type object.
  IDDocument Doc // Optional. Document associated with the message.
  string Par1    // Optional. Parameter associated with the message; can be of any type (except object or generic document).
  string Par2    // Optional. Parameter associated with the message; can be of any type (except object or generic document).
  string Par3    // Optional. Parameter associated with the message; can be of any type (except object or generic document).
  string Par4    // Optional. Parameter associated with the message; can be of any type (except object or generic document).
)
{
  if (Message == "setDocument")
  {
    QualibusDataProvider qdp = (QualibusDataProvider)Doc
    Qualibusdataproviderpanel.setDocument(qdp, true)
  }
  if (Message == "lockQueryField")
  {
    Qualibusdataproviderpanel.Query.setEnabled(false)
  }
  if (Message == "unlockQueryField")
  {
    Qualibusdataproviderpanel.Query.setEnabled(true)
  }
   
}


// ──────────────────────────────────

// ************************************************************************
// Event raised when a form sends a message using the SendMessage procedure
// ************************************************************************
event ManualDataProviderForm.OnSendMessage(
  string Message // Indicates the name of the message
  IDForm Sender  // Identifies the form that sent the message. IDForm type object.
  IDDocument Doc // Optional. Document associated with the message.
  string Par1    // Optional. Parameter associated with the message; can be of any type (except object or generic document).
  string Par2    // Optional. Parameter associated with the message; can be of any type (except object or generic document).
  string Par3    // Optional. Parameter associated with the message; can be of any type (except object or generic document).
  string Par4    // Optional. Parameter associated with the message; can be of any type (except object or generic document).
)
{
  if (Message == "setDocument")
  {
    ManualDataProvider mdp = (ManualDataProvider)Doc
     
    IDCollection coll of IndicatorManualRequest = mdp.getNotCompletedManualRequests()
    IndicatorManualRequest.setCollection(coll, true)
     
    this.ManualDataProvider = mdp
    this.Indicator = mdp.Indicator
  }
  if (Message == "refreshQuery")
  {
    IDCollection notClosedManualRequests of IndicatorManualRequest = this.ManualDataProvider.getNotCompletedManualRequests()
    IndicatorManualRequest.setCollection(notClosedManualRequests, true)
     
    if (notClosedManualRequests.count() == 0)
    {
      IndicatorManualRequest.visible = false
    }
    else 
    {
      IndicatorManualRequest.visible = true
    }
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void ManualDataProviderForm.closeRequest()
{
  IndicatorManualRequest imr = IndicatorManualRequest.document
   
  if (imr)
  {
    if (this.Indicator)
    {
      ManualValueInsertionForm.showForm(this.Indicator, imr, this.IDForm())
    }
    else 
    {
      QappCore.DTTLogMessage("Error: manual resquest's indicator not instanciated", ...)
    }
  }
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void ManualValueInsertionForm.showForm(
  Indicator indicator                      // Write a comment for this parameter or press backspace to delete this comment
  IndicatorManualRequest requestToBeClosed // 
  IDForm callerForm                        // 
)
{
  if (indicator)
  {
    this.show(Modal)
    this.Indicator = indicator
     
    this.resetDocument(indicator)
     
    if (indicator.HasRange == No)
    {
      IndicatorValueInsertionPanel.RangeFrom.setVisible(false)
      IndicatorValueInsertionPanel.RangeTo.setVisible(false)
    }
    else 
    {
      IndicatorValueInsertionPanel.RangeFrom.setVisible(true)
      IndicatorValueInsertionPanel.RangeTo.setVisible(true)
    }
     
    this.ManualRequest = requestToBeClosed
     
    this.CallerForm = callerForm
  }
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public void ManualValueInsertionForm.BottoneAnnulla()
{
  this.close(...)
   
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public void ManualValueInsertionForm.BottoneAggiungirilevazione()
{
  IndicatorValue panelDocument = IndicatorValueInsertionPanel.document
   
  if (panelDocument.validate(...))
  {
    IndicatorValue indicatorToBeInserted = IndicatorValue.create(this.Indicator, panelDocument.Value, now(), panelDocument.RangeFrom, panelDocument.RangeTo)
    this.Indicator.addIndicatorValueToCollection(indicatorToBeInserted)
     
    date rangeFrom = IndicatorValueInsertionPanel.RangeFrom
    date rangeTo = IndicatorValueInsertionPanel.RangeTo
     
    if (!(rangeFrom > toDate(1899, 12, 30) and !(rangeTo > toDate(1899, 12, 30))))
    {
      rangeFrom = null
      rangeTo = null
    }
     
    if (this.ManualRequest)
    {
      this.ManualRequest.closeRequest()
    }
    else 
    {
      QappCore.DTTLogMessage("Error: Manual request to be closed not found", ..., DTTError)
    }
     
    this.close(...)
     
    this.CallerForm.sendMessage("refreshQuery", null, ...)
  }
   
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public void ManualValueInsertionForm.resetDocument(
  Indicator indicator // 
)
{
  IndicatorValue iv = IndicatorValue.createEmpty(indicator, now())
  if (iv)
  {
    IndicatorValueInsertionPanel.setDocument(iv, true)
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static AfterSaveHook AfterSaveHook.create(
  MainModule mainModule   // 
  QappCommand qappCommand // 
)
{
  AfterSaveHook ash = new()
  ash.init()
   
  ash.MainModule = mainModule
   
  string mainId = toString(mainModule.getMainID())
   
  LoginToken token = LoginToken.create(QappCore.Loggeduser)
  token.saveToDB(...)
   
  string tokenValue = token.VALUE
   
  string url = qappCommand.getCommandUrl(mainId, tokenValue, NormalExecution)
   
  ash.IndeGethttpCall = IndeGethttpCall.create(null, url, "ForwardCommand")
   
  return ash
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void AfterSaveHook.Perform()
{
  string message = ""
  try 
  {
    string textResponse = ""
    textResponse = IndeGethttpCall.perform()
     
    IDMap response = cast(JSON.parse(textResponse))
    message = response.getValue("message")
  }
  catch 
  {
    QappCore.DTTLogMessage("Cannot retrieve the message parameter in the response", ..., DTTInfo)
  }
   
  if (message != "" and message != null)
  {
    // temporary
    QappCore.messageBox(message)
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void FormNotifier.setData(
  IDForm destination   // 
  IDDocument sourceDoc // 
)
{
  Destination = destination
  SourceDocument = sourceDoc
}


// ──────────────────────────────────

// ********************************
// this procedure setups a notifier
// ********************************
public void FormNotifier.setup(
  IDForm destination   // 
  IDDocument sourceDoc // 
)
{
  Destination = destination
  SourceDocument = sourceDoc
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void FormNotifier.notify(
  string Message // 
  IDDocument Doc // 
)
{
  switch (Message)
  {
    case EditorResult:
       
      FileResult rf = (FileResult)Doc
      string filePath = rf.getResultFilePath()
      Destination.sendMessage(Message, SourceDocument, filePath, ...)
    break
    case waitingResult:
      Destination.sendMessage(Message, SourceDocument, ...)
    break
    case notWaitingResult:
      Destination.sendMessage(Message, SourceDocument, ...)
    break
    case EditorMissingResult:
      Destination.sendMessage(Message, SourceDocument, ...)
    break
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static OnlyOfficeEditorController OnlyOfficeEditorController.create(
  IDDocument fileContainer                  // Write a comment for this parameter or press backspace to delete this comment
  IDForm caller                             // 
  string qualibusDocumentId                 // 
  optional string:editorModes mode = "view" // 
)
{
  OnlyOfficeEditorController controller = new()
  controller.setupController(fileContainer, caller, qualibusDocumentId, mode)
  return controller
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void OnlyOfficeEditorController.setupController(
  IDDocument fileContainer                  // Write a comment for this parameter or press backspace to delete this comment
  IDForm caller                             // 
  string qualibusDocumentId                 // 
  optional string:editorModes mode = "view" // 
)
{
  boolean filepathExtractedSuccessfully = this.computeInfoFromFileContainer(fileContainer)
  if (filepathExtractedSuccessfully)
  {
    this.prepareConfiguration(mode)
    this.prepareNotifier(caller)
    QualibusDocumentId = qualibusDocumentId
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void OnlyOfficeEditorController.prepareNotifier(
  IDForm caller // 
)
{
  FormNotifier notifier = new()
  notifier.setup(caller, SourceDocument)
  Notifier = notifier
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void OnlyOfficeEditorController.prepareConfiguration(
  string:editorModes mode // 
)
{
   
  Configuration = EditorConfiguration.create(FilePath, "TOBESET", mode, Caption, QappCore.Loggeduser.DESCRUTENTE)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private boolean OnlyOfficeEditorController.computeInfoFromFileContainer(
  IDDocument container // Write a comment for this parameter or press backspace to delete this comment
)
{
  boolean result = false
  if (ModuleDocument.isMyInstance(container))
  {
    ModuleDocument md = (ModuleDocument)container
    SourceDocument = (IDDocument)md
     
    int error = 0
    FilePath = md.DocFile.GetPathOfDownloadableDOCFILE(md.ESTENSIONEFILE, md.DESCRIZIONE, error)
     
    string extension = lower(md.ESTENSIONEFILE)
    string uniqueName = formatMessage("|1|2", replace(left(docIDToGuid(newDocID()), 15), "-", "_"), extension, ...)
    string uniqueFilePath = formatMessage("|1/|2", QappCore.tempPath, uniqueName, ...)
    QappCore.renameFile(FilePath, uniqueFilePath)
    FilePath = uniqueFilePath
     
    Caption = formatMessage("Documento: |1", md.DESCRIZIONE, ...)
  }
   
  if (Documento.isMyInstance(container))
  {
    Documento d = (Documento)container
    SourceDocument = (IDDocument)d
    FilePath = d.GetDownloadbleFilePath(..., false)
    string extension = lower(d.EstensioneFile)
    string uniqueName = formatMessage("|1|2", replace(left(docIDToGuid(newDocID()), 15), "-", "_"), extension, ...)
    string uniqueFilePath = formatMessage("|1/|2", QappCore.tempPath, uniqueName, ...)
    QappCore.renameFile(FilePath, uniqueFilePath)
    FilePath = uniqueFilePath
     
    Caption = formatMessage("Documento: |1", d.DESCRDOCUMENTO, ...)
  }
   
  result = FilePath != ""
  return result
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void OnlyOfficeEditorController.open()
{
  OOEditorContainer editor = new()
  editor.showForm(Configuration, Caption, Notifier)
  editor.show(...)
  editor.bringToFront()
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string OnlyOfficeEditorController.getFilePath()
{
  return FilePath
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static string OnlyOfficeDocumentOpener.open(
  IDDocument container    // 
  IDForm caller           // 
  string:editorModes mode // 
)
{
  string openedFilePath = ""
  if (ModuleDocument.isMyInstance(container))
  {
    ModuleDocument mdContainer = (ModuleDocument)container
    openedFilePath = OnlyOfficeDocumentOpener.openModuleDocument(mdContainer, caller, mode)
  }
  if (Documento.isMyInstance(container))
  {
    Documento d = (Documento)container
    openedFilePath = OnlyOfficeDocumentOpener.openDocumento(d, caller, mode)
  }
  return openedFilePath
}


// ──────────────────────────────────



// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private static string OnlyOfficeDocumentOpener.openDocumento(
  Documento documento     // 
  IDForm caller           // 
  string:editorModes mode // 
)
{
  string openedFilePath = ""
  if (OnlyOfficeTools.extensionIsSupportedByEditor(documento.EstensioneFile))
  {
    string originalObjectID = toString(documento.IDDOCUMENTO)
    OnlyOfficeEditorController ooec = OnlyOfficeEditorController.create(documento, caller, originalObjectID, mode)
    ooec.open()
    openedFilePath = ooec.getFilePath()
  }
  else 
  {
    string extractedFilePath = documento.GetDownloadbleFilePath(..., false)
    QappCore.OpenDocumentManager.downloadFile(extractedFilePath)
    openedFilePath = extractedFilePath
  }
  return openedFilePath
}


// ──────────────────────────────────

// ********************************************************************************************************************
// creates a temp file with the source parameter as content so then the file can be passed to the only office converter
// ********************************************************************************************************************
private string OnlyOfficeConverter.storeSourceIntoFile(
  string source     // 
  string sourceType // 
)
{
  string filename = formatMessage("|1.|2", left(docIDToGuid(newDocID()), 6), sourceType, ...)
  FH fh = FH.Create(QappCore.tempPath + "/" + filename, ...)
  fh.addLine(source)
  return fh.Path
}


// ──────────────────────────────────

// *********************************************************************************************************
// the input string (of the inputType) is converted to a string of the outputType
// 
// for example inputString is an rtf code, inputType = rtf, outputType is html, the result is an html string
// *********************************************************************************************************
public static string OnlyOfficeConverter.ConvertToDocumentType(
  string inputString                               // 
  string inputType                                 // 
  string outputType                                // 
  optional inout boolean successfullyConverted = 0 // 
)
{
  OnlyOfficeConverter qcc = new()
  string sourceFilePath = qcc.storeSourceIntoFile(inputString, inputType)
  string result = Converter.Convert(sourceFilePath, outputType, ..., Output_as_content, successfullyConverted)
  return result
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static OrgchartController OrgchartController.create(
  Clifor clifor             // 
  IDForm parentForm         // 
  Orgchart OrgchartInstance // 
)
{
  OrgchartController controller = new()
  controller.computeProperties(clifor, parentForm, OrgchartInstance)
  return controller
}


// ──────────────────────────────────



// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void OrgchartController.handleEmptyOrgchart()
{
  IDCollection initNodes of Node = CliforContactsHandler.computeNodes()
  for each Node n in initNodes
  {
    RequestsHandler.addNode(n)
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void OrgchartController.alignNodeParent(
  Node nodeMoved // 
)
{
  CliforContactsHandler.updateContactParent(nodeMoved)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void OrgchartController.addContact(
  Node parentNode // 
)
{
  Node newNode = CliforContactsHandler.AddContact(parentNode)
  RequestsHandler.addNode(newNode)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void OrgchartController.removeContact(
  Contatto contatto // 
)
{
  string message = ""
  boolean deletionResult = CliforContactsHandler.deleteContact(contatto, message)
  if (deletionResult == false)
  {
    QappCore.messageBox(message)
  }
  else 
  {
    Node corrispondingNode = CliforContactsHandler.getNodeFromContact(contatto)
    RequestsHandler.removeNode(corrispondingNode)
    IDCollection coll of Node = CliforContactsHandler.reassignChildrenParents(contatto)
     
    for each Node n in coll
    {
      RequestsHandler.updateNode(n)
    }
    CliforContactsHandler.removeNode(corrispondingNode)
  }
   
  if (CliforContactsHandler.isEmpty())
  {
    this.handleEmptyOrgchart()
  }
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void OrgchartController.updateContact(
  Contatto contact // 
)
{
  Node nodeUpdated = CliforContactsHandler.updateContactNode(contact)
  RequestsHandler.updateNode(nodeUpdated)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean OrgchartController.contactHasChildren(
  Contatto contact // 
)
{
  boolean contactHasChildren = CliforContactsHandler.contactHasChildren(contact)
  return contactHasChildren
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public IDCollection CliforContactsHandler.computeNodes()
{
  if (ContactsLoaded = false)
  {
    Clifor.loadCliforContacts()
    ContactsLoaded = true
  }
   
  this.recNodeComputation(Clifor.Contacts, ...)
   
  if (NodesMapping.length() == 0)
  {
    this.createFirstNode()
  }
   
  IDCollection nodes of Node = this.getAllNodes()
  return nodes
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public Node CliforContactsHandler.getNodeFromContact(
  Contatto contatto // 
)
{
  Node node = null
  IDArray keys = NodesMapping.getKeys()
  for (int i = 0; i < keys.length(); i = i + 1)
  {
    Node n = (Node)NodesMapping.getObject(keys.getValue(i))
    Contatto c = cast(n.LinkedObjectInstance)
    if (c.IDCONTATTO == contatto.IDCONTATTO)
    {
      node = n
      break 
    }
  }
  return node
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private Node CliforContactsHandler.createNodeFromContact(
  int nodeId                     // 
  Contatto contact               // 
  Node currentParentNode         // 
  optional boolean assistant = 0 // 
)
{
   
  string inactive = if(contact.ATTIVO == No, "(inattivo)", "")
  string concept = SH.Concat(inactive, contact.RUOLO, " ")
  string name = formatMessage("|1 |2", contact.COGNOME, contact.NOME, ...)
  string description = if(contact.EMAIL != "", contact.EMAIL, "")
  boolean isAssistant = assistant or contact.IsAssitant
  Node node = Node.create(nodeId, concept, name, description, isAssistant, ..., currentParentNode, contact)
  NodesMapping.setObject(nodeId, node)
  return node
}


// ──────────────────────────────────



// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void CliforContactsHandler.setClifor(
  Clifor clifor // 
)
{
  Clifor = clifor
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static CliforContactsHandler CliforContactsHandler.create(
  Clifor clifor // 
)
{
  CliforContactsHandler ch = new()
  ch.setClifor(clifor)
  return ch
}


// ──────────────────────────────────

// **************************************************************************************************
// this procedure creates the Nodes corrispoding the Contacts shared as Arg 
// It works recursively when identify a Root contatto or son of a CurrentParentContatto shared as Arg
// please rememeber Nodes id are 1 based
// **************************************************************************************************
private void CliforContactsHandler.recNodeComputation(
  IDCollection contacts of Contatto       // 
  optional Contatto currentParentContatto // 
  optional Node currentParentNode         // 
)
{
  for each Contatto contact in contacts
  {
    if (((contact.IDPADRE == null and currentParentContatto == null) or (currentParentNode and contact.IDPADRE == currentParentContatto.IDCONTATTO)) and contact.deleted == false)
    {
      int newNodeId = NodesMapping.length() + 1
      Node node = this.createNodeFromContact(newNodeId, contact, currentParentNode, ...)
      this.recNodeComputation(contacts, contact, node)
    }
  }
  currentParentNode = null
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void CliforContactsHandler.createFirstNode()
{
  Contatto c = Contatto.create(Clifor, "Contatto", "definisci")
  c.RUOLO = "Primo contatto"
  Clifor.Contacts.add(c)
  Node n = this.createNodeFromContact(NodesMapping.length() + 1, c, null, ...)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public Node CliforContactsHandler.updateContactNode(
  Contatto contatto // 
)
{
  this.updateContattoInClifor(contatto)
  Node nodeUpdated = this.replaceNodeForContatto(contatto)
  return nodeUpdated
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void CliforContactsHandler.updateContattoInClifor(
  Contatto contact // 
)
{
  for each Contatto c in Clifor.Contacts
  {
    if (c.IDCONTATTO == contact.IDCONTATTO)
    {
      c.copyFrom(contact, ...)
      break 
    }
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private Node CliforContactsHandler.replaceNodeForContatto(
  Contatto contatto // 
)
{
  Node previousNodeObj = this.getNodeFromContact(contatto)
  Node parent = previousNodeObj.ParentNode
  Node newNode = this.createNodeFromContact(previousNodeObj.Id, contatto, parent, ...)
  return newNode
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean CliforContactsHandler.deleteContact(
  Contatto contact     // 
  inout string message // 
)
{
  boolean deletionResult = false
  string errorMessage = ""
  boolean contactCanBeDeleted = contact.canBeDeleted(errorMessage)
  boolean successfullyDeleted = true
  if (contactCanBeDeleted)
  {
    try 
    {
      contact.deleted = true
      this.reassignChildrenParents(contact)
    }
    catch 
    {
      successfullyDeleted = false
    }
  }
   
  string explaination = if(successfullyDeleted == false, "Altri motivi", errorMessage)
  string role = if(contact.RUOLO == "", "", "(<i>" + contact.RUOLO + "</i>)")
  if (contactCanBeDeleted == false or successfullyDeleted == false)
  {
    message = formatMessage("Non è possibile eliminare il contatto <b>|1 |2</b> |3: |4", contact.NOME, contact.COGNOME, role, explaination, ...)
  }
   
  deletionResult = contactCanBeDeleted and successfullyDeleted
  return deletionResult
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public IDCollection CliforContactsHandler.reassignChildrenParents(
  Contatto contact // 
)
{
  IDCollection nodesToBeUpdatedInOrgchart of Node = new()
  Node linkedNode = this.getNodeFromContact(contact)
  Node newParent = linkedNode.ParentNode
  for each Contatto c in Clifor.Contacts
  {
    if (c.IDPADRE == contact.IDCONTATTO and c.deleted == false)
    {
      c.IDPADRE = contact.IDPADRE
      Node n = this.getNodeFromContact(c)
      n.ParentNode = newParent
      nodesToBeUpdatedInOrgchart.addRef(n)
    }
  }
  return nodesToBeUpdatedInOrgchart
}


// ──────────────────────────────────

public void CliforContactsHandler.removeNode(
  Node node // 
)
{
  NodesMapping.remove(node.Id)
}


// ──────────────────────────────────

// ***************************************************************
// this procedure checks if the contact passed as arg has children
// ***************************************************************
public boolean CliforContactsHandler.contactHasChildren(
  Contatto contact // 
)
{
  boolean hasChildren = false
  for each Contatto c in Clifor.Contacts
  {
    if (c.IDPADRE == contact.IDCONTATTO)
    {
      hasChildren = true
      break 
    }
  }
  return hasChildren
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public Node CliforContactsHandler.AddContact(
  Node node // 
)
{
  Contatto newContact = this.generateNewContatto(node)
  Node newNode = this.generateNewNode(node, newContact)
  return newNode
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private Contatto CliforContactsHandler.generateNewContatto(
  Node node // 
)
{
  string nameNewContact = if(node.IsAssistant, "Assistente", "Contatto")
  Contatto newContact = Contatto.create(Clifor, nameNewContact, "Nuovo")
  newContact.RUOLO = if(node.IsAssistant, "Assistente", "Contatto")
  newContact.IsAssitant = node.IsAssistant
  if (node.ParentNode != null)
  {
    if (node.ParentNode.LinkedObjectInstance and Contatto.isMyInstance(node.ParentNode.LinkedObjectInstance))
    {
      Contatto parentContatto = cast(node.ParentNode.LinkedObjectInstance)
      newContact.IDPADRE = parentContatto.IDCONTATTO
    }
  }
  Clifor.Contacts.add(newContact)
  return newContact
}


// ──────────────────────────────────



// ──────────────────────────────────

// **************************************************************************************
// this procedure updates the Contact Parent basing on the info of the shared node as Arg
// **************************************************************************************
public void CliforContactsHandler.updateContactParent(
  Node node // 
)
{
  Contatto linkedContatto = cast(node.LinkedObjectInstance)
  if (linkedContatto and linkedContatto.IDCONTATTO)
  {
    Node newParent = node.ParentNode
    Contatto newParentLinkedContatto = cast(newParent.LinkedObjectInstance)
    linkedContatto.IDPADRE = newParentLinkedContatto.IDCONTATTO
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean CliforContactsHandler.isEmpty()
{
//  QappCore.messageBox(toString(Clifor.Contacts.count()) + " " + toString(NodesMapping.length()))
  boolean isEmpty = Clifor.Contacts.count() == 0
  return isEmpty
}


// ──────────────────────────────────

public void PersonaleQuickSearch.computeDescription()
{
  Description = formatMessage("|1 |2", COGNOME, NOME, ...)
}


// ──────────────────────────────────

public void ClassForQuickSearch.computeDescription()
{
  QappCore.DTTLogMessage("ABSTRACT", ..., DTTError)
}


// ──────────────────────────────────

// ************************************************************
// Event raised by the document after loading from the database
// ************************************************************
event ClassForQuickSearch.AfterLoad(
  boolean AlreadyLoaded     // A boolean parameter that specifies whether the document was previously loaded.
  IDArray LoadedCollections // IDArray type parameter that contains the collections loaded by the framework. If the value of the Already Loaded parameter is False, the Loaded Collections parameter is NULL.
)
{
  this.computeDescription()
}


// ──────────────────────────────────

// ***************************************************************************************************************************************************************
// populates the imdb based where conditions of panel master queries based on imdb that
// this shuold be called before opening a lookup form or list form
// 
// if is mandatory to pass the either kordapp or referenceType
// 
// NOTE: since this method uses IMDBs it is not usable in a server session (like in a unit test), this is why we unit test only the computeRefeeneTypes.... method
// ***************************************************************************************************************************************************************
public static IDArray SearchFilterIMDBInteractions.initializePanelSearchQueriesRuntimeFilters(
  optional string:utenteVisibilityGetResultsTypes getResultsType = "consultazione" // 
  optional Utente utente                        // 
  optional int:kordapp kordApp = 0              // 
  optional ReferenceType referenceType          // 
  optional IDArray recordsToBeExcluded          // 
  optional boolean showMultipleSelection = 0    // 
  optional int subId = 0                        // 
  optional int MainIdToLimitSubModuleSearch = 0 // 
)
{
  if (utente == null)
  {
    utente = QappCore.Loggeduser
  }
   
  IDArray resultsForTestingOnly = new()
   
  if (kordApp = 0 and referenceType == null)
    QappCore.DTTLogMessage("either kordApp or referenceType should be passed", ..., DTTError)
   
  if (referenceType)
  {
    kordApp = referenceType.SourceKordapp
  }
   
  string commaSeparatedListOfTipoIds = ""
  string commaSeparatedListOfAllowedIds = ""
  IDCollection referenceTypeTipiIds of IntDataType = new()
  string commaSeparatedListOfIdsTobeExcluded = ""
  if (referenceType)
  {
    referenceTypeTipiIds = referenceType.computeReferenceTypesDestinationIdsAsCollectionOfIntDataType()
    for each IntDataType idt in referenceTypeTipiIds
    {
      commaSeparatedListOfTipoIds = SH.Concat(commaSeparatedListOfTipoIds, toString(idt.IntegerValue), ",")
    }
  }
   
  UtenteVisibility uv = UtenteVisibility.create(utente)
  IDCollection mainIdsUserCanSee of IntDataType = new()
   
  if (MainIdToLimitSubModuleSearch <> 0)
    mainIdsUserCanSee.add(IntDataType.createInteger(MainIdToLimitSubModuleSearch))
  else 
    mainIdsUserCanSee = uv.getIdsUserCanSee(kordApp, subId, getResultsType)
   
  for each IntDataType idt in mainIdsUserCanSee
  {
    commaSeparatedListOfAllowedIds = SH.Concat(commaSeparatedListOfAllowedIds, toString(idt.IntegerValue), ",")
  }
   
  if (recordsToBeExcluded)
  {
    commaSeparatedListOfIdsTobeExcluded = SearchFilterIMDBInteractions.computeMainIdsAsCommaSeparatedList(recordsToBeExcluded)
  }
  QappCore.RunTimeFilter.ShowMultipleSelection = showMultipleSelection
   
  // initialize to -1 all the strings to be used in search form's query
  if (commaSeparatedListOfTipoIds == "")
  {
    commaSeparatedListOfTipoIds = "-1"
  }
  if (commaSeparatedListOfAllowedIds == "")
  {
    commaSeparatedListOfAllowedIds = "-1"
  }
  if (commaSeparatedListOfIdsTobeExcluded == "")
  {
    commaSeparatedListOfIdsTobeExcluded = "-1"
  }
   
  SearchFilterIMDBInteractions.populatedFilterIMDBValues(commaSeparatedListOfTipoIds, commaSeparatedListOfAllowedIds, commaSeparatedListOfIdsTobeExcluded, showMultipleSelection)
   
  // we return an array so we can unit test what this method does
  resultsForTestingOnly.addValue(commaSeparatedListOfAllowedIds)
  resultsForTestingOnly.addValue(commaSeparatedListOfTipoIds)
  resultsForTestingOnly.addValue(commaSeparatedListOfIdsTobeExcluded)
  return resultsForTestingOnly
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static void SearchFilterIMDBInteractions.populatedFilterIMDBValues(
  string commSeparatedTipoIds       // 
  string commaSeparatedOfAllowedIds // 
  string commSeparatedIdsToExcluded // 
  boolean showMultipleSelection     // 
)
{
  QappCore.RunTimeFilter.CommaSeparatedListOfTipoIds = commSeparatedTipoIds
  QappCore.RunTimeFilter.CommaSeparatedListOfAllowedIds = commaSeparatedOfAllowedIds
  QappCore.RunTimeFilter.CommaSeparatedListOfIdsToBeExcluded = commSeparatedIdsToExcluded
  QappCore.RunTimeFilter.ShowMultipleSelection = showMultipleSelection
   
}


// ──────────────────────────────────



// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static LookupHelper LookupHelper.create(
  IDForm callerForm                              // 
  optional int:kordapp kordApp = 0               // 
  optional boolean supportMultiSelection = 0     // 
  optional IDDocument relatedConfigurationObject // only ReferenceType used for now
  optional string fieldCode = ""                 // 
  optional Utente utente                         // 
  optional int subId = 0                         // 
  optional IDArray recordsToBeExcluded           // 
  optional string subModuleClassName = ""        // 
  optional int mainIdToLimitSubModuleSearch = 0  // 
)
{
   
  // to pass referenceType and not other relatedCOnfigurationObjects ot InitializePanel... we extract ReferenceType and passs it to the initialize procedure
  ReferenceType referenceType = null
  if (relatedConfigurationObject != null)
  {
    if (relatedConfigurationObject.typeName() == ReferenceType.className(...))
    {
      referenceType = (ReferenceType)relatedConfigurationObject
      kordApp = referenceType.SourceKordapp
    }
    else 
    {
      QappCore.DTTLogMessage("only ReferenceType is a supported relatedConfigurationObject", ..., DTTError)
    }
  }
   
  SearchFilterIMDBInteractions.initializePanelSearchQueriesRuntimeFilters(..., utente, kordApp, referenceType, recordsToBeExcluded, supportMultiSelection, subId, mainIdToLimitSubModuleSearch)
   
  LookupHelper lh = new()
  lh.Kordapp = kordApp
  lh.CallerForm = callerForm
  lh.SupportMultiselection = supportMultiSelection
  lh.RelatedConfigurationObject = (IDDocument)relatedConfigurationObject
  lh.SubModuleClassName = subModuleClassName
  lh.MainIdToLimitSubmoduleSearch = mainIdToLimitSubModuleSearch
   
   
  if (fieldCode != "")
  {
    lh.FieldCode = fieldCode
  }
  return lh
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void LookupHelper.displayLookup()
{
   
  // calling setObjectTag will open the form because it is a form method
  switch (Kordapp)
  {
    case Progetti:
       
      ProgettiLookup.setObjectTag("lookupHelper", this)
    break
    case Personale:
      PersonaleLookup.setObjectTag("lookupHelper", this)
    break
    case Funzioni:
       
      FunzioniLookup.setObjectTag("lookupHelper", this)
    break
    case Eventi:
       
      EventiLookup.setObjectTag("lookupHelper", this)
    break
    case Articoli:
       
      ArticoliLookup.setObjectTag("lookupHelper", this)
    break
    case Privati:
       
      PrivatiLookup.setObjectTag("lookupHelper", this)
    break
    case AltreAnagrafiche:
      if (SubModuleClassName == Intervento.className(...))
         InterventoLookup.setObjectTag("lookupHelper", this)
      else 
         AltreAnagraficheLookup.setObjectTag("lookupHelper", this)
       
    break
    case ClientiFornitori:
      if (SubModuleClassName == Contatto.className(...))
         ContattiLookup.setObjectTag("lookupHelper", this)
      else 
         ClientiFornitoriLookup.setObjectTag("lookupHelper", this)
    break
    case Documenti:
       
      DocumentiLookup.setObjectTag("lookupHelper", this)
    break
  }
   
  if (SupportMultiselection)
    this.initializeMultiSelection()
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void LookupHelper.sendResult()
{
  string testInfo = null
   
  if (SupportMultiselection)
  {
    SearchResult sr = this.getMultiselectionAsSearchResult()
    boolean subModuleIsContatto = SubModuleClassName == Contatto.className(...)
    CallerForm.sendMessage("setLookupResults", sr, null, null, FieldCode, if(subModuleIsContatto, "contatti", ""))
    IDCollection coll of IDDocument = sr.getDataTypeCollection()
    testInfo = formatMessage("MSG: |1 - SEARCHRESULTSCOUNT: |2 - MULTISEL: |3", "setLookupResults", coll.count(), SupportMultiselection, ...)
  }
  else 
  {
    if (SubModuleClassName == Contatto.className(...))
    {
      Contatto c = Contatto.get(SelectedId)
      CallerForm.sendMessage("setLookupResults", c, toString(SelectedId), SelectedDescription, FieldCode, "contatti")
      string testInfoPart1 = formatMessage("MSG: |1 - ID: |2 - DESCR: |3 - FIELDCODE: |4 - MULTISEL: |5", "setLookupResults", SelectedId, SelectedDescription, FieldCode, SupportMultiselection)
      string testInfoPart2 = formatMessage("subModuleIsContatto: |1", true, ...)
      testInfo = testInfoPart1 + " - " + testInfoPart2
    }
    else if (SubModuleClassName == Intervento.className(...))
    {
      Intervento i = Intervento.getFromDB(SelectedId, quickLoad)
      CallerForm.sendMessage("setLookupResults", i, toString(SelectedId), SelectedDescription, FieldCode, "contatti")
      string testInfoPart1 = formatMessage("MSG: |1 - ID: |2 - DESCR: |3 - FIELDCODE: |4 - MULTISEL: |5", "setLookupResults", SelectedId, SelectedDescription, FieldCode, SupportMultiselection)
      string testInfoPart2 = formatMessage("subModuleIsIntervento: |1", true, ...)
      testInfo = testInfoPart1 + " - " + testInfoPart2
    }
    else 
    {
      MainModule mm = MainModule.retrieve(Kordapp, SelectedId, quickLoad)
      CallerForm.sendMessage("setLookupResults", mm, toString(SelectedId), SelectedDescription, FieldCode, ...)
      string testInfoPart1 = formatMessage("MSG: |1 - ID: |2 - DESCR: |3 - FIELDCODE: |4 - MULTISEL: |5", "setLookupResults", SelectedId, SelectedDescription, FieldCode, SupportMultiselection)
      string mainModuleDescription = if(mm != null, mm.getDescription(), "")
      string testInfoPart2 = formatMessage("MAINMODULEDESCR: |1", mainModuleDescription, ...)
      testInfo = testInfoPart1 + " - " + testInfoPart2
    }
  }
  CallerForm.setTag("testInfo", testInfo)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void LookupHelper.setResults(
  int id             // 
  string description // 
)
{
  SelectedId = id
  SelectedDescription = description
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void LookupHelper.initializeMultiSelection()
{
  MultiselectionMap.clear()
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void LookupHelper.updateMultiSelection(
  int idValue            // 
  boolean selectedStatus // 
)
{
  if (selectedStatus)
  {
    MultiselectionMap.setValue(idValue, true)
  }
  else 
  {
    MultiselectionMap.remove(idValue)
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public SearchResult LookupHelper.getMultiselectionAsSearchResult()
{
  SearchResult sr = new()
   
  IDArray selectedIdsArray = MultiselectionMap.getKeys()
  for (int i = 0; i < selectedIdsArray.length(); i = i + 1)
  {
    int currentInteger = selectedIdsArray.getValue(i)
    sr.addInteger(currentInteger)
  }
   
  // in case no any result is selected we add last selectedId
  if (selectedIdsArray.length() == 0)
  {
    if (SelectedId > 0)
    {
      sr.addInteger(SelectedId)
    }
  }
  return sr
}


// ──────────────────────────────────

// ******************************************************************************
// Event raised by a panel with multiple selection when the selected rows change.
// ******************************************************************************
public static void LookupHelper.handleOnChangeSelection(
  IDForm idForm    // 
  boolean Selected // 
  boolean Final    // 
  int selectedId   // 
)
{
   
  // we consider only Final=false because it is the normal case, Final=true is an extra call we want to avoid
  if (Final)
    return 
   
  LookupHelper lh = (LookupHelper)idForm.getObjectTag("lookupHelper")
   
  if (!(lh))
    return 
   
  if (lh.SupportMultiselection)
  {
    lh.updateMultiSelection(selectedId, Selected)
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static void LookupHelper.handleUnloadEvent(
  IDForm idForm   // 
  boolean Confirm // 
)
{
  if (!(Confirm))
    return 
   
  LookupHelper lh = (LookupHelper)idForm.getObjectTag("lookupHelper")
  if (lh)
    lh.sendResult()
}


// ──────────────────────────────────

// **********************************************************************************************************************************************
// since the load event needs to address an IMDB we do not have a handleLoadEvent but this methos initliazeMultiSelection is called in every load
// **********************************************************************************************************************************************
public static void LookupHelper.initializeMultiSelectionInLoadEvent(
  IDPanel idPanel // 
)
{
  if (QappCore.RunTimeFilter.ShowMultipleSelection == -1)
  {
    PanelTools.EnableMultiselection(idPanel, true, ...)
  }
}


// ──────────────────────────────────

// ***********************************************************************
// Event raised by the panel when the data in the active panel row changes
// ***********************************************************************
public static void LookupHelper.handleOnChangeRow(
  IDForm idForm              // 
  int selectedId             // 
  string selectedDescription // 
)
{
  LookupHelper lh = (LookupHelper)idForm.getObjectTag("lookupHelper")
  if (lh)
  {
    // even if multiselection selected we set the last focused result
    // this is to handle the condition when multiselection is enabled and user double click one row only
    lh.setResults(selectedId, selectedDescription)
  }
}


// ──────────────────────────────────

// ***********************************************************
// Event raised by the document when the Show method is called
// ***********************************************************
event Memo.OnShow(
  inout string ClassName // An output parameter that can be set to the class name of the form to be used to show the document, or an empty string to cancel opening the document.
)
{
}


// ──────────────────────────────────

// *****************************************************************
// Event raised to the document to set the value of a named property
// *****************************************************************
event Memo.OnSetNamedPropertyValue(
  string PropertyName  // The name of the named property whose value to set.
  string PropertyValue // The value that should be set for the named property.
)
{
  if (PropertyName == "METHOD")
  {
    if (PropertyValue == "EDIT")
    {
      this.show(Modal)
    }
  }
}


// ──────────────────────────────────

// ************************************************************
// Event raised to the document during the validation procedure
// ************************************************************
event Memo.OnValidate(
  int:validateReasons Reason // An integer specifying the reason the document is being validated. The most common values are 0 to indicate complete validation before saving, or 1 to indicate a quick validati...
  inout boolean Error        // A boolean output parameter. If set to True, specifies an error condition and stops the validation immediately.
  inout boolean Skip         // A boolean output parameter. If set to True, does not check the rules for automatic validation of the document (see notes).
)
{
  if (isNull(LABELCOLOR))
  {
    LABELCOLOR = Nessuno
  }
}


// ──────────────────────────────────



// ──────────────────────────────────

// *************************************************************
// Event raised to the document at the end of the save procedure
// *************************************************************
event throws exception Memo.AfterSave(
  inout boolean Cancel // Boolean output parameter that makes it possible to cancel the entire save. It is equivalent to the Cancel parameter of the BeforeSave event.
)
{
   
  if (inserted)
  {
//    insert values into RisorseMemo (last value variable)
//      set IDPROMEMORIA = IDPROMEMORIA
//      set IDUTENTE = QappCore.Loggeduser.IDUTENTE
  }
  if (deleted)
  {
//    delete from RisorseMemo
//    where
//      IDPROMEMORIA = IDPROMEMORIA
  }
  if (inserted)
  {
    for each row (readwrite)
    {
      select
         SEQNAMESequence = SEQNAME
         LASTIDSequence = LASTID
      from 
         SW9SEQUENCES // master table
      where
         SEQNAME = DIS_ID_PROMEMORIA
      // 
      LASTIDSequence = LASTIDSequence + 1
    }
  }
}


// ──────────────────────────────────

// **************************************************************************************
// Event raised to the document during the initial steps of the document saving procedure
// **************************************************************************************
event throws exception Memo.BeforeSave(
  inout boolean Skip         // A boolean output parameter. If set to True, tells the framework not to perform operations on the database, because they will be managed independently.
  inout boolean Cancel       // A boolean output parameter. If set to True, tells the framework to cancel the save procedure due to errors that occurred. Since the entire document is saved in the same transa...
  int:beforeSavePhases Phase // An integer from 0 to 3 that specifies the progress status of the save procedure as detailed at the beginning of the article.
)
{
  if (updated && QappCore.Loggeduser != null)
  {
    IDUTENTEULTMOD = QappCore.Loggeduser.IDUTENTE
    DATAULTIMAMOD = now()
  }
   
  if (deleted)
  {
    for each RisorsaMemo rm in RisorseMemo
    {
      rm.deleted = true
      rm.saveToDB(...)
    }
  }
   
  // prima di salvare calcolo l'idpromermoria aggiornato
  if (inserted)
  {
    for each row (readonly)
    {
      select
         SEQNAMESequence = SEQNAME
         LASTIDSequence = LASTID
      from 
         SW9SEQUENCES // master table
      where
         SEQNAME = DIS_ID_PROMEMORIA
       
      IDPROMEMORIA = LASTIDSequence + 1
    }
  }
}


// ──────────────────────────────────

// ***********************************************************************
// Event raised to the document to determine the value of a named property
// ***********************************************************************
event Memo.OnGetNamedPropertyValue(
  string PropertyName        // The name of the named property whose value to retrieve.
  inout string PropertyValue // An output parameter containing the value of the named property.
)
{
  if (PropertyName = "STARTDATE")
  {
    PropertyValue = convert(getDate(DATAINIZIOPROMEMORIA))
  }
   
  if (PropertyName = "STARTTIME")
  {
    time t = getTime(DATAINIZIOPROMEMORIA)
    PropertyValue = convert(t)
  }
  if (PropertyName == "STATUS")
  {
    PropertyValue = Fixed
  }
   
  if (PropertyName == "DURATION")
  {
    // ORAINIZIO and ORAFINE sometimes contain NULL while DATAINIZIOPROMEMORIA and DATAFINEPROMEMORIA in the time part always contain the correct time
    time starttime = getTime(DATAINIZIOPROMEMORIA)
    time endtime = getTime(DATAFINEPROMEMORIA)
    int MinutesFromMidnightStartTime = hour(starttime) * 60 + minute(starttime)
    int MinutesFromMidnightEndTime = hour(endtime) * 60 + minute(endtime)
    //  
    // convert: metti in queestoIDVARIANT il valore ignorando il tipo sting, torniamo un intero
    PropertyValue = convert(MinutesFromMidnightEndTime - MinutesFromMidnightStartTime)
  }
  if (PropertyName == "DESCRIPTION")
  {
    string MemoDescription = ""
    //  
    // MESSAGE holds the dx memo notes
    if (MESSAGE = "")
    {
      MemoDescription = DESCRPROMEMORIA
    }
    else 
    {
      MemoDescription = DESCRPROMEMORIA + " - " + MESSAGE
       
    }
    PropertyValue = MemoDescription
     
  }
  //  
  int MemoColor = 0
  //  
  // Scrivi un commento per questo blocco o premi backspace per eliminare questo commento
  if (PropertyName = "COLOR")
  {
    PropertyValue = convert(Tools.DxcolorToInde(LABELCOLOR))
  }
  if (PropertyName = "ICON")
  {
    PropertyValue = convert(Memo)
  }
   
  if (PropertyName = "CREATIONDETAIL")
  {
    string UtenteInserimento = ""
    select into variables (found variable)
      set UtenteInserimento = Username
    from 
      Utenti // master table
    where
      IDUTENTE = IDUTENTEINS
    PropertyValue = "Inserito da " + UtenteInserimento + " il " + toString(DATAINS)
  }
  if (PropertyName = "CHANGEDETAIL")
  {
    string UtenteModifica = ""
    select into variables (found variable)
      set UtenteModifica = Username
    from 
      Utenti // master table
    where
      IDUTENTE = IDUTENTEULTMOD
    PropertyValue = "Modificato da " + UtenteModifica + " il " + toString(DATAULTIMAMOD)
  }
  if (PropertyName == "POPUPACTIONS")
  {
    PropertyValue = "Modifica memo;EDIT;memo.png"
  }
}


// ──────────────────────────────────

// ****************************************************************************************************
// Event that creaetes a new memo, it is meant to be used instead of memo.Init, in fact it calls it too
// Memo is created for a given user and at a passed datetime, setting its duration to 30 minutes
// ****************************************************************************************************
public void Memo.Initialize(
  Utente Utente           // 
  date time StartDateTime // Scrivi un commento per questo parametro o premi backspace per eliminare questo commento
)
{
  this.init()
  this.CreateDefaultResource(Utente)
  this.DATAINIZIOPROMEMORIA = StartDateTime
  this.DATAFINEPROMEMORIA = dateAdd(Minute, 30, StartDateTime)
}


// ──────────────────────────────────

// **************************************************************
// This method prepares the RisorseMemo collection for a new memo
// It requires Utente parameter
// this is called in Memo.Create
// **************************************************************
private void Memo.CreateDefaultResource(
  Utente Utente // 
)
{
  RisorsaMemo risorsa = new()
  // 
  risorsa.inserted = true
  risorsa.IDUTENTE = Utente.IDUTENTE
  risorsa.IDPROMEMORIA = IDPROMEMORIA
  RisorseMemo.add(risorsa)
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public static IDCollection Memo.GetUserMemo(
  Utente Utente       // 
  date time StartDate // 
  date time EndDate   // 
)
{
   
  IDCollection usermemos of Memo = new()
  select into collection (usermemos)
    set IDPROMEMORIA = Promemoria.IDPROMEMORIA
    set OLDID = Promemoria.OLDID
    set KORDAPP = Promemoria.KORDAPP
    set IDTIPOPROMEMORIA = Promemoria.IDTIPOPROMEMORIA
    set IDSTATOPROMEMORIA = Promemoria.IDSTATOPROMEMORIA
    set PROMEMORIAGRUPPO = Promemoria.PROMEMORIAGRUPPO
    set IDITEM = Promemoria.IDITEM
    set RISERVATO = Promemoria.RISERVATO
    set IDUTENTEINS = Promemoria.IDUTENTEINS
    set DATAINS = Promemoria.DATAINS
    set IDUTENTEULTMOD = Promemoria.IDUTENTEULTMOD
    set DATAULTIMAMOD = Promemoria.DATAULTIMAMOD
    set PRGIDINTERFACCIA = Promemoria.PRGIDINTERFACCIA
    set PRGIDFASE = Promemoria.PRGIDFASE
    set CLIFORIDCONTATTO = Promemoria.CLIFORIDCONTATTO
    set ASFPVIDAMBITO = Promemoria.ASFPVIDAMBITO
    set ASFPVIDARTICOLO = Promemoria.ASFPVIDARTICOLO
    set ASFPVLUOGOEVENTO = Promemoria.ASFPVLUOGOEVENTO
    set ASFPVDESCRRISULTATO = Promemoria.ASFPVDESCRRISULTATO
    set DESCRPROMEMORIA = Promemoria.DESCRPROMEMORIA
    set DATAINIZIOPROMEMORIA = Promemoria.DATAINIZIOPROMEMORIA
    set ORARIOINIZIO = Promemoria.ORARIOINIZIO
    set DATAFINEPROMEMORIA = Promemoria.DATAFINEPROMEMORIA
    set ORARIOFINE = Promemoria.ORARIOFINE
    set IDESECUTORE = Promemoria.IDESECUTORE
    set IDUTENTECHIUSURA = Promemoria.IDUTENTECHIUSURA
    set DATACHIUSURA = Promemoria.DATACHIUSURA
    set NOTECHIUSURA = Promemoria.NOTECHIUSURA
    set ESITO = Promemoria.ESITO
    set EVENTTYPEDISP = Promemoria.EVENTTYPEDISP
    set OPTIONSDISP = Promemoria.OPTIONSDISP
    set LABELCOLOR = Promemoria.LABELCOLOR
    set STATE = Promemoria.STATE
    set RESOURCEID = Promemoria.RESOURCEID
    set ACTUALFINISH = Promemoria.ACTUALFINISH
    set ACTUALSTART = Promemoria.ACTUALSTART
    set LOCATION = Promemoria.LOCATION
    set MESSAGE = Promemoria.MESSAGE
    set PARENTID = Promemoria.PARENTID
    set RECCURENCEINDEX = Promemoria.RECCURENCEINDEX
    set RECCURENCEINFO = Promemoria.RECCURENCEINFO
    set REMINDERDATE = Promemoria.REMINDERDATE
    set REMINDERMINSBEFORESTART = Promemoria.REMINDERMINSBEFORESTART
    set PROFID = Promemoria.PROFID
  from 
    Promemoria  // master table
    RisorseMemo // joined with Promemoria using key FK_MEMO_RESPONSIBLE_DIS_PROMEMORIA
  where
    Promemoria.DATAINIZIOPROMEMORIA >= StartDate && Promemoria.DATAFINEPROMEMORIA <= EndDate
    RisorseMemo.IDUTENTE = Utente.IDUTENTE
    Promemoria.KORDAPP = Calendar
  return usermemos
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static Memo Memo.get(
  int idMemo // 
)
{
  Memo memo = new()
  memo.IDPROMEMORIA = idMemo
  try 
  {
    memo.loadFromDB(...)
  }
  catch 
  {
    memo = null
    QappCore.DTTLogMessage(formatMessage("unable to load calendar memo for ID: |1", idMemo, ...), ...)
  }
  return memo
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event MSGLINK.OnInit()
{
  IDLINK = Sequence.getNextSequence(MSGN_ID_MSG_LINK, ...)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static MSGLINK MSGLINK.create(
  Messaggio messaggio       // 
  MainModule mainModuleLink // 
)
{
  MSGLINK msglink = new()
  msglink.init()
  msglink.IDMESSAGIO = messaggio.IDMESSAGGIO
  msglink.KORDAPP = mainModuleLink.getKordApp()
  msglink.IDITEM = mainModuleLink.getMainID()
  return msglink
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event MSGDESTINATARI.OnInit()
{
  ELIMINATO = No
  NOTIFICATO = No
  LETTO = No
  APERTO = No
   
  // sendingTime is a misleading name, in fact it is "Mailsender Sending time", so a
  // new message should have it null
  SENDINGTIME = null
  EMAILSTATUS = 0
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static MSGDESTINATARI MSGDESTINATARI.create(
  Messaggio messaggio // 
  Utente recipient    // 
)
{
  MSGDESTINATARI msgdestinatari = new()
  msgdestinatari.init()
  msgdestinatari.IDMESSAGGIO = messaggio.IDMESSAGGIO
  msgdestinatari.IDUTENTE = recipient.IDUTENTE
  return msgdestinatari
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Messaggio.addRecipient(
  Utente utente // 
)
{
  if (utente)
  {
    Recipients.add(utente)
  }
  else 
  {
    QappCore.DTTLogMessage("invalid utente passed to addRecipient", ..., DTTError)
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Messaggio.addLink(
  MainModule mainModule // 
)
{
  if (mainModule)
  {
    LinkedMainModules.add(mainModule)
  }
  else 
  {
    QappCore.DTTLogMessage("invalid mainModule passed to addLink", ..., DTTError)
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Messaggio.send()
{
  this.saveToDB(999, ...)
}


// ──────────────────────────────────

// ************************************************************************************************************************
// constructor of messaggio (init is already called by create), after creating it with create perform the following actions
// call addRecipient more times passing Utente, one per recipient
// call addLink more times passing MainModule, one per link
// 
// finally call send, this will save the message in principle send and savetoDb do the same, but send is nicer
// ************************************************************************************************************************
public static Messaggio Messaggio.create(
  string subject            // 
  string body               // 
  Utente utenteSender       // 
  optional int idUtente = 0 // pass this in alternative to utente Sender, if you pass idUtente pass null in utenteSender
)
{
  if (utenteSender)
  {
    if (idUtente != 0)
    {
      QappCore.DTTLogMessage("you cannot pass utenteSender and idUtente both", ..., DTTError)
      return null
    }
  }
   
  Messaggio m = new()
  m.init()
  m.IDUTENTE = if(utenteSender == null, idUtente, utenteSender.IDUTENTE)
  m.OGGETTO = subject
  m.TESTO = body
   
  return m
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public IDCollection Messaggio.getRecipients()
{
  this.populateCollections()
  return Recipients
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public IDCollection Messaggio.getLinks()
{
  this.populateCollections()
  return LinkedMainModules
}


// ──────────────────────────────────

// *************************************************************************
// from values stored in db the developer friendly collections are populated
// *************************************************************************
private void Messaggio.populateCollections()
{
  for each MSGLINK ml in MSGLINK
  {
     
    // THIS TO BE MOVED IN A FACTORY METHOD OF MAIN MODULE THAT RETURNS THE PROPERLY TYPED OBJECT
    MainModule mm = MainModule.retrieve(ml.KORDAPP, ml.IDITEM, normalAllLevels)
    LinkedMainModules.add(mm)
  }
   
  for each MSGDESTINATARI md in MSGDESTINATARI
  {
    Utente u = new()
    u.IDUTENTE = md.IDUTENTE
    u.loadFromDB(...)
    Recipients.add(u)
  }
}


// ──────────────────────────────────

// **************************************************************************************
// Event raised to the document during the initial steps of the document saving procedure
// **************************************************************************************
event throws exception Messaggio.BeforeSave(
  inout boolean Skip         // A boolean output parameter. If set to True, tells the framework not to perform operations on the database, because they will be managed independently.
  inout boolean Cancel       // A boolean output parameter. If set to True, tells the framework to cancel the save procedure due to errors that occurred. Since the entire document is saved in the same transa...
  int:beforeSavePhases Phase // An integer from 0 to 3 that specifies the progress status of the save procedure as detailed at the beginning of the article.
)
{
  for each Utente u in Recipients
  {
    MSGDESTINATARI msgdestinatario = MSGDESTINATARI.create(this, u)
     
    MSGDESTINATARI.add(msgdestinatario)
  }
   
  for each MainModule mm in LinkedMainModules
  {
    MSGLINK msglink = MSGLINK.create(this, mm)
     
    MSGLINK.add(msglink)
  }
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event Messaggio.OnInit()
{
  IDMESSAGGIO = Sequence.getNextSequence(MSGN_ID_MESSAGGIO, ...)
  DATAMESSAGGIO = now()
  ELIMINATO = "N"
  RICHIEDICONFERMA = "N"
  IDORIGINEDOC = 0
  IDFILEDOC = 0
  ISDISCUSSION = "N"
  ISCLOSEDDISCUSSION = "N"
}


// ──────────────────────────────────

// ************************************************************
// Event raised by the document after loading from the database
// ************************************************************
event Ntfrecipient.AfterLoad(
  boolean AlreadyLoaded     // A boolean parameter that specifies whether the document was previously loaded.
  IDArray LoadedCollections // IDArray type parameter that contains the collections loaded by the framework. If the value of the Already Loaded parameter is False, the Loaded Collections parameter is NULL.
)
{
  if (!(NTFSTATUS.loaded))
  {
    this.loadCollectionFromDB(NTFSTATUS, ...)
  }
  this.loadAddtionalProperties()
  this.setOriginal()
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event Ntfrecipient.OnInit()
{
  IDRECIPIENT = Sequence.getNextSequence(NTFN_ID_RECIPIENT, ...)
}


// ──────────────────────────────────

// ************************************************************
// Event raised to the document during the validation procedure
// ************************************************************
event Ntfrecipient.OnValidate(
  int:validateReasons Reason // An integer specifying the reason the document is being validated. The most common values are 0 to indicate complete validation before saving, or 1 to indicate a quick validati...
  inout boolean Error        // A boolean output parameter. If set to True, specifies an error condition and stops the validation immediately.
  inout boolean Skip         // A boolean output parameter. If set to True, does not check the rules for automatic validation of the document (see notes).
)
{
  if (Reason == Complete)
  {
    if (RECIPIENTTYPE == Others or RECIPIENTTYPE == Personale or RECIPIENTTYPE == Contact)
    {
      if (EMAIL == "")
      {
         Error = true
         this.setPropertyError("Ogni destinatario deve avere un indirizzo email", EMAIL)
      }
    }
  }
  if (EMAIL != "")
  {
    boolean isValidEmail = EmailTools.CheckEmailIsValid(trim(EMAIL))
    if (!(isValidEmail))
    {
      Error = true
      this.setPropertyError(formatMessage("Email non valida <b>|1</b>", EMAIL, ...), EMAIL)
    }
  }
}


// ──────────────────────────────────



// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static Ntfrecipient Ntfrecipient.create(
  string:NTFRECIPIENTSTypes recipientType // 
  int:kordapp kordApp // 
  int mainID          // 
  int detailID        // 
  boolean isFunction  // 
)
{
  Ntfrecipient ntf = new()
  ntf.init()
  ntf.RECIPIENTTYPE = recipientType
  ntf.KORDAPP = kordApp
  ntf.MAINID = mainID
  ntf.DETAILID = detailID
  ntf.ISFUNCTION = if(isFunction, Yes, No)
   
  return ntf
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static MenuManager MenuManager.create()
{
  MenuManager menuManager = new()
  menuManager.init()
  return menuManager
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void MenuManager.createMainMenu(
  IDCommand mainMenu // 
  Utente user        // 
)
{
  this.computeMenuFolders()
  for each MenuFolder mf in MenuFolders
  {
    QappCore.DTTLogMessage(mf.FOLDERDESCR, ...)
    QappCore.DTTLogMessage(mf.IDFOLDER, ...)
     
    mf.computeMenuItemsForSpecificUser(user)
     
    if (mf.RuntimeMenuitem.count() > 0)
    {
      if (mf.RuntimeMenuitem.count() == 1)
      {
         mf.RuntimeMenuitem.moveFirst()
         RuntimeMenuitem rm = (RuntimeMenuitem)mf.RuntimeMenuitem.getAt()
         if (rm.IsRootItem)
         {
           IDCommand menuItemAtRootLevel = this.createMenuItemCommand(rm)
           mainMenu.addCommand(menuItemAtRootLevel)
         }
         else 
         {
           IDCommand menuFolderCommand = this.createFolderMenuCommand(mf)
           mainMenu.addCommand(menuFolderCommand)
           IDCommand menuItemCommand = this.createMenuItemCommand(rm)
           menuFolderCommand.addCommand(menuItemCommand)
         }
      }
      else 
      {
         IDCommand menuFolderCommand = this.createFolderMenuCommand(mf)
         mainMenu.addCommand(menuFolderCommand)
         for each RuntimeMenuitem rm in mf.RuntimeMenuitem
         {
           IDCommand menuItemCommand = this.createMenuItemCommand(rm)
           menuFolderCommand.addCommand(menuItemCommand)
         }
      }
    }
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean MenuManager.canSeeOpzioni(
  int:kordapp kordapp // 
)
{
  boolean canSeeOpzioni = false
  int linkedTabelleKordApp = this.getMatchingTabelleKordApp(kordapp)
  if (linkedTabelleKordApp > 0)
  {
    canSeeOpzioni = QappCore.Loggeduser.hasSpecificPrivilege(linkedTabelleKordApp, Execute)
  }
  return canSeeOpzioni
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private int MenuManager.getMatchingTabelleKordApp(
  int:kordapp kordapp // 
)
{
  int:kordapp resultingTabKordapp = 0
  switch (kordapp)
  {
    case ClientiFornitori:
      resultingTabKordapp = TabelleClientiFornitori
    break
    case Progetti:
      resultingTabKordapp = TabelleProgetti
    break
    case Personale:
      resultingTabKordapp = TabellePersonale
    break
    case Articoli:
      resultingTabKordapp = TabelleArticoli
    break
    case AltreAnagrafiche:
      resultingTabKordapp = TabelleAltreAnagrafiche
    break
    case Privati:
      resultingTabKordapp = TabellePrivati
    break
    case Documenti:
      resultingTabKordapp = TabelleDocumenti
    break
    default:
      QappCore.DTTLogMessage("wrong usage: the passed kordapp is not linked to a Tabelle*", ..., DTTError)
      resultingTabKordapp = null
    break
  }
  return resultingTabKordapp
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void MenuManager.computeMenuFolders()
{
  IDCollection folders of MenuFolder = new()
  select into collection (folders)
    set IDFOLDER = IDFOLDER
    set FOLDERDESCR = FOLDERDESCR
    set ICONINDEX = ICONINDEX
    set SEQ = SEQ
    set STARTGROUP = STARTGROUP
  from 
    MenuFolder // master table
   
  MenuFolders.clear()
  MenuFolders.addAll(folders, true)
  this.addCustomCreatedMenuFolders()
   
  MenuFolders.addSortCriteria(MenuFolder.SEQ)
  MenuFolders.addSortCriteria(MenuFolder.FOLDERDESCR)
  MenuFolders.doSort()
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public IDCommand MenuManager.createFolderMenuCommand(
  MenuFolder menuFolder // 
)
{
  IDCommand menuCommand = new()
  string folderIcon = QappCore.IconFactory.GetFontAwesomeIcon(menuFolder.ICONINDEX)
   
  menuCommand.code = menuFolder.FOLDERDESCR
  menuCommand.caption = formatMessage("|1  |2", folderIcon, menuFolder.FOLDERDESCR, ...)
  menuCommand.setIsToolbar(false)
  menuCommand.setIsMenu(true)
  menuCommand.visible = true
  menuCommand.enabled = true
  menuCommand.setIsCommandSet(true)
   
//  // send messages to add command set in the Qualibus web application
//  QappCore.sendAppMessage("AddCommandSet", menuCommand)
  return menuCommand
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public IDCommand MenuManager.createMenuItemCommand(
  RuntimeMenuitem menuItem // 
)
{
  IDCommand menuItemCommand = new()
  string icon2 = menuItem.getIcon()
  menuItemCommand.caption = icon2 + " " + menuItem.Description
  menuItemCommand.code = menuItem.computeMenuCode()
  menuItemCommand.visible = true
  menuItemCommand.enabled = true
  return menuItemCommand
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void MenuManager.addCustomCreatedMenuFolders()
{
  MenuFolder qappMenuFolder = MenuFolder.createQAppMenuFolder()
  MenuFolders.add(qappMenuFolder)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void MenuFolder.computeMenuItemsForSpecificUser(
  Utente utente // 
)
{
  RuntimeMenuitem.clear()
  RuntimeMenuitem = RuntimeMenuitem.getRuntimeMenuItemsBasedOnUser(this, utente)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static MenuFolder MenuFolder.createQAppMenuFolder()
{
  MenuFolder mf = new()
  mf.IDFOLDER = -1001
  mf.FOLDERDESCR = "Qapps"
  mf.ICONINDEX = -1
   
  // to be shown at the end of all the folder menus always so we keep high value in sequence
  mf.SEQ = 100000
  mf.STARTGROUP = No
  return mf
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private int MenuFolder.countNumberOfMenuFoldersAsignedToMenuItems()
{
   
  int numberOfMenuFoldersWithCurrentMenuItems = 0
  select into variables (found variable)
    set numberOfMenuFoldersWithCurrentMenuItems = count(...)
  from 
    MenuItems // master table
  where
    IDFOLDER == IDFOLDER
   
  return numberOfMenuFoldersWithCurrentMenuItems
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private int MenuFolder.countNumberOfMenuFoldersWithEvaClassiEvento()
{
  int numberOfMenuFoldersWithCurrentEvaClassiEvento = 0
  select into variables (found variable)
    set numberOfMenuFoldersWithCurrentEvaClassiEvento = count(...)
  from 
    EVACLASSIEVENTO // master table
  where
    IDFOLDER == IDFOLDER
   
  return numberOfMenuFoldersWithCurrentEvaClassiEvento
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static MenuFolder MenuFolder.create(
  string folderDescription // 
)
{
  MenuFolder mf = new()
  mf.init()
   
  mf.FOLDERDESCR = folderDescription
   
  return mf
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event MenuFolder.OnInit()
{
  IDFOLDER = Sequence.getNextSequence(NGTN_ID_FOLDER, ...)
}


// ──────────────────────────────────

// ************************************************************
// Event raised to the document during the validation procedure
// ************************************************************
event MenuFolder.OnValidate(
  int:validateReasons Reason // An integer specifying the reason the document is being validated. The most common values are 0 to indicate complete validation before saving, or 1 to indicate a quick validati...
  inout boolean Error        // A boolean output parameter. If set to True, specifies an error condition and stops the validation immediately.
  inout boolean Skip         // A boolean output parameter. If set to True, does not check the rules for automatic validation of the document (see notes).
)
{
  if (deleted)
  {
    int numberOfMenuFoldersAssignedToMenuItems = this.countNumberOfMenuFoldersAsignedToMenuItems()
    int numberOfMenuFoldersAssignedToEvaClassiEvento = this.countNumberOfMenuFoldersWithEvaClassiEvento()
     
    if (numberOfMenuFoldersAssignedToMenuItems > 0 or numberOfMenuFoldersAssignedToEvaClassiEvento > 0)
    {
      this.addDocumentError("Non si può eliminare la cartella in quanto è usata da un elemento del menu o da una classe di evento.")
      Error = true
    }
  }
}


// ──────────────────────────────────

// ***********************************************************************
// Event raised to the document to determine the value of a named property
// ***********************************************************************
event MenuFolder.OnGetNamedPropertyValue(
  string PropertyName        // The name of the named property whose value to retrieve.
  inout string PropertyValue // An output parameter containing the value of the named property.
)
{
  if (PropertyName == "FONT_ICON")
  {
    string s = QappCore.IconFactory.GetFontAwesomeIcon(ICONINDEX)
    PropertyValue = s
  }
}


// ──────────────────────────────────

// ****************************************************************************
// Event raised to the document to determine the definition of a named property
// ****************************************************************************
event MenuFolder.OnGetNamedPropertyDefinition(
  string PropertyName                     // The name of the named property whose definition is sought.
  IDPropertyDefinition PropertyDefinition // The object of the IDPropertyDefinition type that will be used to communicate the property definition to the caller.
)
{
  if (PropertyName == "FONT_ICON")
  {
    PropertyDefinition.dataType = Character
  }
}


// ──────────────────────────────────

// ***********************************************************
// Event raised to the document when the transaction is closed
// ***********************************************************
event MenuFolder.OnEndTransaction()
{
  if (SEQ <= 0)
  {
    SEQ = 1
  }
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event MenuItem.OnInit()
{
  IDMENUITEM = Sequence.getNextSequence(NGT_ID_MENUITEM, ...)
}


// ──────────────────────────────────

// ***********************************************************************
// Event raised to the document to determine the value of a named property
// ***********************************************************************
event MenuItem.OnGetNamedPropertyValue(
  string PropertyName        // The name of the named property whose value to retrieve.
  inout string PropertyValue // An output parameter containing the value of the named property.
)
{
  if (PropertyName == "FONT_ICON")
  {
    string s = QappCore.IconFactory.GetFontAwesomeIcon(ICONINDEX)
    PropertyValue = s
  }
}


// ──────────────────────────────────

// ****************************************************************************
// Event raised to the document to determine the definition of a named property
// ****************************************************************************
event MenuItem.OnGetNamedPropertyDefinition(
  string PropertyName                     // The name of the named property whose definition is sought.
  IDPropertyDefinition PropertyDefinition // The object of the IDPropertyDefinition type that will be used to communicate the property definition to the caller.
)
{
  if (PropertyName == "FONT_ICON")
  {
    PropertyDefinition.dataType = Character
  }
}


// ──────────────────────────────────

// ***********************************************************
// Event raised to the document when the transaction is closed
// ***********************************************************
event MenuItem.OnEndTransaction()
{
  if (SEQ <= 0)
  {
    SEQ = 1
  }
}


// ──────────────────────────────────

// ************************************************************
// Event raised to the document during the validation procedure
// ************************************************************
event MenuItem.OnValidate(
  int:validateReasons Reason // An integer specifying the reason the document is being validated. The most common values are 0 to indicate complete validation before saving, or 1 to indicate a quick validati...
  inout boolean Error        // A boolean output parameter. If set to True, specifies an error condition and stops the validation immediately.
  inout boolean Skip         // A boolean output parameter. If set to True, does not check the rules for automatic validation of the document (see notes).
)
{
  if (IDFOLDER == 0 or IDFOLDER == null)
  {
    Error = true
    this.setPropertyError("Selezionare una cartella di destinazione.", IDFOLDER)
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static RuntimeMenuitem RuntimeMenuitem.create(
  string:runtimeMenuitemTypes type       // 
  int mainId // 
  optional int SCMId = 0                 // 
  optional string description = ""       // 
  optional int sequence = 0              // 
  optional boolean createAtRootLevel = 0 // 
  optional int Icon = 0                  // 
)
{
  RuntimeMenuitem rm = new()
  rm.init()
   
   
  if (type == EventiSCM and SCMId == 0)
  {
    QappCore.DTTLogMessage("Invalid SCMId", ..., DTTError)
    return null
  }
   
  rm.Type = type
  rm.MainId = mainId
  rm.SCMId = SCMId
  rm.Description = description
  rm.Sequence = sequence
  rm.IsRootItem = createAtRootLevel
  rm.ICONINDEX = Icon
  return rm
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string RuntimeMenuitem.computeMenuCode()
{
  string menuCode = ""
  string:menuItemSpecificInfoValues specificInfo = ""
  switch (Type)
  {
    case MainModule:
      specificInfo = TuttiGliEventiSpecificInfo
    break
    case EventiSCM:
    case AnaisSCM:
      specificInfo = toString(SCMId)
    break
    case Qapp:
      specificInfo = QappSpecificInfo
    break
  }
   
  menuCode = formatMessage("|1:|2", MainId, specificInfo, ...)
   
  return menuCode
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static IDCollection RuntimeMenuitem.getRuntimeMenuItemsBasedOnUser(
  MenuFolder menuFolder // 
  Utente utente         // 
)
{
  IDCollection allMenuItems of RuntimeMenuitem = new()
  IDCollection mainModuleMenuItems of RuntimeMenuitem = this.getMainModuleRuntimeMenuItemsBasedOnUser(menuFolder, utente)
  IDCollection eventiSCMMenuItems of RuntimeMenuitem = this.getEventiSCMRuntimeMenuItemsBasedOnUser(menuFolder, utente)
  IDCollection QappMenuItems of RuntimeMenuitem = this.getQappRuntimeMenuItemsBasedOnUser(menuFolder, utente)
   
//  if (menuFolder.CustomDescription == "INDICATORI_MENU_ITEM")
//  {
//    RuntimeMenuitem rm = RuntimeMenuitem.create(MainModule, Indicatori, null, "Indicatori", 1000, false, 0)
//    allMenuItems.add(rm)
//  }
   
  allMenuItems.addAll(mainModuleMenuItems, true)
  allMenuItems.addAll(eventiSCMMenuItems, true)
  allMenuItems.addAll(QappMenuItems, true)
   
  allMenuItems.addSortCriteria(RuntimeMenuitem.Sequence)
  allMenuItems.addSortCriteria(RuntimeMenuitem.Description)
  allMenuItems.doSort()
   
  return allMenuItems
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string RuntimeMenuitem.getIcon()
{
  string icon = ""
  if (ICONINDEX > 0)
  {
    icon = QappCore.IconFactory.GetFontAwesomeIcon(ICONINDEX)
  }
  else 
  {
    switch (MainId)
    {
      case ClientiFornitori:
         icon = "{{icon-fa-handshake fa-solid}}"
      break
      case Personale:
         icon = "{{icon-fa-user-tie fa-solid}}"
      break
    }
  }
  return icon
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private static IDCollection RuntimeMenuitem.getMainModuleRuntimeMenuItemsBasedOnUser(
  MenuFolder menuFolder // 
  Utente utente         // 
)
{
  IDCollection menuItems of RuntimeMenuitem = new()
   
  // Menu Items: mainmodule or anaIs SCM items can be found
  for each row (readonly)
  {
    select
      MODULENAMEMenuItem = MODULENAME
      SEQMenuItem = SEQ
      KORDAPPMenuItem = KORDAPP
      SUBKORDAPPMenuItem = SUBKORDAPP
      ISROOTFOLDERMenuItem = ISROOTFOLDER
      ICONINDEXMenuItem = ICONINDEX
    from 
      MenuItems // master table
    where
      IDFOLDER == menuFolder.IDFOLDER
     
    string:runtimeMenuitemTypes type = if(KORDAPPMenuItem == AltreAnagrafiche, AnaisSCM, MainModule)
    int:kordapp kordApp = KORDAPPMenuItem
    if (utente.hasSpecificPrivilege(kordApp, Execute))
    {
      RuntimeMenuitem runtimeMenuItemFromMenuItemsTable = RuntimeMenuitem.create(type, kordApp, SUBKORDAPPMenuItem, MODULENAMEMenuItem, SEQMenuItem, ISROOTFOLDERMenuItem == Yes, ICONINDEXMenuItem)
      menuItems.add(runtimeMenuItemFromMenuItemsTable)
    }
  }
  return menuItems
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private static IDCollection RuntimeMenuitem.getEventiSCMRuntimeMenuItemsBasedOnUser(
  MenuFolder menuFolder // 
  Utente utente         // 
)
{
  IDCollection menuItems of RuntimeMenuitem = new()
   
  //  SCM Eventi
  for each row (readonly)
  {
    select
      DESCRCLASSEEvaclassievento = DESCRCLASSE
      MENUMENUSEQUENZAEvaclassievento = MENUSEQUENZA
      IDCLASSEEvaclassievento = IDCLASSE
    from 
      EVACLASSIEVENTO // master table
    where
      INDEPENDANTMODULE == Yes
      HIDDEN == No
      IDFOLDER == menuFolder.IDFOLDER
      ATTIVA == Yes
     
    if (utente.canSeeSCMEvento(IDCLASSEEvaclassievento))
    {
      RuntimeMenuitem runtimeMenuItemFromClasseEVento = RuntimeMenuitem.create(EventiSCM, Eventi, IDCLASSEEvaclassievento, DESCRCLASSEEvaclassievento, MENUMENUSEQUENZAEvaclassievento, ...)
      menuItems.add(runtimeMenuItemFromClasseEVento)
    }
  }
  return menuItems
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private static IDCollection RuntimeMenuitem.getQappRuntimeMenuItemsBasedOnUser(
  MenuFolder menuFolder // 
  Utente utente         // 
)
{
  IDCollection menuItems of RuntimeMenuitem = new()
   
   
  //  Qapps from NGT APPLICAZIONI
  for each row (readonly)
  {
    select
      DESCRAPPLICAZIONENGTAPPLICAZIONI = DESCRAPPLICAZIONE
      SEQAPPLICAZIONENGTAPPLICAZIONI = SEQAPPLICAZIONE
      IDAPPLICAZIONENGTAPPLICAZIONI = IDAPPLICAZIONE
    from 
      NGTAPPLICAZIONI // master table
    where
      ISQAPPWEB == Yes
      SHOWINQUALIBUSMENU == Yes
      ((IDFOLDER == menuFolder.IDFOLDER) or (menuFolder.FOLDERDESCR == "Qapps"))
     
    boolean hasPrivilegeToShow = utente.HasSpecificPrivilegeForIDApplicazione(IDAPPLICAZIONENGTAPPLICAZIONI, Execute)
    QappCore.DTTLogMessage(formatMessage("user: "|1" has "|2" privilege to show "|3", utente.DESCRUTENTE, hasPrivilegeToShow, DESCRAPPLICAZIONENGTAPPLICAZIONI, ...), 676767, ...)
    if (hasPrivilegeToShow)
    {
      RuntimeMenuitem runtimeMenuItemFromQapp = RuntimeMenuitem.create(Qapp, IDAPPLICAZIONENGTAPPLICAZIONI, null, DESCRAPPLICAZIONENGTAPPLICAZIONI, SEQAPPLICAZIONENGTAPPLICAZIONI, ...)
      menuItems.add(runtimeMenuItemFromQapp)
    }
     
  }
   
  return menuItems
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string IconFactory.GetFontAwesomeIcon(
  int iconIndex // 
)
{
  string fontAwesomeIconKey = OfficialIconsMap.getValue(iconIndex)
   
   
  if (fontAwesomeIconKey != "")
  {
    fontAwesomeIconKey = formatMessage("{{icon-fa-|1}}", fontAwesomeIconKey, ...)
  }
  else 
  {
    fontAwesomeIconKey = "{{icon-fa-folder}}"
  }
   
  return fontAwesomeIconKey
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public IDArray IconFactory.getArrayOfAllAvailableIcons()
{
  IDArray allIconsKeys = OfficialIconsMap.getKeys()
   
  IDArray allFontAwesomeIconsArray = new()
   
  IDMap alreadyHandledIconsMap = new()
   
  // array is parsed backwards because getKeys returns keys in the backward order, so in this way the returned array
  for (int k = OfficialIconsMap.length() - 1; k >= 0; k = k - 1)
  {
    int:hardcodedIcons currentIconIndex = allIconsKeys.getValue(k)
    string currentIcon = QappCore.IconFactory.GetFontAwesomeIcon(currentIconIndex)
     
    if (!(alreadyHandledIconsMap.containsKey(currentIcon)))
    {
      alreadyHandledIconsMap.setValue(currentIcon, true)
      allFontAwesomeIconsArray.addValue(currentIcon)
    }
    else 
    {
      QappCore.DTTLogMessage("Ignored: " + currentIcon, ..., DTTInfo)
    }
  }
   
  return allFontAwesomeIconsArray
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public int IconFactory.getIndexFromFontAwesome(
  string fontAwesome // 
)
{
  int foundIndex = null
  string cleanedFontAwesome = this.cleanFontAwesome(fontAwesome)
   
  IDArray allKeys = OfficialIconsMap.getKeys()
   
  for (int i = 0; i < allKeys.length(); i = i + 1)
  {
    int:hardcodedIcons currentKey = allKeys.getValue(i)
    string currentFontAwesomeInnerString = OfficialIconsMap.getValue(currentKey)
     
    QappCore.DTTLogMessage(currentFontAwesomeInnerString + " " + toString(currentKey), ...)
    if (currentFontAwesomeInnerString == cleanedFontAwesome)
    {
      foundIndex = currentKey
      break 
    }
  }
   
  return foundIndex
}


// ──────────────────────────────────

// ***********************************************************************************************************************************
// hardcoding of the map using a match between fontawesome anc delphi icons (for more delphi icons same fontawesome is used, ex: cogs)
// ***********************************************************************************************************************************
public void IconFactory.initializeOfficialIconsMap()
{
  OfficialIconsMap.setValue(hardDiskWithARegistryOverIt, "hard-drive")
  OfficialIconsMap.setValue(Key, "key")
  OfficialIconsMap.setValue(MultipleChecks, "check-double")
  OfficialIconsMap.setValue(HandShake, "handshake")
  OfficialIconsMap.setValue(Tools, "screwdriver-wrench")
  OfficialIconsMap.setValue(FolderWithRegistries, "folder")
  OfficialIconsMap.setValue(3People, "people-group")
  OfficialIconsMap.setValue(Question, "question")
  OfficialIconsMap.setValue(LineChart, "chart-line")
  OfficialIconsMap.setValue(Objective, "bullseye")
  OfficialIconsMap.setValue(orgchart, "sitemap")
  OfficialIconsMap.setValue(Contact, "users")
  OfficialIconsMap.setValue(Mobile, "mobile")
  OfficialIconsMap.setValue(Gantt, "chart-gantt")
  OfficialIconsMap.setValue(editor, "file")
  OfficialIconsMap.setValue(PeopleTalking, "people-arrows")
  OfficialIconsMap.setValue(Schedule, "calendar-days")
  OfficialIconsMap.setValue(Grid, "table-cells-large")
  OfficialIconsMap.setValue(DocumentChecked, "file-circle-check")
  OfficialIconsMap.setValue(CogChecked, "cog")
  OfficialIconsMap.setValue(packWithRandomThingsInside, "cubes")
  OfficialIconsMap.setValue(ArrowRight, "arrow-rotate-right")
  OfficialIconsMap.setValue(info, "info")
  OfficialIconsMap.setValue(Star, "star")
  OfficialIconsMap.setValue(ClockThatGoesLikeAlwaysForward(thereIsAnArrowToRight), "clock")
  OfficialIconsMap.setValue(SimpleTools, "screwdriver-wrench")
  OfficialIconsMap.setValue(SimpleGantt, "chart-gantt")
  OfficialIconsMap.setValue(AnotherCogWithCheck, "cog")
  OfficialIconsMap.setValue(Warning, "triangle-exclamation")
  OfficialIconsMap.setValue(WorkersHelmet, "helmet-safety")
  OfficialIconsMap.setValue(DocumentSigned, "file-contract")
  OfficialIconsMap.setValue(SuperRadioactive, "circle-radiation")
  OfficialIconsMap.setValue(anotherOrgchart,ButYellow, "sitemap")
  OfficialIconsMap.setValue(Clinic, "briefcase-medical")
  OfficialIconsMap.setValue(graduationCap, "graduation-cap")
  OfficialIconsMap.setValue(AnotherCog,ButYellow, "cog")
  OfficialIconsMap.setValue(AnotherCog,ButGreen, "cog")
  OfficialIconsMap.setValue(AnotherCog,ButBlue, "cog")
  OfficialIconsMap.setValue(AnotherCog,ButRed, "cog")
  OfficialIconsMap.setValue(QualityCircle, "circle-notch")
  OfficialIconsMap.setValue(DollarCharOnAPaper, "euro-sign")
  OfficialIconsMap.setValue(Books, "book")
  OfficialIconsMap.setValue(Certificate, "certificate")
  OfficialIconsMap.setValue(AnotherCheck, "square-check")
  OfficialIconsMap.setValue(SafariIcon, "safari")
  OfficialIconsMap.setValue(ClientiFornitori, "handshake-simple")
  OfficialIconsMap.setValue(paperRolled, "note-sticky")
  OfficialIconsMap.setValue(Doctor, "user-doctor")
  OfficialIconsMap.setValue(BlueFlag, "flag")
  OfficialIconsMap.setValue(SimpleLineChart, "chart-line")
  OfficialIconsMap.setValue(AgainTools, "screwdriver-wrench")
  OfficialIconsMap.setValue(lightBulb, "lightbulb")
  OfficialIconsMap.setValue(Computer, "computer")
  OfficialIconsMap.setValue(network, "network-wired")
  OfficialIconsMap.setValue(personale, "user-tie")
  OfficialIconsMap.setValue(LogicBlocks, "diagram-project")
  OfficialIconsMap.setValue(bullhorn, "bullhorn")
  OfficialIconsMap.setValue(SchedulerWithClock, "calendar-days")
  OfficialIconsMap.setValue(EmailSicure, "envelope-circle-check")
  OfficialIconsMap.setValue(handshakeBetweenPriests, "handshake")
  OfficialIconsMap.setValue(lifesaver, "life-ring")
  OfficialIconsMap.setValue(calendar, "calendar-days")
  OfficialIconsMap.setValue(Shield, "shield")
  OfficialIconsMap.setValue(anotherHandlshake, "handshake")
  OfficialIconsMap.setValue(Helm, "sailboat")
  OfficialIconsMap.setValue(Notes, "note-sticky")
   
}


// ──────────────────────────────────

// **************************************************
// given {{icon-fa-something}}, something is returned
// 
// **************************************************
private static string IconFactory.cleanFontAwesome(
  string fontAwesomeIcon // 
)
{
  string cleanedString = replace(fontAwesomeIcon, "{{icon-fa-", "")
  cleanedString = replace(cleanedString, "}}", "")
   
  return cleanedString
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event IconFactory.OnInit()
{
  this.initializeOfficialIconsMap()
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static Orgchart Orgchart.getFromDB(
  int mainId                                                             // 
  optional string:getFromDBLoadingModes loadingMode = "normalZeroLevels" // 
)
{
  Orgchart o = cast(MainModule.retrieve(Organigrammi, mainId, loadingMode))
  return o
}


// ──────────────────────────────────

// *******************************************************************************************************************
// it returns the property index of the property linked to the main image field of the class (e.g. ART_ANAGRAFICA.FOTO
// *******************************************************************************************************************
public int Orgchart.PROTECTEDgetMainImagePropertyIndex()
{
  return null
}


// ──────────────────────────────────

// ****************************************************************************************************************************
// it returns the property index of the property linked to the second image field of the class (e.g. ART_ANAGRAFICA.FOTO_SECOND
// ****************************************************************************************************************************
public int Orgchart.PROTECTEDgetSecondImagePropertyIndex()
{
  return null
}


// ──────────────────────────────────

// *******************************************************************
// This method retrieve ID EMPLOYEE (Id DIPENDETE) for given ID UTENTE
// *******************************************************************
public static int Personale.GetIDEmployee(
  int IdUtente // NGT_UTENTI.ID_UTENTE
)
{
  int IdEmployee = 0
  Personale personale = new()
  personale.IDUTENTE = IdUtente
  try 
  {
    personale.loadFromDB(...)
    IdEmployee = personale.IDDIPENDENTE
  }
  catch 
  {
    throw 0, formatMessage("Unable to load employee for IdUtente=|1", IdUtente, ...)
  }
  return IdEmployee
}


// ──────────────────────────────────

// ************************************
// // get personale for given ID utente
// ************************************
public static Personale Personale.GetPersonale(
  int IdUtente // 
)
{
  Personale p = new()
  p.IDUTENTE = IdUtente
  try 
  {
    p.loadFromDB(0)
  }
  return p
}


// ──────────────────────────────────

// ****************************************************************************************************************
// create a new Link document for given document and Idrevision and add in to collection of Personale doc collegati
// ****************************************************************************************************************
public void Personale.AddDocCollegati(
  Documento Document // 
  int IdRevision     // 
)
{
  PERDOCUMENTI pd = new()
  pd.init()
  pd.IDDIPENDENTE = IDDIPENDENTE
  pd.IDDOCUMENTO = Document.IDDOCUMENTO
  pd.IDREVISIONE = null
  if (IdRevision <> 0)
  {
    pd.IDREVISIONE = IdRevision
  }
  PERDOCUMENTI.add(pd)
}


// ──────────────────────────────────

// **********************************************************************************************************
// Check if Document link already exists in collegati collection
// this method is require to validate/check when we create Document link to prevent Duplicate error on saving
// **********************************************************************************************************
public boolean Personale.DocumentLinkAlreadyExists(
  Documento document // 
)
{
  boolean sameDocumentLinkAlreadyExists = false
  this.loadPersonaleDocumentiCollegati(false, 0)
  for each PERDOCUMENTI perdocumenti in PERDOCUMENTI
  {
    if (perdocumenti.IDDOCUMENTO == document.IDDOCUMENTO)
    {
      sameDocumentLinkAlreadyExists = true
      break 
    }
  }
  return sameDocumentLinkAlreadyExists
}


// ──────────────────────────────────

// **********************************************************************************************
// (Static) Given an Utente it returns, if found (otherwise it returns null) the linked Personale
// **********************************************************************************************
public static Personale Personale.getPersonaleLinkedToUtente(
  Utente utente // 
)
{
  Personale foundPersonale = null
   
  IDCollection linkedPersonaleMonoRecordCollection of Personale = new()
  select into collection (linkedPersonaleMonoRecordCollection)
  from 
    Personale // master table
  where
    IDUTENTE == utente.IDUTENTE
   
   
   
  if (linkedPersonaleMonoRecordCollection.count() == 1)
  {
    linkedPersonaleMonoRecordCollection.moveFirst()
    foundPersonale = (Personale)linkedPersonaleMonoRecordCollection.getAt()
  }
  else 
  {
    foundPersonale = null
  }
   
  return foundPersonale
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public int Personale.IDfromDescription(
  string description // 
)
{
   
  string cleanDescription = replace(upper(description), " ", "")
   
  QualibusDB.maxRows = 1
  int matchingID = 0
  select into variables (found variable)
    set matchingID = IDDIPENDENTE
  from 
    Personale // master table
  where
    (replace(upper(NOME + COGNOME), " ", "") = cleanDescription) or (replace(upper(COGNOME + NOME), " ", "") = cleanDescription) or upper(CODICEFISCALE) == cleanDescription
   
  return matchingID
   
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Personale.computeStandardImportMetadata()
{
  base.addImportMetadato(tipologia, toPropertyIndex(IDTIPOANAGR), true, null, TipoPersonale.className(false), ...)
  base.addImportMetadato(matricola, toPropertyIndex(NROMATRICOLA), false, ...)
  base.addImportMetadato(stato_anagrafica, toPropertyIndex(STATO), true, null, "", IDMap.fromEnum(StatiPersonale))
  base.addImportMetadato(tipo_rapporto, toPropertyIndex(IDTIPORAPPORTO), false, null, PERTIPIRAPPORTO.className(false), ...)
  base.addImportMetadato(nome, toPropertyIndex(NOME), true, ...)
  base.addImportMetadato(cognome, toPropertyIndex(COGNOME), true, ...)
  base.addImportMetadato(sesso, toPropertyIndex(SESSO), false, ...)
  base.addImportMetadato(codice_fiscale, toPropertyIndex(CODICEFISCALE), true, ...)
  base.addImportMetadato(data_nascita, toPropertyIndex(DATANASCITA), true, ...)
  base.addImportMetadato(comune_nascita, toPropertyIndex(LUOGONASCITA), false, ...)
  base.addImportMetadato(cod_provincia_nascita, toPropertyIndex(CODPROVINCIANASCITA), false, ...)
  base.addImportMetadato(cod_nazione_nascita, toPropertyIndex(CODSTATONASCITA), false, ...)
  base.addImportMetadato(indirizzo, toPropertyIndex(INDIRIZZO), false, ...)
  base.addImportMetadato(cap, toPropertyIndex(CAP), false, ...)
  base.addImportMetadato(comune, toPropertyIndex(COMUNE), false, ...)
  base.addImportMetadato(cod_provincia, toPropertyIndex(CODPROVINCIA), false, ...)
  base.addImportMetadato(cod_nazione, toPropertyIndex(CODSTATO), false, ...)
  base.addImportMetadato(telefono, toPropertyIndex(TELEFONO), false, ...)
  base.addImportMetadato(email, toPropertyIndex(EMAILSECONDARIA), false, ...)
  base.addImportMetadato(cellulare, toPropertyIndex(CELLULARE), false, ...)
  base.addImportMetadato(data_assunzione, toPropertyIndex(DATAASSUNZIONE), false, ...)
  base.addImportMetadato(telefono_aziendale, toPropertyIndex(TELEFONOAZ), false, ...)
  base.addImportMetadato(email_aziendale, toPropertyIndex(EMAIL), false, ...)
  base.addImportMetadato(cellulare_aziendale, toPropertyIndex(CELLULAREAZ), false, ...)
  base.addImportMetadato(notetxt, toPropertyIndex(NOTETXT), false, ...)
  base.addImportMetadato(note, toPropertyIndex(NOTE), false, ...)
  base.addImportMetadato(stato_civile, toPropertyIndex(STATOCIVILE), false, ...)
  base.addImportMetadato(data_cessazione, toPropertyIndex(DATACESSAZIONE), false, ...)
  base.addImportMetadato(data_scadenza_prova, toPropertyIndex(DATASCADENZAPROVA), false, ...)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Personale.computeFullName()
{
  FullName = COGNOME + " " + NOME
}


// ──────────────────────────────────

// *****************************************************
// Computes the description of Reparto for linked Utente
// *****************************************************
private void Personale.computeReparto()
{
  if (IDUTENTE > 0)
  {
    string vDESCRREPARTO = ""
    select into variables (found variable)
      set vDESCRREPARTO = Reparti.DESCRREPARTO
    from 
      Utenti  // master table
      Reparti // joined with Utenti using key FK_NGT_UTENTI01
    where
      Utenti.IDUTENTE = IDUTENTE
    Reparto = vDESCRREPARTO
  }
}


// ──────────────────────────────────

// *******************************************************************************************************************
// it returns the property index of the property linked to the main image field of the class (e.g. ART_ANAGRAFICA.FOTO
// *******************************************************************************************************************
public int Personale.PROTECTEDgetMainImagePropertyIndex()
{
  return toPropertyIndex(FOTO)
}


// ──────────────────────────────────

// ****************************************************************************************************************************
// it returns the property index of the property linked to the second image field of the class (e.g. ART_ANAGRAFICA.FOTO_SECOND
// ****************************************************************************************************************************
public int Personale.PROTECTEDgetSecondImagePropertyIndex()
{
  return null
}


// ──────────────────────────────────

// *********************************************************************************
// giveen the query it returns a IDCollection of results for which the query matches
// it must be extended in every mainModule subclass
// *********************************************************************************
public IDCollection Personale.search(
  string query // 
)
{
   
  query = replace(query, " ", "|")
   
  string firstWord = SH.leftUpToDelimiter(query, "|", ...)
  firstWord = upper("%" + firstWord + "%")
   
  string lastWord = SH.rightUpToDelimiter(query, "|", ...)
  lastWord = upper("%" + lastWord + "%")
   
   
  IDCollection foundItems of Personale = new()
  select into collection (foundItems)
  from 
    Personale // master table
  where
    ((firstWord != lastWord) and (upper(NOME) like firstWord and upper(COGNOME) like lastWord)) or ((firstWord != lastWord) and (upper(COGNOME) like firstWord and upper(NOME) like
       lastWord)) or ((firstWord == lastWord) and (upper(NOME) like firstWord)) or ((firstWord == lastWord) and (upper(COGNOME) like firstWord))
   
   
  return foundItems
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean Personale.isLocked()
{
  return false
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean Personale.isClosed()
{
  return STATO == Licenziato/Chiuso
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public int Personale.setMainTxtFieldPropertyIndex()
{
  return toPropertyIndex(NOTETXT)
}


// ──────────────────────────────────

// *********************
// Method to be extended
// *********************
public int Personale.setMainHtmlFieldPropertyIndex()
{
  return toPropertyIndex(NOTEHTML)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static Personale Personale.create(
  string COGNOME       // 
  string NOME          // 
  int IdTipoAnagrafica // 
)
{
  Personale p = new()
  p.init()
  p.COGNOME = COGNOME
  p.NOME = NOME
  p.IDTIPOANAGR = IdTipoAnagrafica
  return p
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public Recordset Personale.getSmartLookupRecordsetForUtente(
  string querystring // 
)
{
  Recordset allUnassingedUsersPlusPersonaleUser = new()
  select into recordset (allUnassingedUsersPlusPersonaleUser)
    IDUTENTE as IDUTENTE
    DESCRUTENTE as DESCRUTENTE
  from 
    Utenti // master table
  where
    not(IDUTENTE in subquery)
      select top 1 // 
         IDUTENTE
      from 
         Personale // master table
      where
         Personale.IDUTENTE == Utenti.IDUTENTE
    ATTIVO == Yes
    DESCRUTENTE like "%" + querystring + "%"
   
   
  // we create a nee recordset item contining the value of user currently linekd to the personale object that is firing the smart lookup. This is done to allow the user to choose also that value from the
  // smartlookup dropdown
   
  int storedIdUtente = 0
  string storedDescrUtente = ""
  select into variables (found variable)
    set storedIdUtente = IDUTENTE
    set storedDescrUtente = DESCRUTENTE
  from 
    VUTENTIDIPENDENTI // master table
  where
    DESCRUTENTE like "%" + querystring + "%"
    IDDIPENDENTE == IDDIPENDENTE
   
  Collection recordPreviouslySetCurrentPersonale = new()
  recordPreviouslySetCurrentPersonale.addInteger(storedIdUtente)
  recordPreviouslySetCurrentPersonale.addString(storedDescrUtente)
  allUnassingedUsersPlusPersonaleUser.addRow(recordPreviouslySetCurrentPersonale)
   
  allUnassingedUsersPlusPersonaleUser.addSortCriteria("DESCRUTENTE ASC")
  allUnassingedUsersPlusPersonaleUser.doSort()
   
  return allUnassingedUsersPlusPersonaleUser
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string Personale.getInitials()
{
  string initials = ""
  if (NOME != "" and NOME != null)
  {
    initials = left(NOME, 1)
  }
   
  if (COGNOME != "" and COGNOME != null)
  {
    initials = initials + left(COGNOME, 1)
  }
   
  if (initials == "")
  {
    initials = "U"
  }
   
  return initials
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean Personale.isValidEmail(
  string email // 
)
{
  boolean isValidEmail = EmailTools.CheckEmailIsValid(trim(email))
   
  return isValidEmail
}


// ──────────────────────────────────

// *********************************************************************************************************************
// this NON STATIC method uses fullName for internal query string because in lookups fullname is used as decoding field 
// *********************************************************************************************************************
public Recordset Personale.createSmartLookupRecordsetFromFullName()
{
  string queryString = if(FullName == "*", "", FullName)
   
  IDCollection personaleColl of Personale = QappCore.Loggeduser.getPersonaleUtenteCanSee(queryString, lookupMode)
   
  Recordset r = new()
  RecordsetMetaData rmd = new()
  rmd.setColumnCount(2)
  rmd.setFieldName(1, "IDDIPENDENTE")
  rmd.setFieldName(2, "FULLNAME")
  rmd.setFieldType(1, Integer)
  rmd.setFieldType(2, Character)
  r.setMetaData(rmd)
   
  for each Personale p in personaleColl
  {
    Tools.addRowToRecordset(r, toString(p.IDDIPENDENTE), p.FullName)
  }
  r.addSortCriteria(2)
  r.doSort()
   
  return r
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Personale.loadPersonaleCarriera(
  optional boolean forceReloadFromDb = 1 // Write a comment for this parameter or press backspace to delete this comment
  optional int childrenLevel = 9999      // 
)
{
  if (forceReloadFromDb)
  {
    PERCARRIERE.loaded = false
  }
  this.loadCollectionFromDB(PERCARRIERE, childrenLevel)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Personale.loadPersonaleFunzioniRicoperte(
  optional boolean forceReloadFromDb = 1 // Write a comment for this parameter or press backspace to delete this comment
  optional int childrenLevel = 9999      // Write a comment for this parameter or press backspace to delete this comment
)
{
  if (forceReloadFromDb)
  {
    MSQPERSFUNZIONI.loaded = false
  }
  this.loadCollectionFromDB(MSQPERSFUNZIONI, childrenLevel)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Personale.loadPersonaleDocumentiCollegati(
  optional boolean forceReloadFromDb = 1 // Write a comment for this parameter or press backspace to delete this comment
  optional int childrenLevel = 9999      // Write a comment for this parameter or press backspace to delete this comment
)
{
  if (forceReloadFromDb)
  {
    PERDOCUMENTI.loaded = false
  }
  this.loadCollectionFromDB(PERDOCUMENTI, childrenLevel)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Personale.loadPersonaleCaratterizzazioni(
  optional boolean forceReloadFromDb = 1 // Write a comment for this parameter or press backspace to delete this comment
  optional int childrenLevel = 9999      // Write a comment for this parameter or press backspace to delete this comment
)
{
  if (forceReloadFromDb)
  {
    PERCARATTERIZZAZIONI.loaded = false
  }
  this.loadCollectionFromDB(PERCARATTERIZZAZIONI, childrenLevel)
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public static Personale Personale.GetByIdDipendente(
  int idDipendente                  // 
  optional boolean lightVersion = 0 // 
)
{
  if (idDipendente == null || idDipendente == 0)
  {
    return null
  }
   
  Personale p = new()
  try 
  {
    p.IDDIPENDENTE = idDipendente
     
    if (lightVersion)
      p.loadFromDB(0)
    else 
      p.loadFromDB(...)
  }
  catch 
  {
    p = null
  }
   
  return p
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string Personale.getCaption(
  string additionalDescription // 
)
{
  return "Personale " + "{{icon-fa-chevron-right}}"
}


// ──────────────────────────────────

// ************************************************************
// Event raised by the document after loading from the database
// ************************************************************
event Personale.AfterLoad(
  boolean AlreadyLoaded     // A boolean parameter that specifies whether the document was previously loaded.
  IDArray LoadedCollections // IDArray type parameter that contains the collections loaded by the framework. If the value of the Already Loaded parameter is False, the Loaded Collections parameter is NULL.
)
{
  base.AfterLoad(AlreadyLoaded, LoadedCollections)
   
  boolean IsQuickLoadRequested = base.quickLoadRequested()
   
  if (IsQuickLoadRequested)
  {
    return 
  }
   
  // implement here code that would be skipped by QuickLoad
   
   
  if (AlreadyLoaded)
  {
    return 
  }
  // 
   
  this.computeReparto()
  this.computeFullName()
  this.setOriginal()
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event Personale.OnInit()
{
  IDDIPENDENTE = Sequence.getNextSequence(PERN_ID_DIPENDENTE, ...)
  IDTIPOANAGR = 1
  STATO = Attivo
  NOME = "Dipendente" + toString(IDDIPENDENTE)
  COGNOME = "Dipendente" + toString(IDDIPENDENTE)
  PROFID = 0
  COSTOORARIO = 0,0
  ADDEBITOORARIO = 0,0
  PARTTIME = No
}


// ──────────────────────────────────

// **************************************************************************************
// Event raised to the document during the initial steps of the document saving procedure
// **************************************************************************************
event throws exception Personale.BeforeSave(
  inout boolean Skip         // A boolean output parameter. If set to True, tells the framework not to perform operations on the database, because they will be managed independently.
  inout boolean Cancel       // A boolean output parameter. If set to True, tells the framework to cancel the save procedure due to errors that occurred. Since the entire document is saved in the same transa...
  int:beforeSavePhases Phase // An integer from 0 to 3 that specifies the progress status of the save procedure as detailed at the beginning of the article.
)
{
  base.BeforeSave(Skip, Cancel, Phase)
   
  if (deleted)
  {
    this.loadPersonaleFunzioniRicoperte(...)
     
    for each FunzioneMembers msqpersfunzioni in MSQPERSFUNZIONI
    {
      msqpersfunzioni.deleted = true
      msqpersfunzioni.saveToDB(...)
    }
  }
}


// ──────────────────────────────────

// **************************************************************
// Event raised to the document when the Deleted property changes
// **************************************************************
event Personale.OnDeleting()
{
  if (deleted)
  {
    this.loadPersonaleFunzioniRicoperte(...)
     
    for each FunzioneMembers msqpersfunzioni in MSQPERSFUNZIONI
    {
      msqpersfunzioni.deleted = true
    }
  }
}


// ──────────────────────────────────

// ***********************************************************
// Event raised to the document when the transaction is closed
// ***********************************************************
event Personale.OnEndTransaction()
{
  if (wasModified(IDUTENTE))
  {
    this.computeReparto()
  }
  if (wasModified(COGNOME) or wasModified(NOME))
  {
    this.computeFullName()
  }
}


// ──────────────────────────────────

// ************************************************************
// Event raised to the document during the validation procedure
// ************************************************************
event Personale.OnValidate(
  int:validateReasons Reason // An integer specifying the reason the document is being validated. The most common values are 0 to indicate complete validation before saving, or 1 to indicate a quick validati...
  inout boolean Error        // A boolean output parameter. If set to True, specifies an error condition and stops the validation immediately.
  inout boolean Skip         // A boolean output parameter. If set to True, does not check the rules for automatic validation of the document (see notes).
)
{
  base.OnValidate(Reason, Error, Skip)
   
   
  string unformattedInvalidFormatErrorMessage = "Formato non valido <b>|1</b>"
  if (EMAIL != null)
  {
    boolean isValidemail = this.isValidEmail(EMAIL)
    if (!(isValidemail))
    {
      Error = true
      this.setPropertyError(formatMessage(unformattedInvalidFormatErrorMessage, EMAIL, ...), EMAIL)
    }
  }
   
  if (EMAILSECONDARIA != null)
  {
    boolean isValidEmailSecondaria = this.isValidEmail(EMAILSECONDARIA)
    if (!(isValidEmailSecondaria))
    {
      Error = true
      this.setPropertyError(formatMessage(unformattedInvalidFormatErrorMessage, EMAILSECONDARIA, ...), EMAILSECONDARIA)
    }
  }
}


// ──────────────────────────────────

// **************************************************************************
// Event called to customize the Smart Lookup procedure at the document level
// **************************************************************************
event Personale.OnGetSmartLookup(
  Recordset RecordSet       // The recordset to be filled if the queries run during the Smart Lookup event are customized.
  inout int Level           // The Smart Lookup procedure performs multiple attempts, increasingly widening the search field. This integer parameter specifies the number of attempts made.
  inout boolean NullValue   // A boolean output parameter. If set to True, then NULL (no results found) will be returned to the panel.
  inout boolean Skip        // A boolean output parameter. If set to True, then the standard queries for the current level will not be executed.
  inout boolean Cancel      // A boolean output parameter. If set to True, the Smart Lookup procedure will be stopped.
  IDDocument CallerDocument // The document for which the smart lookup procedure is launched. It can be null.
)
{
  Skip = true
  Cancel = true
   
  Recordset r = this.createSmartLookupRecordsetFromFullName()
   
  RecordSet.copyFrom(r)
}


// ──────────────────────────────────

// ***************************************
// Descrivi lo scopo di questa funzione...
// ***************************************
public int Personale.getMainID()
{
  return IDDIPENDENTE
}


// ──────────────────────────────────

// ***************************************
// Descrivi lo scopo di questa funzione...
// ***************************************
public int Personale.getTypeID()
{
  return IDTIPOANAGR
}


// ──────────────────────────────────

// ***************************************
// Descrivi lo scopo di questa funzione...
// ***************************************
public int Personale.getKordApp()
{
  return Personale
}


// ──────────────────────────────────

// ****************************************
// Describe the purpose of this function...
// ****************************************
public string Personale.getDescription()
{
   
  if (FullName == "")
    this.computeFullName()
   
  return FullName
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static Personale Personale.getFromDB(
  int mainId                                                             // 
  optional string:getFromDBLoadingModes loadingMode = "normalZeroLevels" // 
)
{
  Personale p = cast(MainModule.retrieve(Personale, mainId, loadingMode))
  return p
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event TipoPersonale.OnInit()
{
  IDTIPOANAGR = Sequence.getNextSequence(PERN_ID_TABELLE, ...)
  STATOATTIVO = Yes
  MATRICOLA = No
  SHOWCUSTOMDATA = Yes
  SHOWREFERENCES = Yes
  SHOWCAREER = Yes
  SHOWPROMEMORIA = Yes
  SHOWDOCUMENTS = Yes
  SHOWINRUBRICA = Yes
  SHOWSCOPE = No
}


// ──────────────────────────────────

// *************************************************************
// Event raised to the document at the end of the save procedure
// *************************************************************
event throws exception TipoPersonale.AfterSave(
  inout boolean Cancel // Boolean output parameter that makes it possible to cancel the entire save. It is equivalent to the Cancel parameter of the BeforeSave event.
)
{
  if (inserted)
  {
    IDCollection commonPersonaleCollections of CdataSection = new()
    select into collection (commonPersonaleCollections)
    from 
      CdataSection // master table
    where
      Active = Yes
      KordApp = Personale
      ISCOMMON = Yes
     
    for each CdataSection cs in commonPersonaleCollections
    {
      CDATAMODULETYPE cm = new()
      cm.init()
      cm.IDCDATASEC = cs.IDCDATASEC
      cm.IDTIPO = IDTIPOANAGR
      cm.saveToDB(...)
    }
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean TipoPersonale.shouldTabBeVisible(
  string:moduleSubformTypes subformType // 
)
{
  boolean result = false
  QappCore.DTTLogMessage(subformType, ..., DTTInfo)
   
  switch (subformType)
  {
    case CustomDataSubform:
      result = if(SHOWCUSTOMDATA == Yes, true, false)
    break
    case RiferimentiSubform:
      result = if(SHOWREFERENCES == Yes, true, false)
    break
    case PromemoriaSubform:
      result = if(SHOWPROMEMORIA == Yes, true, false)
    break
    case DocumentiSubform:
      result = if(SHOWDOCUMENTS == Yes, true, false)
    break
    case CarrieraSubform:
      result = if(SHOWCAREER == Yes, true, false)
    break
    case AmbitiSubform:
      result = if(SHOWSCOPE == Yes, true, false)
    break
    default:
      QappCore.DTTLogMessage(formatMessage("subform '|1' not found, so visibility is set to true", subformType, ...), ..., DTTInfo)
      result = true
    break
  }
   
  return result
}


// ──────────────────────────────────

// ****************************************************************************************
// unused method at the moment of writing since all visibility is in user visibillity class
// anyway it is tested and working
// ****************************************************************************************
public static IDCollection TipoPersonale.getIdsOfTipiPersonaleUserCanSee(
  Utente utente // 
)
{
  IDCollection tipoPersonaleIds of IntDataType = new()
//   
  // alternative approach with DB for discussion
  boolean userIsPers1InPersonale = utente.hasSpecificPrivilegeForKordApp(Personale, Pers1)
   
//  for each row (readonly)
//  {
//    select
//      idTipoAnagr = IDTIPOANAGR
//    from 
//      PERTIPIANAGR // master table
//    where
//      (userIsPers1InPersonale == true) or exists(subquery)
//         select // 
//         from 
//           TipoPersonaleRoles // master table
//         where
//           TipoPersonaleRoles.VISUALIZZA == Yes
//           TipoPersonaleRoles.IDUTENTE == utente.IDUTENTE
//    // 
//    IntDataType idt = IntDataType.createInteger(idTipoAnagr)
//    tipoPersonaleIds.add(idt)
//  }
   
   
  IDCollection tipipersonaleUserCanSee of TipoPersonale = new()
  if (userIsPers1InPersonale)
  {
    IDCollection allTipiPersonale of TipoPersonale = new()
     
    // return all existing tipi
    select into collection (allTipiPersonale)
    from 
      TipoPersonale // master table
     
    tipipersonaleUserCanSee.addAll(allTipiPersonale, true)
  }
  else 
  {
    DevTools.ToBeReviewed("PERTIPIRUOLI.getTipiPersonaleUserCanSee taking too much time becase of BeforeLoadCollection event")
    IDCollection tipoPersonaleRolesForUser of TipoPersonaleRoles = TipoPersonaleRoles.getTipiPersonaleUserCanSee(utente)
    for each TipoPersonaleRoles pertipiruoli in tipoPersonaleRolesForUser
    {
      TipoPersonale tp = TipoPersonale.get(pertipiruoli.IDTIPOANAGR)
      tipipersonaleUserCanSee.add(tp)
    }
  }
   
  for each TipoPersonale tp in tipipersonaleUserCanSee
  {
    tipoPersonaleIds.add(IntDataType.createInteger(tp.IDTIPOANAGR))
  }
   
   
  return tipoPersonaleIds
}


// ──────────────────────────────────

// **********************************
// get TipoClifor object for given ID
// **********************************
public static TipoPersonale TipoPersonale.get(
  int idTipoPersonale // 
)
{
  TipoPersonale tp = new()
  tp.IDTIPOANAGR = idTipoPersonale
  try 
  {
    tp.loadFromDB(0)
  }
  catch 
  {
    QappCore.DTTLogMessage(formatMessage("Unable to load TipoPersonale for ID: |1", idTipoPersonale, ...), ..., DTTError)
  }
  return tp
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event PERTIPIRAPPORTO.OnInit()
{
  IDTIPORAPPORTO = Sequence.getNextSequence(PERN_ID_TABELLE, ...)
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event PERCARATTERIZZAZIONI.OnInit()
{
  IDCARATTERIZZAZIONE = Sequence.getNextSequence(PERN_ID_CARATT, ...)
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event PERTIPICARATT.OnInit()
{
  IDTIPOCARATT = Sequence.getNextSequence(PERN_ID_TABELLE, ...)
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event PERQUALIFICHE.OnInit()
{
  IDQUALIFICA = Sequence.getNextSequence(PERN_ID_TABELLE, ...)
}


// ──────────────────────────────────

// *****************************************************************************************************************************************************************************************
// method to be called in BeforeLoadCollection that makes sure that the returned collection contains all the records (while C/S considers only "Y" records and generates "N" in memory only)
// *****************************************************************************************************************************************************************************************
public static void TipoPersonaleRoles.handleBeforeLoadCollection(
  inout boolean Skip                    // 
  IDCollection Collection of IDDocument // 
)
{
  Skip = true
   
  IDCollection allRuoli of TipoPersonaleRoles = new()
   
  for each row (readonly)
  {
    select
      IDTIPOANAGRPERTIPIANAGR1 = IDTIPOANAGR
    from 
      PERTIPIANAGR // master table
     
    for each row (readonly)
    {
      select
         IDUTENTE = IDUTENTE
      from 
         Utenti // master table
      where
         ATTIVO == Yes
       
      TipoPersonaleRoles ptr = new()
      ptr.IDUTENTE = IDUTENTE
      ptr.IDTIPOANAGR = IDTIPOANAGRPERTIPIANAGR
      try 
      {
         ptr.loadFromDB(...)
      }
      catch 
      {
         ptr.init()
         ptr.VISUALIZZA = No
      }
      ptr.loaded = true
      allRuoli.add(ptr)
    }
     
  }
   
  Collection.addAll(allRuoli, true)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static IDCollection TipoPersonaleRoles.getTipiPersonaleUserCanSee(
  Utente utente // 
)
{
  // temporarily commented to avoid effect of BeforeLoadCollection of TipoPersonaleRoles
   
  IDCollection tipoPersonaleUserCanSee of TipoPersonaleRoles = new()
//  select into collection (tipoPersonaleUserCanSee)
//  from 
//    TipoPersonaleRoles // master table
//  where
//    VISUALIZZA == Yes
//    IDUTENTE == utente.IDUTENTE
   
  // temporarily alternative code of above commented query
   
  for each row (readonly)
  {
    select
      idTipoAnagr = IDTIPOANAGR
    from 
      TipoPersonaleRoles // master table
    where
      IDUTENTE == utente.IDUTENTE
      VISUALIZZA == Yes
     
    TipoPersonaleRoles pertipiruoli = new()
    pertipiruoli.IDTIPOANAGR = idTipoAnagr
    tipoPersonaleUserCanSee.add(pertipiruoli)
  }
   
  return tipoPersonaleUserCanSee
}


// ──────────────────────────────────

// ********************************************************************
// Event raised to the document before loading a collection of its type
// ********************************************************************
event TipoPersonaleRoles.BeforeLoadCollection(
  IDDocument Parent                     // The parent document that requested loading of the collection. It can be Null Object if the event is raised by the LoadCollectionByExample procedure.
  IDCollection Collection of IDDocument // The collection to be loaded.
  inout boolean Skip                    // A boolean output parameter. If set to True, standard loading will not be performed.
)
{
   
  this.handleBeforeLoadCollection(Skip, Collection)
   
}


// ──────────────────────────────────

// ************************************************************
// Event raised to the document during the validation procedure
// ************************************************************
event Carriera.OnValidate(
  int:validateReasons Reason // An integer specifying the reason the document is being validated. The most common values are 0 to indicate complete validation before saving, or 1 to indicate a quick validati...
  inout boolean Error        // A boolean output parameter. If set to True, specifies an error condition and stops the validation immediately.
  inout boolean Skip         // A boolean output parameter. If set to True, does not check the rules for automatic validation of the document (see notes).
)
{
  string dateDalNotCompiledError = "Il valore del campo 'Dal' è obbligatorio"
   
  if (DADATA == null)
  {
    Error = true
    this.setPropertyError(dateDalNotCompiledError, DADATA)
  }
   
  string dateNotValid = "Date non valide"
  if (ADATA != null and DADATA > ADATA)
  {
    Error = true
    this.setPropertyError(dateNotValid, DADATA)
    this.setPropertyError(dateNotValid, ADATA)
  }
   
  string funzioneFieldNotCompiledError = "Il campo 'Funzione' è obbligatorio"
  if (IDFUNZIONE == null or IDFUNZIONE <= 0)
  {
    Error = true
    this.setPropertyError(funzioneFieldNotCompiledError, IDFUNZIONE)
  }
   
  string livelloNotCompiledError = "Il campo 'Contratto' è obbligatorio"
  if (IDQUALIFICA == null or IDQUALIFICA <= 0)
  {
    Error = true
    this.setPropertyError(livelloNotCompiledError, IDQUALIFICA)
  }
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event Carriera.OnInit()
{
  IDCARRIERA = Sequence.getNextSequence(PERN_ID_CARRIERA, ...)
  IDDIPENDENTE = 0
  DADATA = now()
  IDFUNZIONE = 0
  IDQUALIFICA = 0
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static Carriera Carriera.create(
  Personale dipendente // 
)
{
  Carriera c = new()
  c.init()
   
  c.IDDIPENDENTE = dipendente.IDDIPENDENTE
   
  return c
}


// ──────────────────────────────────

// ***************************************************************************************************************
// create a new Link document for given document and Idrevision and add in to collection of Privati doc collegati 
// ***************************************************************************************************************
public void Privati.AddDocCollegati(
  Documento Document // 
  int IdRevision     // 
)
{
  CISDOCUMENTI cd = new()
  cd.init()
  cd.IDCITTADINI = IDCITTADINI
  cd.IDDOCUMENTO = Document.IDDOCUMENTO
  cd.IDREVISIONE = null
  if (IdRevision <> 0)
  {
    cd.IDREVISIONE = IdRevision
  }
  CISDOCUMENTI.add(cd)
}


// ──────────────────────────────────

// **********************************************************************************************************
// Check if Document link already exists in collegati collection
// this method is require to validate/check when we create Document link to prevent Duplicate error on saving
// **********************************************************************************************************
public boolean Privati.DocumentLinkAlreadyExists(
  Documento document // 
)
{
  boolean sameDocumentLinkAlreadyExists = false
  this.loadCollectionFromDB(CISDOCUMENTI, 0)
  for each CISDOCUMENTI cisdocumenti in CISDOCUMENTI
  {
    if (cisdocumenti.IDDOCUMENTO == document.IDDOCUMENTO)
    {
      sameDocumentLinkAlreadyExists = true
      break 
    }
  }
  return sameDocumentLinkAlreadyExists
}


// ──────────────────────────────────

// ********************************
// create privati object and return
// ********************************
public static Privati Privati.create(
  optional string code = ""    // 
  optional string name = ""    // 
  optional string surname = "" // 
)
{
  Privati p = new()
  p.init()
  p.CODICE = code
  p.NOME = name
  p.COGNOME = surname
  return p
}


// ──────────────────────────────────

// *******************************************************************************************************************
// it returns the property index of the property linked to the main image field of the class (e.g. ART_ANAGRAFICA.FOTO
// *******************************************************************************************************************
public int Privati.PROTECTEDgetMainImagePropertyIndex()
{
  return toPropertyIndex(FOTO)
}


// ──────────────────────────────────

// ****************************************************************************************************************************
// it returns the property index of the property linked to the second image field of the class (e.g. ART_ANAGRAFICA.FOTO_SECOND
// ****************************************************************************************************************************
public int Privati.PROTECTEDgetSecondImagePropertyIndex()
{
  return null
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string Privati.getCaption(
  string additionalDescription // 
)
{
  return "Privati " + "{{icon-fa-chevron-right}}"
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean Privati.isLocked()
{
  return false
}


// ──────────────────────────────────

// ***************************************
// Descrivi lo scopo di questa funzione...
// ***************************************
public int Privati.getMainID()
{
  return IDCITTADINI
}


// ──────────────────────────────────

// ***************************************
// Descrivi lo scopo di questa funzione...
// ***************************************
public int Privati.getTypeID()
{
  return IDTIPOANAGR
}


// ──────────────────────────────────

// ***************************************
// Descrivi lo scopo di questa funzione...
// ***************************************
public int Privati.getKordApp()
{
  return Privati
}


// ──────────────────────────────────

// ****************************************
// Describe the purpose of this function...
// ****************************************
public string Privati.getDescription()
{
  return COGNOME + " " + NOME
}


// ──────────────────────────────────

// ************************************************************
// Event raised by the document after loading from the database
// ************************************************************
event Privati.AfterLoad(
  boolean AlreadyLoaded     // A boolean parameter that specifies whether the document was previously loaded.
  IDArray LoadedCollections // IDArray type parameter that contains the collections loaded by the framework. If the value of the Already Loaded parameter is False, the Loaded Collections parameter is NULL.
)
{
  base.AfterLoad(AlreadyLoaded, LoadedCollections)
   
  boolean IsQuickLoadRequested = base.quickLoadRequested()
   
  if (IsQuickLoadRequested)
  {
    return 
  }
   
  // implement here code that would be skipped by QuickLoad
   
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event Privati.OnInit()
{
  IDCITTADINI = Sequence.getNextSequence(CITT_ID_CITTADINI, ...)
  PROFID = 0
  STATO = "1"
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static Privati Privati.getFromDB(
  int mainId                                                             // 
  optional string:getFromDBLoadingModes loadingMode = "normalZeroLevels" // 
)
{
  Privati p = cast(MainModule.retrieve(Privati, mainId, loadingMode))
  return p
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event PrivatiTipoAnagrafica.OnInit()
{
  IDTIPOANAGR = Sequence.getNextSequence(CITT_ID_ANAGR, ...)
  DESCRTIPOANAGR = "Nuovo tipo anagrafica"
  STATOATTIVO = Yes
  MATRICOLA = No
}


// ──────────────────────────────────

// ***********************************************************************
// Event raised to the document to determine the value of a named property
// ***********************************************************************
event Promemoria.OnGetNamedPropertyValue(
  string PropertyName        // The name of the named property whose value to retrieve.
  inout string PropertyValue // An output parameter containing the value of the named property.
)
{
  if (PropertyName = "STARTDATE")
  {
    PropertyValue = convert(getDate(DATAINIZIOPROMEMORIA))
  }
   
  if (PropertyName = "STARTTIME")
  {
    time t = getTime(DATAINIZIOPROMEMORIA)
    PropertyValue = convert(t)
  }
  if (PropertyName == "STATUS")
  {
    if (isNull(IDESECUTORE))
    {
      PropertyValue = Fixed
    }
    else if (isNull(DATACHIUSURA))
    {
      PropertyValue = Open
    }
    else 
    {
      PropertyValue = Closed
    }
  }
   
  if (PropertyName == "DURATION")
  {
    // ORAINIZIO and ORAFINE sometimes contain NULL while DATAINIZIOPROMEMORIA and DATAFINEPROMEMORIA in the time part always contain the correct time
    time starttime = getTime(DATAINIZIOPROMEMORIA)
    time endtime = getTime(DATAFINEPROMEMORIA)
    int MinutesFromMidnightStartTime = hour(starttime) * 60 + minute(starttime)
    int MinutesFromMidnightEndTime = hour(endtime) * 60 + minute(endtime)
    //  
    // convert: metti in queestoIDVARIANT il valore ignorando il tipo sting, torniamo un intero
    PropertyValue = convert(MinutesFromMidnightEndTime - MinutesFromMidnightStartTime)
  }
  if (PropertyName == "DESCRIPTION")
  {
    // VALUTARE DI USARE GETTAG/SETTAG PER OTTIMIZZARE
    string ModuleDescription = ""
     
    switch (KORDAPP)
    {
      case ClientiFornitori:
         string vRAGIONESOCIALE = ""
         select into variables (found variable)
           set vRAGIONESOCIALE = RAGIONESOCIALE
         from 
           ClientiFornitori // master table
         where
           IDITEM = IDCONTO
         ModuleDescription = vRAGIONESOCIALE
          
      break
      case Articoli:
         string vCODARTICOLO = ""
         select into variables (found variable)
           set vCODARTICOLO = CODARTICOLO
         from 
           Articoli // master table
         where
           IDITEM = IDARTICOLO
         ModuleDescription = vCODARTICOLO
          
      break
      case Privati:
         string vCOGNOME = ""
         string vNOME = ""
         select into variables (found variable)
           set vCOGNOME = COGNOME
           set vNOME = NOME
         from 
           Privati // master table
         where
           IDITEM = IDCITTADINI
         ModuleDescription = Tools.Concatenate(vCOGNOME, vNOME, " ")
          
      break
      case AltreAnagrafiche:
         string vCODCESPITE = ""
         select into variables (found variable)
           set vCODCESPITE = Code
         from 
           AltreAnagrafiche // master table
         where
           IDITEM = ID
         ModuleDescription = vCODCESPITE
          
      break
      case Progetti:
         string vCODPROGETTO = ""
         select into variables (found variable)
           set vCODPROGETTO = CODPROGETTO
         from 
           Progetti // master table
         where
           IDITEM = IDPROGETTO
         ModuleDescription = vCODPROGETTO
          
      break
      case Personale:
         string vCOGNOMEEmployee = ""
         string vNOMEEmployee = ""
         select into variables (found variable)
           set vCOGNOMEEmployee = COGNOME
           set vNOMEEmployee = NOME
         from 
           Personale // master table
         where
           IDITEM = IDDIPENDENTE
         ModuleDescription = Tools.Concatenate(vCOGNOMEEmployee, vNOMEEmployee, " ")
          
      break
      case Funzioni:
         string vCODFUNZIONE = ""
         select into variables (found variable)
           set vCODFUNZIONE = CODFUNZIONE
         from 
           Funzioni // master table
         where
           IDITEM = IDFUNZIONE
         ModuleDescription = vCODFUNZIONE
          
      break
      default:
         // Organigrammi goes in the Default
         ModuleDescription = ""
      break
    }
    // 
    PropertyValue = Tools.Concatenate(ModuleDescription, Description, ...)
     
  }
  //  
  //  
  // Scrivi un commento per questo blocco o premi backspace per eliminare questo commento
  if (PropertyName = "COLOR")
  {
    int ModuleColor = 0
    select into variables (found variable)
      set ModuleColor = Colore
    from 
      Colorimoduli // master table
    where
      Kordapp = KORDAPP
     
    PropertyValue = convert(Tools.DxcolorToInde(ModuleColor))
     
  }
  if (PropertyName = "ICON")
  {
    switch (this.getNamedPropertyValue("STATUS"))
    {
      case Fixed:
         PropertyValue = convert(PromemoriaSenzaEsecutore)
      break
      case Open:
         PropertyValue = convert(PromemoriaConEsecutore)
      break
      default:
         PropertyValue = convert(PromemoriaChiuso)
      break
    }
  }
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event Promemoria.OnInit()
{
  IDITEM = 0
  RISERVATO = Yes
  IDUTENTEINS = QappCore.Loggeduser.IDUTENTE
  DATAINS = now()
  IDUTENTEULTMOD = QappCore.Loggeduser.IDUTENTE
  DATAULTIMAMOD = now()
  EVENTTYPEDISP = 0
  OPTIONSDISP = 2
  STATE = 2
  RECCURENCEINDEX = -1
  PROFID = 0
  LABELCOLOR = Nessuno
  this.initializePromemoriaDateTime()
   
   
  this.computeNextId()
}


// ──────────────────────────────────

// ***********************************************************
// Event raised to the document when the transaction is closed
// ***********************************************************
event Promemoria.OnEndTransaction()
{
  if (wasModified(IDTIPOPROMEMORIA))
  {
    if (Description == "")
      Description = this.getDefaultDescription()
  }
   
  if (wasModified(PromemoriaDate) or wasModified(StartTime) or wasModified(EndTime))
  {
    this.encodeTimeAndDateProperties()
  }
   
  if (wasModified(NOTECHIUSURA))
  {
     
    // this was added to debug but without this notes are not stored so left for now...
    QappCore.DTTLogMessage("Note Chiusura just modified", ..., DTTInfo)
  }
  if (wasModified(IDESECUTORE))
  {
    if (IDESECUTORE <= 0)
    {
      DATACHIUSURA = null
      IDUTENTECHIUSURA = null
      ESITO = null
    }
  }
}


// ──────────────────────────────────

// ************************************************************
// Event raised by the document after loading from the database
// ************************************************************
event Promemoria.AfterLoad(
  boolean AlreadyLoaded     // A boolean parameter that specifies whether the document was previously loaded.
  IDArray LoadedCollections // IDArray type parameter that contains the collections loaded by the framework. If the value of the Already Loaded parameter is False, the Loaded Collections parameter is NULL.
)
{
  this.decodeTimeAndDateProperties()
  this.setOriginal()
}


// ──────────────────────────────────



// ──────────────────────────────────

// **************************************************************************************
// Event raised to the document during the initial steps of the document saving procedure
// **************************************************************************************
event throws exception Promemoria.BeforeSave(
  inout boolean Skip         // A boolean output parameter. If set to True, tells the framework not to perform operations on the database, because they will be managed independently.
  inout boolean Cancel       // A boolean output parameter. If set to True, tells the framework to cancel the save procedure due to errors that occurred. Since the entire document is saved in the same transa...
  int:beforeSavePhases Phase // An integer from 0 to 3 that specifies the progress status of the save procedure as detailed at the beginning of the article.
)
{
  if (Phase == PreSave)
  {
    if (!(deleted))
    {
      this.PerformStoringOfEditData()
    }
  }
}


// ──────────────────────────────────

// ****************************************************
// Raised to the document during duplication operations
// ****************************************************
event Promemoria.OnDuplicate(
  IDDocument SourceDocument // This parameter contains the original document when duplicating the document header. Otherwise, its value is Null Object. This way, it is possible to distinguish the two cases, ...
)
{
  this.computeNextId()
}


// ──────────────────────────────────

// ********************************************************************
// Event raised to the document before loading a collection of its type
// ********************************************************************
event Promemoria.BeforeLoadCollection(
  IDDocument Parent                     // The parent document that requested loading of the collection. It can be Null Object if the event is raised by the LoadCollectionByExample procedure.
  IDCollection Collection of IDDocument // The collection to be loaded.
  inout boolean Skip                    // A boolean output parameter. If set to True, standard loading will not be performed.
)
{
   
  // since Promemoria is based on a table that is specific to all modules we need a ad hoc query to load a collection
   
  if (!(MainModule.isMyInstance(Parent)))
  {
    return 
  }
  if (Collection.loaded)
  {
    return 
  }
  Skip = true
  MainModule parentMainModule = (MainModule)Parent
   
  int:kordapp kordApp = parentMainModule.getKordApp()
  int mainId = parentMainModule.getMainID()
   
   
  IDCollection promemoriaOfParentMainModule of Promemoria = new()
  select into collection (promemoriaOfParentMainModule)
  from 
    Promemoria // master table
  where
    IDITEM == mainId
    KORDAPP == kordApp
   
   
  // IMPORTANT: we do Move because otherwise the collection does not "sense" it has the document
  Collection.addAll(promemoriaOfParentMainModule, ...)
  Collection.loaded = true
   
  // since the collection is just retrieved we mark as original or it will look modified
  Collection.setOriginal()
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public static IDCollection Promemoria.GetEmployeePromemoria(
  Personale employee  // 
  date time StartDate // 
  date time EndDate   // 
)
{
   
  IDCollection userpromemoria of Promemoria = new()
  select into collection (userpromemoria)
    set IDPROMEMORIA = IDPROMEMORIA
    set OLDID = OLDID
    set KORDAPP = KORDAPP
    set IDTIPOPROMEMORIA = IDTIPOPROMEMORIA
    set IDSTATOPROMEMORIA = IDSTATOPROMEMORIA
    set PROMEMORIAGRUPPO = PROMEMORIAGRUPPO
    set IDITEM = IDITEM
    set RISERVATO = RISERVATO
    set IDUTENTEINS = IDUTENTEINS
    set DATAINS = DATAINS
    set IDUTENTEULTMOD = IDUTENTEULTMOD
    set DATAULTIMAMOD = DATAULTIMAMOD
    set PRGIDINTERFACCIA = PRGIDINTERFACCIA
    set PRGIDFASE = PRGIDFASE
    set CLIFORIDCONTATTO = CLIFORIDCONTATTO
    set ASFPVIDAMBITO = ASFPVIDAMBITO
    set ASFPVIDARTICOLO = ASFPVIDARTICOLO
    set ASFPVLUOGOEVENTO = ASFPVLUOGOEVENTO
    set ASFPVDESCRRISULTATO = ASFPVDESCRRISULTATO
    set DESCRPROMEMORIA = DESCRPROMEMORIA
    set DATAINIZIOPROMEMORIA = DATAINIZIOPROMEMORIA
    set ORARIOINIZIO = ORARIOINIZIO
    set DATAFINEPROMEMORIA = DATAFINEPROMEMORIA
    set ORARIOFINE = ORARIOFINE
    set IDESECUTORE = IDESECUTORE
    set IDUTENTECHIUSURA = IDUTENTECHIUSURA
    set DATACHIUSURA = DATACHIUSURA
    set NOTECHIUSURA = NOTECHIUSURA
    set ESITO = ESITO
    set EVENTTYPEDISP = EVENTTYPEDISP
    set OPTIONSDISP = OPTIONSDISP
    set LABELCOLOR = LABELCOLOR
    set STATE = STATE
    set RESOURCEID = RESOURCEID
    set ACTUALFINISH = ACTUALFINISH
    set ACTUALSTART = ACTUALSTART
    set LOCATION = LOCATION
    set MESSAGE = MESSAGE
    set PARENTID = PARENTID
    set RECCURENCEINDEX = RECCURENCEINDEX
    set RECCURENCEINFO = RECCURENCEINFO
    set REMINDERDATE = REMINDERDATE
    set REMINDERMINSBEFORESTART = REMINDERMINSBEFORESTART
    set PROFID = PROFID
  from 
    Promemoria // master table
  where
    IDESECUTORE = employee.IDDIPENDENTE
    DATAINIZIOPROMEMORIA >= StartDate && DATAFINEPROMEMORIA <= EndDate
    KORDAPP != Calendar
   
  return userpromemoria
}


// ──────────────────────────────────



// ──────────────────────────────────

// **********************************************************
// This method will Update following fields
// 1. Execution Start date with time (DATA_INIZIO_PROMEMORIA)
// 2. Execution Start Time (Float value - )ORARIO_INIZIO
// 3. Execution End date with time (DATA_INIZIO_PROMEMORIA)
// 4. Execution End Time (Float value - )ORARIO_INIZIO
// **********************************************************
public void Promemoria.SetExecutionDateTime(
  date Date                           // date
  time Time                           // time
  optional int DurationInMinutes = 30 // Duration of time in minutes
)
{
   
   
  date time StartDateTime = dateTime(year(Date), month(Date), day(Date), hour(Time), minute(Time), second(Time))
  DATAINIZIOPROMEMORIA = StartDateTime
  DATAFINEPROMEMORIA = dateAdd(Minute, DurationInMinutes, DATAINIZIOPROMEMORIA)
  ORARIOINIZIO = Tools.TimeToFloat(DATAINIZIOPROMEMORIA)
  ORARIOFINE = Tools.TimeToFloat(DATAFINEPROMEMORIA)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static Promemoria Promemoria.get(
  int idPromemoria // 
)
{
  Promemoria promemoria = new()
  promemoria.IDPROMEMORIA = idPromemoria
  try 
  {
    promemoria.loadFromDB(...)
  }
  catch 
  {
    promemoria = null
    QappCore.DTTLogMessage(formatMessage("unable to load calendar memo for ID: |1", idPromemoria, ...), ...)
  }
  return promemoria
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void Promemoria.encodeTimeAndDateProperties()
{
  ORARIOINIZIO = Tools.TimeToFloat(StartTime)
  ORARIOFINE = Tools.TimeToFloat(EndTime)
   
  DATAINIZIOPROMEMORIA = dateTime(year(PromemoriaDate), month(PromemoriaDate), day(PromemoriaDate), hour(StartTime), minute(StartTime), 0)
  DATAFINEPROMEMORIA = dateTime(year(PromemoriaDate), month(PromemoriaDate), day(PromemoriaDate), hour(EndTime), minute(EndTime), 0)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void Promemoria.decodeTimeAndDateProperties()
{
  PromemoriaDate = toDate(year(DATAINIZIOPROMEMORIA), month(DATAINIZIOPROMEMORIA), day(DATAINIZIOPROMEMORIA))
  StartTime = Tools.FloatToTime(ORARIOINIZIO)
  EndTime = Tools.FloatToTime(ORARIOFINE)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public Recordset Promemoria.computeTipoLookupRecordset(
  optional string searchCondition = "" // 
)
{
  string likeClauseForSearchCondition = "%" + if(searchCondition == "*", "", searchCondition) + "%"
   
  Recordset rs = new()
  select into recordset (rs)
    IDTIPOPROMEMORIA as IDTIPOPROMEM
    DESCRTIPOPROMEMORIA as DESCTIPOPROM
    SEQUENZA as SEQUENZA
  from 
    TipiPromemoria // master table
  where
    DESCRTIPOPROMEMORIA like likeClauseForSearchCondition
    (ANACLIFOR == Yes and KORDAPP == ClientiFornitori) or (ANAPERSONALE == Yes and KORDAPP == Personale) or (PROGETTI == Yes and KORDAPP == Progetti) or (PRIVATI == Yes and KORDAPP == Privati)
       or (EVENTI == Yes and KORDAPP == Eventi) or (ANAFUNZIONI == Yes and KORDAPP == Funzioni) or (ORGANIGRAMMI == Yes and KORDAPP == Organigrammi) or (ANAIS == Yes and KORDAPP == AltreAnagrafiche
       ) or (ANAARTICOLI == Yes and KORDAPP == Articoli)
  order by
    SEQUENZA
    DESCRTIPOPROMEMORIA
   
  return rs
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public Recordset Promemoria.computeStatoLookupRecordset(
  optional string searchCondition = "" // 
)
{
  string likeClauseForSearchCondition = "%" + if(searchCondition == "*", "", searchCondition) + "%"
   
  Recordset rs = new()
  select into recordset (rs)
    IDSTATOPROMEMORIA as IDSTATOPROME
    DESCRSTATOPROMEMORIA as DESCSTATPROM
  from 
    StatiPromemoria // master table
  where
    DESCRSTATOPROMEMORIA like likeClauseForSearchCondition
    (ANACLIFOR == Yes and KORDAPP == ClientiFornitori) or (ANAPERSONALE == Yes and KORDAPP == Personale) or (PROGETTI == Yes and KORDAPP == Progetti) or (PRIVATI == Yes and KORDAPP == Privati)
       or (EVENTI == Yes and KORDAPP == Eventi) or (ANAFUNZIONI == Yes and KORDAPP == Funzioni) or (ORGANIGRAMMI == Yes and KORDAPP == Organigrammi) or (ANAIS == Yes and KORDAPP == AltreAnagrafiche
       ) or (ANAARTICOLI == Yes and KORDAPP == Articoli)
   
  return rs
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Promemoria.close(
  string closureNotes           // 
  optional boolean success = -1 // 
)
{
  if (QappCore.Loggeduser.IDUTENTE <= 0)
  {
    QappCore.DTTLogMessage("Logged user is not set", ..., DTTError)
    return 
  }
   
  NOTECHIUSURA = closureNotes
  ESITO = if(success, Positive, Negative)
   
  DATACHIUSURA = now()
  IDUTENTECHIUSURA = QappCore.Loggeduser.IDUTENTE
   
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Promemoria.reopen()
{
  if (QappCore.Loggeduser.IDUTENTE <= 0)
  {
    QappCore.DTTLogMessage("Logged user is not set", ..., DTTError)
    return 
  }
   
  NOTECHIUSURA = null
  ESITO = null
   
  DATACHIUSURA = null
  IDUTENTECHIUSURA = null
   
   
}


// ──────────────────────────────────

// ****************************************************************
// retrieve list of contacts in recordset for idConto of promemoria
// ****************************************************************
public Recordset Promemoria.ComputeContattoLookupRecordset()
{
   
  Recordset rs = new()
  select into recordset (rs)
    IDCONTATTO as IDCONTATTO
    COGNOME + " " + NOME as FULLNAME
  from 
    GCFCONTATTI // master table
  where
    IDCONTO == IDITEM
  order by
    COGNOMEGCFCONTATTI+""+NOMEGCFCONTATTI
   
  return rs
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string Promemoria.getClosureConfirmationMessage()
{
  string message = formatMessage("|1, confermi l'esecuzione dell'impegno '|2'?", QappCore.Loggeduser.DESCRUTENTE, Description, ...)
  return message
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private string Promemoria.getDefaultDescription()
{
  string retrievedDefaultDescription = ""
  TipoPromemoria tp = getLinkedDocument(false, TipoPromemoria.className(...), ...)
  if (tp)
  {
    retrievedDefaultDescription = tp.DESCRPROMDEFAULT
  }
  return retrievedDefaultDescription
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void Promemoria.computeNextId()
{
  IDPROMEMORIA = Sequence.getNextSequence(DIS_ID_PROMEMORIA, ...)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean Promemoria.isClosed()
{
  boolean promemoriaIsClosed = DATACHIUSURA != null
  return promemoriaIsClosed
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static Promemoria Promemoria.create(
  int:kordapp kordApp               // 
  int moduleID                      // 
  optional int idTipoPromemoria = 0 // 
)
{
  Promemoria p = new()
  p.init()
  p.KORDAPP = kordApp
  p.IDITEM = moduleID
  if (idTipoPromemoria > 0)
  {
    p.IDTIPOPROMEMORIA = idTipoPromemoria
  }
  return p
}


// ──────────────────────────────────

// **************************************************************
// Initialize fields related to date and time of promemoria 
// This method is called on Init event when promemoria is created
// **************************************************************
public void Promemoria.initializePromemoriaDateTime()
{
  date currentDate = today()
  time currentTime = time()
  DATAINIZIOPROMEMORIA = dateTime(year(currentDate), month(currentDate), day(currentDate), hour(currentTime), minute(currentTime), second(currentTime))
  DATAFINEPROMEMORIA = dateAdd(Minute, 30, DATAINIZIOPROMEMORIA)
   
  time endTime = dateAdd(Minute, 30, currentTime)
  ORARIOINIZIO = Tools.TimeToFloat(currentTime)
  ORARIOFINE = Tools.TimeToFloat(endTime)
   
  this.decodeTimeAndDateProperties()
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean Promemoria.isClosable()
{
   
  boolean isPromemoriaClosable = false
  boolean isExecutorAssigned = IDESECUTORE > 0
  boolean isClosed = this.isClosed()
  boolean valid = validate(...)
  isPromemoriaClosable = !(isClosed) and valid and isExecutorAssigned
   
  return isPromemoriaClosable
}


// ──────────────────────────────────

// ****************************************
// Describe the purpose of this function...
// ****************************************
public void Promemoria.PerformStoringOfEditData()
{
  IDUTENTEULTMOD = QappCore.Loggeduser.IDUTENTE
  DATAULTIMAMOD = now()
}


// ──────────────────────────────────

// **************************************************************************
// Event called to customize the Smart Lookup procedure at the document level
// **************************************************************************
event StatoPromemoria.OnGetSmartLookup(
  Recordset RecordSet       // The recordset to be filled if the queries run during the Smart Lookup event are customized.
  inout int Level           // The Smart Lookup procedure performs multiple attempts, increasingly widening the search field. This integer parameter specifies the number of attempts made.
  inout boolean NullValue   // A boolean output parameter. If set to True, then NULL (no results found) will be returned to the panel.
  inout boolean Skip        // A boolean output parameter. If set to True, then the standard queries for the current level will not be executed.
  inout boolean Cancel      // A boolean output parameter. If set to True, the Smart Lookup procedure will be stopped.
  IDDocument CallerDocument // The document for which the smart lookup procedure is launched. It can be null.
)
{
  if (CallerDocument)
  {
    if (Promemoria.isMyInstance(CallerDocument))
    {
      Promemoria promemoria = (Promemoria)CallerDocument
      if (promemoria)
      {
         Recordset computedRecordSet = promemoria.computeStatoLookupRecordset(DESCRSTATOPROMEMORIA)
         RecordSet.copyFrom(computedRecordSet)
      }
    }
  }
}


// ──────────────────────────────────

// **************************************************************************
// Event called to customize the Smart Lookup procedure at the document level
// **************************************************************************
event TipoPromemoria.OnGetSmartLookup(
  Recordset RecordSet       // The recordset to be filled if the queries run during the Smart Lookup event are customized.
  inout int Level           // The Smart Lookup procedure performs multiple attempts, increasingly widening the search field. This integer parameter specifies the number of attempts made.
  inout boolean NullValue   // A boolean output parameter. If set to True, then NULL (no results found) will be returned to the panel.
  inout boolean Skip        // A boolean output parameter. If set to True, then the standard queries for the current level will not be executed.
  inout boolean Cancel      // A boolean output parameter. If set to True, the Smart Lookup procedure will be stopped.
  IDDocument CallerDocument // The document for which the smart lookup procedure is launched. It can be null.
)
{
  Skip = true
  Cancel = true
   
   
  if (Promemoria.isMyInstance(CallerDocument))
  {
    Promemoria promemoria = (Promemoria)CallerDocument
    if (promemoria)
    {
      Recordset computedRecordSet = promemoria.computeTipoLookupRecordset(DESCRTIPOPROMEMORIA)
      RecordSet.copyFrom(computedRecordSet)
    }
  }
   
}


// ──────────────────────────────────

// **************************************************************************
// Event raised to the document when a panel requests execution of a decoding
// **************************************************************************
event TipoPromemoria.BeforeLookup(
  inout boolean Skip   // A boolean output parameter. If set to True, the framework will not execute the lookup via a query.
  inout boolean Cancel // A boolean output parameter. If set to True, the decoding procedure ends immediately. The AfterLookup event will not be called.
)
{
   
  // this will decode also in case a tipo promemoria is not assigned anymore to a kord app (same as active = false)
  Skip = true
   
  string lookupValue = ""
  select into variables (found variable)
    set lookupValue = DESCRTIPOPROMEMORIA
  from 
    TipiPromemoria // master table
  where
    IDTIPOPROMEMORIA == IDTIPOPROMEMORIA
   
  DESCRTIPOPROMEMORIA = lookupValue
}


// ──────────────────────────────────



// ──────────────────────────────────

// **************************************************************************
// Event called to customize the Smart Lookup procedure at the document level
// **************************************************************************
event PRGFASI.OnGetSmartLookup(
  Recordset RecordSet       // The recordset to be filled if the queries run during the Smart Lookup event are customized.
  inout int Level           // The Smart Lookup procedure performs multiple attempts, increasingly widening the search field. This integer parameter specifies the number of attempts made.
  inout boolean NullValue   // A boolean output parameter. If set to True, then NULL (no results found) will be returned to the panel.
  inout boolean Skip        // A boolean output parameter. If set to True, then the standard queries for the current level will not be executed.
  inout boolean Cancel      // A boolean output parameter. If set to True, the Smart Lookup procedure will be stopped.
  IDDocument CallerDocument // The document for which the smart lookup procedure is launched. It can be null.
)
{
  Skip = true
  if (Promemoria.isMyInstance(CallerDocument))
  {
    Promemoria p = (Promemoria)CallerDocument
     
    Recordset idsOnlyRs = new()
    select into recordset (idsOnlyRs)
      IDFASE as ID_FASE
    from 
      PRGFASI // master table
    where
      IDPROGETTO == p.IDITEM
     
    Recordset lookupRecordset = null
    lookupRecordset = SmartLookupHelper.getWellFormedLookupRecordset(FullCode, this.className(...), idsOnlyRs)
     
    Recordset properlySortedRS = this.moveProgettoFaseAtFirst(lookupRecordset)
     
    RecordSet.copyFrom(properlySortedRS)
     
    // it is important tos et NullValue to true when no records found or the smart lookup will behave bad
    if (properlySortedRS.recordCount() == 0)
    {
      NullValue = true
      Cancel = true
    }
     
  }
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event PRGFASI.OnInit()
{
  IDFASE = Sequence.getNextSequence(PRGN_ID_FASE, ...)
  SEQUENZA = 0
  VISUALIZZAGANTT = No
  SOSPESA = No
  RIESAME = No
  DATAINIZIO = today()
  PERCAVANZAMENTO = 0
  ORELAVOROPREV = 0
  COSTOORARIO = 0
  ALTRICOSTI = 0
  CODFASE = this.getFixedNameOfProgettoFase()
  LIVELLO = 0
  NOTIFICA = No
  AVANZAMENTOMANUALE = Yes
  RICAVIPREVISTI = 0
   
}


// ──────────────────────────────────

// *******************************************
// Returns the Fase Padre of the current Phase
// *******************************************
public PRGFASI PRGFASI.getFasePadre()
{
  PRGFASI prgfasi = new()
  prgfasi.IDFASE = IDFASEPADRE
  prgfasi.IDPROGETTO = IDPROGETTO
   
  try 
  {
    prgfasi.loadFromDB(0)
    return prgfasi
  }
  catch 
  {
    return null
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static PRGFASI PRGFASI.get(
  int idFase // 
)
{
  PRGFASI fase = new()
  fase.IDFASE = idFase
  fase.loadFromDB(...)
  return fase
}


// ──────────────────────────────────

// *****************************************************************************************************
// This method returns the fixed name expected for the Progetto fase (the one whose ID TIPO FASE is null
// *****************************************************************************************************
public static string PRGFASI.getFixedNameOfProgettoFase()
{
  return "Progetto"
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void PRGFASI.computeFullCode()
{
  PRGTIPIFASE tipoFase = getLinkedDocument(false, PRGTIPIFASE.className(...), ...)
  if (tipoFase)
    FullCode = CODFASE + " - " + tipoFase.DESCRTIPOFASE
  else if (IDTIPOFASE == null)
    FullCode = this.getFixedNameOfProgettoFase()
  else 
    QappCore.DTTLogMessage("This should not happen", ..., DTTError)
}


// ──────────────────────────────────

// ************************************************************************************
// this methods takes a well formed recordset that typically is like
// 1 - Avvio progetto
// 1.1 - Qualcosa
// 2 - chiusura progetto
// Progetto
// 
// and returns a similar recordset where "Progetto" at the beginning, so the result is:
// Progetto
// 1 - Avvio progetto
// 1.1 - Qualcosa
// 2 - chiusura progetto
// ************************************************************************************
private Recordset PRGFASI.moveProgettoFaseAtFirst(
  Recordset wellFormedRecordset // 
)
{
  Recordset computedRecordset = new()
   
  RecordsetMetaData rmd = (RecordsetMetaData)wellFormedRecordset.getMetaData()
   
  // duplicate the metadata since it is not possible to assign a metadata to more recordsets
  RecordsetMetaData rmdDuplicated = new()
  rmdDuplicated.setColumnCount(2)
  rmdDuplicated.setFieldType(1, rmd.getFieldType(1))
  rmdDuplicated.setFieldType(2, rmd.getFieldType(2))
  rmdDuplicated.setFieldName(1, rmd.getFieldName(1))
  rmdDuplicated.setFieldName(2, rmd.getFieldName(2))
   
  computedRecordset.setMetaData(rmdDuplicated)
   
   
  // find the "progetto" row and add it to the new recordset (so it is the first)...
  wellFormedRecordset.moveFirst()
   
  while (!(wellFormedRecordset.EOF()))
  {
    int id = toInteger(wellFormedRecordset.getFieldValueIdx(1))
    string description = wellFormedRecordset.getFieldValueIdx(2)
    if (description == this.getFixedNameOfProgettoFase())
    {
      Collection c = new()
      c.addInteger(id)
      c.addString(description)
      computedRecordset.addRow(c)
      break 
    }
    wellFormedRecordset.moveNext()
  }
   
  // ...then add all the other rows
  wellFormedRecordset.moveFirst()
   
  while (!(wellFormedRecordset.EOF()))
  {
    int id = toInteger(wellFormedRecordset.getFieldValueIdx(1))
    string description = wellFormedRecordset.getFieldValueIdx(2)
    if (description != this.getFixedNameOfProgettoFase())
    {
      Collection c = new()
      c.addInteger(id)
      c.addString(description)
      computedRecordset.addRow(c)
    }
    wellFormedRecordset.moveNext()
  }
   
  return computedRecordset
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void PRGFASI.computeStandardImportMetadata()
{
  base.addImportMetadato(id_fase_padre, toPropertyIndex(IDFASEPADRE), false, null, ...)
  base.addImportMetadato(id_tipo_fase, toPropertyIndex(IDTIPOFASE), false, null, PRGTIPIFASE.className(false), ...)
  base.addImportMetadato(id_progetto, toPropertyIndex(IDPROGETTO), true, null, Progetto.className(false), ...)
  base.addImportMetadato(responsabile, toPropertyIndex(IDRESPONSABILE), false, null, Personale.className(false), ...)
  base.addImportMetadato(sequenza, toPropertyIndex(SEQUENZA), true, null, ...)
  base.addImportMetadato(visualizza_gantt, toPropertyIndex(VISUALIZZAGANTT), true, null, ...)
  base.addImportMetadato(sospesa, toPropertyIndex(SOSPESA), true, null, ...)
  base.addImportMetadato(riesame, toPropertyIndex(RIESAME), true, null, ...)
  base.addImportMetadato(data_inizio_prevista, toPropertyIndex(DATAINIZIOPREV), false, null, ...)
  base.addImportMetadato(data_fine_prevista, toPropertyIndex(DATAFINEPREV), false, null, ...)
  base.addImportMetadato(data_inizio, toPropertyIndex(DATAINIZIO), false, null, ...)
  base.addImportMetadato(data_fine, toPropertyIndex(DATAFINE), false, null, ...)
  base.addImportMetadato(perc_avanzamento, toPropertyIndex(PERCAVANZAMENTO), true, null, ...)
  base.addImportMetadato(descrizione, toPropertyIndex(DESCRFASE), false, null, ...)
  base.addImportMetadato(ore_lavoro_previste, toPropertyIndex(ORELAVOROPREV), true, null, ...)
  base.addImportMetadato(costo_orario, toPropertyIndex(COSTOORARIO), true, null, ...)
  base.addImportMetadato(altri_costi, toPropertyIndex(ALTRICOSTI), true, null, ...)
  base.addImportMetadato(codice, toPropertyIndex(CODFASE), true, null, ...)
  base.addImportMetadato(livello, toPropertyIndex(LIVELLO), true, null, ...)
  base.addImportMetadato(notifica, toPropertyIndex(NOTIFICA), true, null, ...)
  base.addImportMetadato(avanzamento_manuale, toPropertyIndex(AVANZAMENTOMANUALE), false, null, ...)
  base.addImportMetadato(ricavi_previsti, toPropertyIndex(RICAVIPREVISTI), true, null, ...)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static PRGFASI PRGFASI.create(
  int idProgetto // 
)
{
  PRGFASI prgfasi = new()
  prgfasi.init()
  prgfasi.IDPROGETTO = idProgetto
   
  return prgfasi
   
}


// ──────────────────────────────────

// **************************************************************************************************************************************************
// description of interfaccia is computed and returned
// since interfaccia can be of many types (cliente, employee, funzione), the "toString()" is different for each case and this is what the method does
// **************************************************************************************************************************************************
private string PRGINTERFACCE.getFullDescription()
{
  string computedDescription = ""
   
  if (!(loaded))
  {
    this.loadFromDB(...)
  }
   
  PRGTIPIINTERF tipoInterfaccia = getLinkedDocument(false, PRGTIPIINTERF.className(...), ...)
   
   
  if (tipoInterfaccia.CLIENTE == Yes or tipoInterfaccia.FORNITORE == Yes)
  {
    Clifor c = Clifor.getFromDB(IDCONTO, ...)
     
    if (IDCONTATTO != null)
    {
      Contatto contatto = Contatto.get(IDCONTATTO)
      contatto.ComputeCdataImportMetadata()
      computedDescription = contatto.getFullDescription()
    }
    else 
    {
      computedDescription = c.RAGIONESOCIALE
    }
  }
  else if (tipoInterfaccia.DIPENDENTE == Yes)
  {
    Personale p = Personale.getFromDB(IDDIPENDENTE, ...)
    computedDescription = p.FullName
  }
  else if (tipoInterfaccia.FUNZIONE == Yes)
  {
    Funzione f = Funzione.getFromDB(IDFUNZIONE, ...)
    computedDescription = f.getFullDescription()
  }
  return computedDescription
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void PRGINTERFACCE.computeAdditionalProperties()
{
  FullDescription = this.getFullDescription()
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static PRGINTERFACCE PRGINTERFACCE.get(
  int idPrgInterfaccia // 
)
{
  PRGINTERFACCE interf = new()
  interf.IDINTERF = idPrgInterfaccia
  interf.loadFromDB(...)
  return interf
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static boolean PRGINTERFACCE.userIsInterfaceInProject(
  Progetto project // 
  Utente user      // 
)
{
  int vCount = 0
  select into variables (found variable)
    set vCount = count(...)
  from 
    PRGINTERFACCE // master table
    Personale     // manually joined, see where clauses
  where
    PRGINTERFACCE.IDPROGETTO == project.IDPROGETTO
    PRGINTERFACCE.IDDIPENDENTE == Personale.IDDIPENDENTE
    Personale.IDUTENTE == user.IDUTENTE
   
  boolean userIsInterfaceInProject = vCount > 0
  return userIsInterfaceInProject
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event PRGINTERFACCE.OnInit()
{
  int nextID = Sequence.getNextSequence(PRGN_ID_INTERF, ...)
  IDINTERF = nextID
  INTERFACEADMIN = No
  RECEIVEDOCNOTIFICATION = No
}


// ──────────────────────────────────

// ************************************************************
// Event raised by the document after loading from the database
// ************************************************************
event PRGINTERFACCE.AfterLoad(
  boolean AlreadyLoaded     // A boolean parameter that specifies whether the document was previously loaded.
  IDArray LoadedCollections // IDArray type parameter that contains the collections loaded by the framework. If the value of the Already Loaded parameter is False, the Loaded Collections parameter is NULL.
)
{
  this.computeAdditionalProperties()
}


// ──────────────────────────────────

// **************************************************************************
// Event called to customize the Smart Lookup procedure at the document level
// **************************************************************************
event PRGINTERFACCE.OnGetSmartLookup(
  Recordset RecordSet       // The recordset to be filled if the queries run during the Smart Lookup event are customized.
  inout int Level           // The Smart Lookup procedure performs multiple attempts, increasingly widening the search field. This integer parameter specifies the number of attempts made.
  inout boolean NullValue   // A boolean output parameter. If set to True, then NULL (no results found) will be returned to the panel.
  inout boolean Skip        // A boolean output parameter. If set to True, then the standard queries for the current level will not be executed.
  inout boolean Cancel      // A boolean output parameter. If set to True, the Smart Lookup procedure will be stopped.
  IDDocument CallerDocument // The document for which the smart lookup procedure is launched. It can be null.
)
{
  Skip = true
  if (Promemoria.isMyInstance(CallerDocument))
  {
    Promemoria p = (Promemoria)CallerDocument
     
    // create the recordset with the ids of all interfacce of a given progetto...
    Recordset idsRS = new()
    select into recordset (idsRS)
      IDINTERF as ID_INTERF
    from 
      PRGINTERFACCE // master table
    where
      IDPROGETTO == p.IDITEM
     
     
    // ... and use it to retrieve a SmartLookup-ready recordset
    Recordset r = SmartLookupHelper.getWellFormedLookupRecordset(FullDescription, this.className(...), idsRS)
    RecordSet.copyFrom(r)
     
    // to avoid many Level calls we cancel the smartlookup if recodset is empty it is important to set NullValue to inform the framwork that no results were found
    if (r.recordCount() == 0)
    {
      Cancel = true
      NullValue = true
    }
  }
}


// ──────────────────────────────────

// **************************************************************************
// Event raised to the document when a panel requests execution of a decoding
// **************************************************************************
event PRGINTERFACCE.BeforeLookup(
  inout boolean Skip   // A boolean output parameter. If set to True, the framework will not execute the lookup via a query.
  inout boolean Cancel // A boolean output parameter. If set to True, the decoding procedure ends immediately. The AfterLookup event will not be called.
)
{
  FullDescription = this.getFullDescription()
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event TipoProgetto.OnInit()
{
  IDTIPOPROGETTO = Sequence.getNextSequence(PRGN_ID_TABELLE, ...)
  CLIENTE = No
  ARTICOLO = No
  ATTIVO = Yes
  SEQUENZA = 1
  VISREQUISITI = No
  VISINTERFACCE = Yes
  VISFASI = Yes
  VISEVENTI = No
  VISCOSTI = Yes
  VISDOCUMENTI = Yes
  VISATTIVITA = Yes
  SHOWCUSTOMDATA = Yes
  SHOWREFERENCES = Yes
  LINKREVISION = No
   
}


// ──────────────────────────────────

// ***************************************************************************************************************
// create a new Link document for given document and Idrevision and add in to collection of Progetti doc collegati
// ***************************************************************************************************************
public void Progetto.AddDocCollegati(
  Documento Document // 
  int IdRevision     // 
)
{
  PRGDOCUMENTI pd = new()
  pd.init()
  pd.IDPROGETTO = IDPROGETTO
  pd.IDDOCUMENTO = Document.IDDOCUMENTO
  pd.IDREVISIONE = null
  if (IdRevision <> 0)
  {
    pd.IDREVISIONE = IdRevision
  }
  PRGDOCUMENTI.add(pd)
}


// ──────────────────────────────────

// **********************************************************************************************************
// Check if Document link already exists in collegati collection
// this method is require to validate/check when we create Document link to prevent Duplicate error on saving
// **********************************************************************************************************
public boolean Progetto.DocumentLinkAlreadyExists(
  Documento document // 
)
{
  boolean sameDocumentLinkAlreadyExists = false
  this.loadCollectionFromDB(PRGDOCUMENTI, 0)
  for each PRGDOCUMENTI prgdocumenti in PRGDOCUMENTI
  {
    if (prgdocumenti.IDDOCUMENTO == document.IDDOCUMENTO)
    {
      sameDocumentLinkAlreadyExists = true
      break 
    }
  }
  return sameDocumentLinkAlreadyExists
}


// ──────────────────────────────────

// ***********************************************************************************************
// returns a collection of Progetti of all the children the parameters allow to filter the results
// ***********************************************************************************************
public IDCollection Progetto.getChildrenProjects(
  optional boolean closedToo = 0          // 
  optional boolean onlyWithInterfacce = 0 // 
)
{
   
  IDCollection childrenProgetti of Progetto = new()
  select into collection (childrenProgetti)
    set IDPROGETTO = Progetti.IDPROGETTO
    set IDPROGETTOPADRE = Progetti.IDPROGETTOPADRE
    set IDTIPOPROGETTO = Progetti.IDTIPOPROGETTO
    set IDUTENTERESPONSABILE = Progetti.IDUTENTERESPONSABILE
    set IDCONTO = Progetti.IDCONTO
    set IDARTICOLO = Progetti.IDARTICOLO
    set CODPROGETTO = Progetti.CODPROGETTO
    set DESCRPROGETTO = Progetti.DESCRPROGETTO
    set DATAINIZIO = Progetti.DATAINIZIO
    set DATAFINE = Progetti.DATAFINE
    set IDCARTELLADOC = Progetti.IDCARTELLADOC
    set NOTE = Progetti.NOTE
    set PROGETTOTIPO = Progetti.PROGETTOTIPO
    set IDTIPOPRIORITA = Progetti.IDTIPOPRIORITA
    set DATAFINEPREV = Progetti.DATAFINEPREV
    set DATAINIZIOPREV = Progetti.DATAINIZIOPREV
    set CARTELLAESTERNA = Progetti.CARTELLAESTERNA
    set PROFID = Progetti.PROFID
    set NOTETXT = Progetti.NOTETXT
    set NOTEHTML = Progetti.NOTEHTML
  from 
    Progetti        // master table
    PRGTIPIPROGETTO // joined with Progetti using key FK_PRG_PROGETTI02
  where
    Progetti.IDPROGETTOPADRE == IDPROGETTO
    (PRGTIPIPROGETTO.VISINTERFACCE == Yes) and (onlyWithInterfacce == true)
    isNull(Progetti.DATAFINE) and (closedToo == false)
   
   
   
   
  return childrenProgetti
}


// ──────────────────────────────────

// *********************************************************************************************************************************************************************
// loads the ChildrenProgetti collection, if allChildren is true the collection is filled with all children progetti, otherwise only the first level progetti are loaded
// 
// the progetti are loaded without collections
// *********************************************************************************************************************************************************************
public void Progetto.populateChildrenProjects(
  boolean allChildren // 
)
{
  if (allChildren)
    this.LoadChildrenProjects(this, 0, ChildrenProgetti)
  else 
    this.LoadChildrenProjects(this, 0, null)
}


// ──────────────────────────────────

// **************************************************************************************************************************
// methods that makes all the children projects of the project whose ID is passed in IDPADRE have ChildrenProjects collection
// 
// this is useful if a full tree of projects needs to be displayed
// 
// if a collection is passed as last parameter that collection is populated recursively and it will contain
// **************************************************************************************************************************
private static void Progetto.LoadChildrenProjects(
  Progetto progetto                          // 
  int levels                                 // 
  optional IDCollection projColl of Progetto // 
)
{
  Progetto PrjExample = new()
  PrjExample.IDPROGETTOPADRE = progetto.IDPROGETTO
  if (!(progetto.ChildrenProgetti.loaded))
  {
    PrjExample.loadCollectionByExample(progetto.ChildrenProgetti, true, 0, ...)
  }
   
  if (!(projColl))
  {
    return 
  }
   
  for each Progetto p in progetto.ChildrenProgetti
  {
    projColl.add(p)
  }
   
  boolean beRecursive = false
  if (projColl)
  {
    beRecursive = projColl.count() > 0
  }
   
   
  if (progetto.ChildrenProgetti.count() > 0 and beRecursive)
  {
    for each Progetto p in progetto.ChildrenProgetti
    {
      Progetto.LoadChildrenProjects(p, levels, projColl)
    }
  }
}


// ──────────────────────────────────

// *****************************************************************
// given a progettoTab true is returned if that progetto has the tab
// *****************************************************************
public boolean Progetto.hasTab(
  string:progettiTabs progettiTab // 
)
{
  boolean result = false
   
  TipoProgetto tipoProgetto = getLinkedDocument(false, TipoProgetto.className(...), ...)
   
   
  if (tipoProgetto)
  {
    switch (progettiTab)
    {
      case Interfacce:
         result = (tipoProgetto.VISINTERFACCE == Yes)
      break
      case Attività:
         result = (tipoProgetto.VISATTIVITA == Yes)
      break
      case Costi:
         result = (tipoProgetto.VISCOSTI == Yes)
      break
      case Documenti:
         result = (tipoProgetto.VISDOCUMENTI == Yes)
      break
      case Promemoria:
         result = (tipoProgetto.VISEVENTI == Yes)
      break
      case Fasi:
         result = (tipoProgetto.VISFASI == Yes)
      break
      case Requisiti:
         result = (tipoProgetto.VISREQUISITI == Yes)
      break
    }
  }
   
  return result
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Progetto.computeStandardImportMetadata()
{
  base.addImportMetadato(tipo, toPropertyIndex(IDTIPOPROGETTO), true, null, TipoProgetto.className(false), ...)
  base.addImportMetadato(padre, toPropertyIndex(IDPROGETTOPADRE), false, null, this.className(false), ...)
  base.addImportMetadato(codice, toPropertyIndex(CODPROGETTO), true, null, ...)
  base.addImportMetadato(descrizione, toPropertyIndex(DESCRPROGETTO), true, ...)
  base.addImportMetadato(conto, toPropertyIndex(IDCONTO), false, null, Clifor.className(false), ...)
  base.addImportMetadato(articolo, toPropertyIndex(IDARTICOLO), false, null, Articolo.className(false), ...)
  base.addImportMetadato(responsabile, toPropertyIndex(IDUTENTERESPONSABILE), true, null, Utente.className(...), ...)
  base.addImportMetadato(data_inizio, toPropertyIndex(DATAINIZIO), true, ...)
  base.addImportMetadato(data_fine, toPropertyIndex(DATAFINE), false, ...)
  base.addImportMetadato(data_inizio_prevista, toPropertyIndex(DATAINIZIOPREV), false, ...)
  base.addImportMetadato(data_fine_prevista, toPropertyIndex(DATAFINEPREV), false, ...)
  base.addImportMetadato(note, toPropertyIndex(NOTE), false, ...)
  base.addImportMetadato(notetxt, toPropertyIndex(NOTETXT), false, ...)
  base.addImportMetadato(priorita, toPropertyIndex(IDTIPOPRIORITA), false, null, TipoPriorita.className(...), ...)
  base.addImportMetadato(prof_id, toPropertyIndex(PROFID), true, null, ...)
  base.addImportMetadato(progetto_tipo, toPropertyIndex(PROGETTOTIPO), true, null, ...)
}


// ──────────────────────────────────

// *******************************************************************
// Set default values while duplicate the project from modello project
// *******************************************************************
public void Progetto.InitializeDefaultvalues()
{
  if (!(ModelloProgetto))
  {
    QappCore.DTTLogMessage("nitialize Default values - modelloProgetto not defined!", ..., DTTError)
    return 
  }
   
   
  if (IDPROGETTO == null)
  {
    this.init()
     
    // we delete the default prgfasi created on init because duplica will create from modello
    PRGFASI.moveFirst()
    PRGFASI.deleteAt()
    PRGFASI.removeDeleted()
  }
  IDPROGETTOPADRE = ModelloProgetto.IDPROGETTOPADRE
  IDTIPOPROGETTO = ModelloProgetto.IDTIPOPROGETTO
  IDUTENTERESPONSABILE = ModelloProgetto.IDUTENTERESPONSABILE
  IDCONTO = ModelloProgetto.IDCONTO
  IDARTICOLO = ModelloProgetto.IDARTICOLO
  CODPROGETTO = "(" + ModelloProgetto.CODPROGETTO + ")"
  DESCRPROGETTO = "(Copia di " + ModelloProgetto.DESCRPROGETTO + ")"
   
  // Data Inizioni must be updated from outside after creating Progetto object
  // we initialize it with today since it is not null field
  DATAINIZIO = today()
  IDCARTELLADOC = ModelloProgetto.IDCARTELLADOC
  NOTE = ModelloProgetto.NOTE
   
  // when duplicated we assign Proegtto Tipo = "N"
  PROGETTOTIPO = "N"
  IDTIPOPRIORITA = ModelloProgetto.IDTIPOPRIORITA
  CARTELLAESTERNA = ModelloProgetto.CARTELLAESTERNA
  PROFID = ModelloProgetto.PROFID
  NOTETXT = ModelloProgetto.NOTETXT
   
}


// ──────────────────────────────────

// **********************************************************************
// Populate references collection for progetti referring modello progetto
// **********************************************************************
private void Progetto.CreateRiferimentiFromModello()
{
   
  // get reference collection of modello and loop it create reference
  //  
  for each Riferimento r in ModelloProgetto.Riferimenti
  {
    Riferimento ref = new()
    ref.init()
    ref.IDRiferimento = Sequence.getNextSequence(EVAN_ID_EVA_REFERENCES, ...)
    ref.IDTipoRiferimento = r.IDTipoRiferimento
    ref.IDEvento = IDPROGETTO
    ref.IDForAll = r.IDForAll
    ref.ISMODELLO = No
    Riferimenti.add(ref)
     
  }
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void Progetto.createDocCollegatiFromModello()
{
  for each PRGDOCUMENTI ModelloPrgdocumenti in ModelloProgetto.PRGDOCUMENTI
  {
    PRGDOCUMENTI prgdoc = new()
    prgdoc.init()
    prgdoc.IDPROGETTO = IDPROGETTO
    prgdoc.IDDOCUMENTO = ModelloPrgdocumenti.IDDOCUMENTO
    prgdoc.IDREVISIONE = ModelloPrgdocumenti.IDREVISIONE
    prgdoc.NOTE = ModelloPrgdocumenti.NOTE
    PRGDOCUMENTI.add(prgdoc)
  }
}


// ──────────────────────────────────

// ***************************************************************************************
// populate Custom data collections of all types by reading modello progetto's customdata 
// ***************************************************************************************
private void Progetto.CreateCustomData()
{
  // loop modello's boolean CD and create CD for evento
  for each MainModuleCDBooleanValue cdbv in ModelloProgetto.CDATABOOLEANVALUES
  {
    MainModuleCDBooleanValue CDataBoolean = new()
    CDataBoolean.init()
    CDataBoolean.IDCDATAFLD = cdbv.IDCDATAFLD
    CDataBoolean.IDMODULERECID = IDPROGETTO
    CDataBoolean.ISMODELLO = No
    CDataBoolean.VALUE = cdbv.VALUE
    CDATABOOLEANVALUES.add(CDataBoolean)
     
  }
  //  
  // loop modello's combo  CD and create CD for evento
  for each MainModuleCDComboValue cdcv in ModelloProgetto.CDATACOMBOVALUES
  {
    MainModuleCDComboValue CDataCombo = new()
    CDataCombo.init()
    CDataCombo.IDCDATAFLD = cdcv.IDCDATAFLD
    CDataCombo.IDMODULERECID = IDPROGETTO
    CDataCombo.VALUE = cdcv.VALUE
    CDataCombo.ISMODELLO = No
    CDATACOMBOVALUES.add(CDataCombo)
  }
  //  
  // loop modello's Memo CD and create CD for evento
  for each MainModuleCDMemoValue cdmv in ModelloProgetto.CDATAMEMOVALUES
  {
    MainModuleCDMemoValue CDataMemo = new()
    CDataMemo.init()
    CDataMemo.IDCDATAFLD = cdmv.IDCDATAFLD
    CDataMemo.IDMODULERECID = IDPROGETTO
    CDataMemo.VALUE = cdmv.VALUE
    CDataMemo.ISMODELLO = No
    CDATAMEMOVALUES.add(CDataMemo)
  }
  //  
  // loop modello's Text CD and create CD for evento
  for each MainModuleCDTextValue cdtv in ModelloProgetto.CDATATEXTVALUES
  {
    MainModuleCDTextValue CDataText = new()
    CDataText.init()
    CDataText.IDCDATAFLD = cdtv.IDCDATAFLD
    CDataText.IDMODULERECID = IDPROGETTO
    CDataText.VALUE = cdtv.VALUE
    CDataText.ISMODELLO = No
    CDATATEXTVALUES.add(CDataText)
  }
  //  
  // loop modello's Float CD and create CD for evento
  for each MainModuleCDFloatValue cdfv in ModelloProgetto.CDATAFLOATVALUES
  {
    MainModuleCDFloatValue CDataFloat = new()
    CDataFloat.init()
    CDataFloat.IDCDATAFLD = cdfv.IDCDATAFLD
    CDataFloat.IDMODULERECID = IDPROGETTO
    CDataFloat.VALUE = cdfv.VALUE
    CDataFloat.ISMODELLO = No
    CDATAFLOATVALUES.add(CDataFloat)
  }
  //  
  // loop modello's Date CD and create CD for evento
  for each MainModuleCDDateValue cddv in ModelloProgetto.CDATADATEVALUES
  {
    MainModuleCDDateValue CDataDate = new()
    CDataDate.init()
    CDataDate.IDCDATAFLD = cddv.IDCDATAFLD
    CDataDate.IDMODULERECID = IDPROGETTO
    CDataDate.VALUE = cddv.VALUE
    CDataDate.ISMODELLO = No
    CDATADATEVALUES.add(CDataDate)
  }
  //  
  // loop modello's Module CD and create CD for evento
  for each MainModuleCDModuleValue cdmv in ModelloProgetto.CDATAMODULEVALUES
  {
    MainModuleCDModuleValue CDataModule = new()
    CDataModule.init()
    CDataModule.IDCDATAFLD = cdmv.IDCDATAFLD
    CDataModule.IDMODULERECID = IDPROGETTO
    CDataModule.VALUE = cdmv.VALUE
    CDataModule.ISMODELLO = No
    CDATAMODULEVALUES.add(CDataModule)
  }
  //  
  // loop modello's Integer CD and create CD for evento
  for each MainModuleCDIntegerValue cdiv in ModelloProgetto.CDATAINTEGERVALUES
  {
    MainModuleCDIntegerValue CDataInteger = new()
    CDataInteger.init()
    CDataInteger.IDCDATAFLD = cdiv.IDCDATAFLD
    CDataInteger.IDMODULERECID = IDPROGETTO
    CDataInteger.VALUE = cdiv.VALUE
    CDataInteger.ISMODELLO = No
    CDATAINTEGERVALUES.add(CDataInteger)
  }
   
}


// ──────────────────────────────────

// **************************************************************************************************************************************************************************************
// Populate all collection of sub tabs of duplicated progetto based on modello progetto i.e. requesti, fase, Riesami, interface, Fasi Interface, reference, Doc Collegati and custom data
// **************************************************************************************************************************************************************************************
public void Progetto.CreateChildCollectionOfProgetto()
{
   
  if (!(ModelloProgetto))
  {
    QappCore.DTTLogMessage("Create Child Collection Of Progetto - modelloProgetto not defined!", ..., DTTError)
    return 
  }
   
  // create/popluate collection of Requisiti data
  this.CreateRequisiti()
   
  // create/popluate collection of Interfacce data
  this.CreateInterfaccie()
   
  // create/popluate collection of Fasi, Riesami and Fasi Interface data
  this.CreateFasi()
   
  // Assign correct Padre Fase to each Fase based on modello Proegtto's Fase
  this.AssignPadreToFasi()
   
  // Assign Correct FK (Id Fase and ID Interf in new created records of PRG_INTER_FASI
  this.AssignIdIntefFKInPrgInterfFasi()
   
  // Doc collegati population
  this.createDocCollegatiFromModello()
   
  // Populate reference collection
  this.CreateRiferimentiFromModello()
   
  // populate CD collection
  this.CreateCustomData()
}


// ──────────────────────────────────

// ************************************************************************************
// Create object of requisiti based on modello progetto's requisiti passed as parameter
// ************************************************************************************
private PRGREQUISITI Progetto.CreateRequisitifromModello(
  PRGREQUISITI ModelloPrgRequisiti // 
)
{
  PRGREQUISITI prgrequisiti = new()
  prgrequisiti.init()
  prgrequisiti.IDREQUISITO = Sequence.getNextSequence(PRGN_ID_REQUISITO, ...)
  prgrequisiti.IDPROGETTO = IDPROGETTO
  prgrequisiti.IDTIPOREQUISITO = ModelloPrgRequisiti.IDTIPOREQUISITO
  prgrequisiti.SEQUENZA = ModelloPrgRequisiti.SEQUENZA
  prgrequisiti.DESCRREQUISITO = ModelloPrgRequisiti.DESCRREQUISITO
  return prgrequisiti
}


// ──────────────────────────────────

// ******************************************************************************
// Populate Requisiti collection based on modello progetto's requisiti collection
// ******************************************************************************
private void Progetto.CreateRequisiti()
{
  for each PRGREQUISITI prgrequisiti in ModelloProgetto.PRGREQUISITI
  {
    PRGREQUISITI prgreq = new()
    prgreq = this.CreateRequisitifromModello(prgrequisiti)
    PRGREQUISITI.add(prgreq)
  }
}


// ──────────────────────────────────

// ********************************************************************************
// Populate Interfacce collection based on modello progetto's interfacce collection
// ********************************************************************************
private void Progetto.CreateInterfaccie()
{
  for each PRGINTERFACCE prginterfacce in ModelloProgetto.PRGINTERFACCE
  {
    PRGINTERFACCE pi = new()
    pi = this.CreateInterfacciaFromModello(prginterfacce)
    PRGINTERFACCE.add(pi)
  }
}


// ──────────────────────────────────

// ****************************************************************************************
// Create object of interfaccia based on modello progetto's interfaccia passed as parameter
// ****************************************************************************************
private PRGINTERFACCE Progetto.CreateInterfacciaFromModello(
  PRGINTERFACCE ModelloPrgInterfacce // 
)
{
  PRGINTERFACCE prginterfacce = new()
  prginterfacce.init()
  prginterfacce.IDINTERF = Sequence.getNextSequence(PRGN_ID_INTERF, ...)
  prginterfacce.IDPROGETTO = IDPROGETTO
  prginterfacce.IDTIPOINTERF = ModelloPrgInterfacce.IDTIPOINTERF
  prginterfacce.IDFUNZIONE = ModelloPrgInterfacce.IDFUNZIONE
  prginterfacce.IDDIPENDENTE = ModelloPrgInterfacce.IDDIPENDENTE
  prginterfacce.IDCONTO = ModelloPrgInterfacce.IDCONTO
  prginterfacce.IDCONTATTO = ModelloPrgInterfacce.IDCONTATTO
  prginterfacce.COSTOPREVISTO = ModelloPrgInterfacce.COSTOPREVISTO
  prginterfacce.RECEIVEDOCNOTIFICATION = ModelloPrgInterfacce.RECEIVEDOCNOTIFICATION
  prginterfacce.DESCRINTERF = ModelloPrgInterfacce.DESCRINTERF
   
  // we store Modello ID Interf , later this will utilized to popluate  PRG INTERF FASI colelction
  prginterfacce.ModelloIDINTERF = ModelloPrgInterfacce.IDINTERF
   
  return prginterfacce
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private int Progetto.GetIdInterfUsingModelloIDInterf(
  int ModelloIDInterf // 
)
{
  int IdInterf = -1
  for each PRGINTERFACCE prginterfacce in PRGINTERFACCE
  {
    if (prginterfacce.ModelloIDINTERF == ModelloIDInterf)
    {
      IdInterf = prginterfacce.IDINTERF
      break 
    }
  }
  return IdInterf
}


// ──────────────────────────────────

// ********************************************************************
// Populate Fasi collection based on modello progetto's Fasi collection
// ********************************************************************
private void Progetto.CreateFasi()
{
  for each PRGFASI prgfasi in ModelloProgetto.PRGFASI
  {
    PRGFASI pf = new()
    pf = this.CreateFaseFromModello(prgfasi)
    PRGFASI.add(pf)
     
  }
}


// ──────────────────────────────────

// **************************************************************************
// Create object of Fase based on modello progetto's Fase passed as parameter
// **************************************************************************
private PRGFASI Progetto.CreateFaseFromModello(
  PRGFASI ModelloPrgFase // 
)
{
  PRGFASI prgfase = new()
  prgfase.init()
   
  prgfase.IDPROGETTO = IDPROGETTO
   
  // once fasi collection is populated we can update ID_FASE_PADRE
  // with help of ModelloIDFIELD addition property
  prgfase.ModelloIDFASE = ModelloPrgFase.IDFASE
   
  prgfase.IDTIPOFASE = ModelloPrgFase.IDTIPOFASE
  prgfase.SEQUENZA = ModelloPrgFase.SEQUENZA
  prgfase.VISUALIZZAGANTT = ModelloPrgFase.VISUALIZZAGANTT
  prgfase.SOSPESA = No
  prgfase.RIESAME = if(ModelloPrgFase.RIESAME == "C", "A", ModelloPrgFase.RIESAME)
   
  prgfase.IDRESPONSABILE = if(this.ResponsibleToBeDuplicated(), ModelloPrgFase.IDRESPONSABILE, null)
   
//  // do not assign dates
//  prgfase.DATAINIZIOPREV = ModelloPrgFase.DATAINIZIOPREV
//  prgfase.DATAFINEPREV = ModelloPrgFase.DATAFINEPREV
//  prgfase.DATAINIZIO = ModelloPrgFase.DATAINIZIO
//  prgfase.DATAFINE = ModelloPrgFase.DATAFINE
  prgfase.PERCAVANZAMENTO = 0
  prgfase.DESCRFASE = ModelloPrgFase.DESCRFASE
  prgfase.ORELAVOROPREV = ModelloPrgFase.ORELAVOROPREV
  prgfase.COSTOORARIO = ModelloPrgFase.COSTOORARIO
  prgfase.ALTRICOSTI = ModelloPrgFase.ALTRICOSTI
  prgfase.CODFASE = ModelloPrgFase.CODFASE
  prgfase.LIVELLO = ModelloPrgFase.LIVELLO
  prgfase.NOTIFICA = ModelloPrgFase.NOTIFICA
  prgfase.AVANZAMENTOMANUALE = ModelloPrgFase.AVANZAMENTOMANUALE
  prgfase.RICAVIPREVISTI = ModelloPrgFase.RICAVIPREVISTI
   
  this.CreateRiesami(ModelloPrgFase, prgfase)
  this.CreateInterfaccieFasi(ModelloPrgFase, prgfase)
   
  return prgfase
}


// ──────────────────────────────────

// *********************************************************
// Assign Padre Fase to Fase same as Modello Progetto's fase
// *********************************************************
private void Progetto.AssignPadreToFasi()
{
  for each PRGFASI Modelloprgfasi in ModelloProgetto.PRGFASI
  {
    if (!(isNull(Modelloprgfasi.IDFASEPADRE)))
    {
      // get equivalent ID of ID_FASE_PADRE from FASI collection
      int IdFasePadre = this.GetIdFaseUsingModelloIDFase(Modelloprgfasi.IDFASEPADRE)
      this.SetFasePadre(Modelloprgfasi.IDFASE, IdFasePadre)
       
    }
  }
   
}


// ──────────────────────────────────



// ──────────────────────────────────

// *********************************************************
// Get IdFase of from Fasi collection based on ModelloIdFase
// *********************************************************
private int Progetto.GetIdFaseUsingModelloIDFase(
  int ModelloIDFase // 
)
{
  int IdFase = -1
  for each PRGFASI prgfasi in PRGFASI
  {
    if (prgfasi.ModelloIDFASE == ModelloIDFase)
    {
      IdFase = prgfasi.IDFASE
      break 
    }
  }
  return IdFase
}


// ──────────────────────────────────

// *******************************************************
// Find out whether responsible to be to duplicated or not
// *******************************************************
private boolean Progetto.ResponsibleToBeDuplicated()
{
  boolean ToBeDuplicated = false
  string vDuplicateResponsible = ""
  select into variables (found variable)
    set vDuplicateResponsible = DUPLICARESPONSABILEFASE
  from 
    PRGPARAMETRI // master table
   
  ToBeDuplicated = if(vDuplicateResponsible == Yes, true, false)
  return ToBeDuplicated
}


// ──────────────────────────────────

// ****************************************************************************************
// Populate InterfacceFasi collection based on modello progetto's interfacceFasi collection
// ****************************************************************************************
private void Progetto.CreateInterfaccieFasi(
  PRGFASI ModelloPrgFasi // 
  PRGFASI PrgFasi        // 
)
{
  for each PRGINTERFFASI ModelloPrginterffasi in ModelloPrgFasi.PRGINTERFFASI
  {
    PRGINTERFFASI prgInterfaccieFase = new()
    prgInterfaccieFase = this.CreateInterfaccieFaseFromModello(ModelloPrginterffasi, PrgFasi)
    PrgFasi.PRGINTERFFASI.add(prgInterfaccieFase)
  }
}


// ──────────────────────────────────

// *************************************************************************************
// Create object of InterfFase based on modello progetto's InterFase passed as parameter
// *************************************************************************************
private PRGINTERFFASI Progetto.CreateInterfaccieFaseFromModello(
  PRGINTERFFASI ModelloPrgInterfFasi // 
  PRGFASI prgFasi                    // 
)
{
  PRGINTERFFASI prginterffasi = new()
  prginterffasi.init()
  prginterffasi.IDPROGETTO = IDPROGETTO
  prginterffasi.IDFASE = prgFasi.IDFASE
  prginterffasi.SEQUENZA = ModelloPrgInterfFasi.SEQUENZA
  prginterffasi.NOTE = ModelloPrgInterfFasi.NOTE
  prginterffasi.OREPREVISTE = ModelloPrgInterfFasi.OREPREVISTE
   
   
   
  // ID INTERF will be assinged later based on modello ID Interf
  prginterffasi.ModelloIDINTERF = ModelloPrgInterfFasi.IDINTERF
  return prginterffasi
}


// ──────────────────────────────────

// ***********************************************************************************************************
// Assign IdInterf foreign key in Prg Interf Fasi table based on modello progetto Fasi's interfFasi collection
// ***********************************************************************************************************
private void Progetto.AssignIdIntefFKInPrgInterfFasi()
{
   
  for each PRGFASI ModelloPrgfasi in ModelloProgetto.PRGFASI
  {
    for each PRGINTERFFASI ModelloPrginterffasi in ModelloPrgfasi.PRGINTERFFASI
    {
      int NewIdInterf = this.GetIdInterfUsingModelloIDInterf(ModelloPrginterffasi.IDINTERF)
      this.SetIdInterfinFKPrgInterfFasi(ModelloPrginterffasi.IDFASE, ModelloPrginterffasi.IDINTERF, NewIdInterf)
    }
  }
}


// ──────────────────────────────────

// *****************************************************************************
// set IdInterf in Newely created InterfFasi collection corresponding to modello
// *****************************************************************************
private void Progetto.SetIdInterfinFKPrgInterfFasi(
  int ModelloIdFase   // 
  int ModelloIdInterf // 
  int NewIdInterf     // 
)
{
  boolean Found = false
  for each PRGFASI prgfasi in PRGFASI
  {
    if (prgfasi.ModelloIDFASE == ModelloIdFase)
    {
      for each PRGINTERFFASI prginterffasi in prgfasi.PRGINTERFFASI
      {
         if (prginterffasi.ModelloIDINTERF == ModelloIdInterf)
         {
           prginterffasi.IDINTERF = NewIdInterf
           Found = true
           break 
         }
      }
      if (Found)
      {
         break 
      }
    }
  }
}


// ──────────────────────────────────



// ──────────────────────────────────

// *************************************************************************************
// Create object of Riesami based on modello progetto Fase's Riesami passed as parameter
// *************************************************************************************
public PRGRIESAMI Progetto.CreateRiesamiFromModello(
  PRGRIESAMI ModelloRiesami // 
  PRGFASI PrgFasi           // 
)
{
  PRGRIESAMI prgRiesami = new()
  prgRiesami.init()
  prgRiesami.IDRIGARIESAME = Sequence.getNextSequence(PRGN_ID_RIGA_RIESAME, ...)
  prgRiesami.IDPROGETTO = PrgFasi.IDPROGETTO
  prgRiesami.IDFASE = PrgFasi.IDFASE
  prgRiesami.NRORIGA = ModelloRiesami.NRORIGA
  prgRiesami.RIFERIMENTI = ModelloRiesami.RIFERIMENTI
  prgRiesami.DESCRDOMANDA = ModelloRiesami.DESCRDOMANDA
   
  return prgRiesami
}


// ──────────────────────────────────

// ********************************************
// creates a duplicate project of the passedone
// ********************************************
public static Progetto Progetto.createDuplicated(
  int idModelloProgetto        // 
  boolean linkDocumentRevision // 
)
{
  Progetto p = new()
   
  p.setLinkDocumentRevisionOnDuplication(linkDocumentRevision)
   
  p.ModelloProgetto = new()
  p.ModelloProgetto.IDPROGETTO = idModelloProgetto
  try 
  {
    p.ModelloProgetto.loadFromDB(...)
  }
  catch 
  {
    QappCore.DTTLogMessage("Unable to load template project.", ..., DTTError)
  }
   
  p.InitializeDefaultvalues()
  p.CreateChildCollectionOfProgetto()
   
  return p
}


// ──────────────────────────────────



// ──────────────────────────────────

// *******************************************************************************************************************
// it returns the property index of the property linked to the main image field of the class (e.g. ART_ANAGRAFICA.FOTO
// *******************************************************************************************************************
public int Progetto.PROTECTEDgetMainImagePropertyIndex()
{
  return null
}


// ──────────────────────────────────

// ****************************************************************************************************************************
// it returns the property index of the property linked to the second image field of the class (e.g. ART_ANAGRAFICA.FOTO_SECOND
// ****************************************************************************************************************************
public int Progetto.PROTECTEDgetSecondImagePropertyIndex()
{
  return null
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string Progetto.getCaption(
  string additionalDescription // 
)
{
  return "Progetti " + "{{icon-fa-chevron-right}}"
}


// ──────────────────────────────────

// ***************************************
// Descrivi lo scopo di questa funzione...
// ***************************************
public int Progetto.getMainID()
{
  return IDPROGETTO
}


// ──────────────────────────────────

// ***************************************
// Descrivi lo scopo di questa funzione...
// ***************************************
public int Progetto.getTypeID()
{
  return IDTIPOPROGETTO
}


// ──────────────────────────────────

// ***************************************
// Descrivi lo scopo di questa funzione...
// ***************************************
public int Progetto.getKordApp()
{
  return Progetti
}


// ──────────────────────────────────

// ****************************************
// Describe the purpose of this function...
// ****************************************
public string Progetto.getDescription()
{
  string description = SH.Concat(CODPROGETTO, DESCRPROGETTO, " ")
  return description
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static Progetto Progetto.getFromDB(
  int mainId                                                             // 
  optional string:getFromDBLoadingModes loadingMode = "normalZeroLevels" // 
)
{
  Progetto p = cast(MainModule.retrieve(Progetti, mainId, loadingMode))
  return p
}


// ──────────────────────────────────

// ************************************************************
// Event raised by the document after loading from the database
// ************************************************************
event Progetto.AfterLoad(
  boolean AlreadyLoaded     // A boolean parameter that specifies whether the document was previously loaded.
  IDArray LoadedCollections // IDArray type parameter that contains the collections loaded by the framework. If the value of the Already Loaded parameter is False, the Loaded Collections parameter is NULL.
)
{
  base.AfterLoad(AlreadyLoaded, LoadedCollections)
   
  boolean IsQuickLoadRequested = base.quickLoadRequested()
   
  if (IsQuickLoadRequested)
  {
    return 
  }
   
  // implement here code that would be skipped by QuickLoad
   
   
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event Progetto.OnInit()
{
  IDPROGETTO = Sequence.getNextSequence(PRGN_ID_PROGETTO, ...)
   
  PRGFASI prgfase = PRGFASI.create(IDPROGETTO)
  PRGFASI.add(prgfase)
}


// ──────────────────────────────────

// **************************************************************************************
// Event raised to the document during the initial steps of the document saving procedure
// **************************************************************************************
event throws exception Progetto.BeforeSave(
  inout boolean Skip         // A boolean output parameter. If set to True, tells the framework not to perform operations on the database, because they will be managed independently.
  inout boolean Cancel       // A boolean output parameter. If set to True, tells the framework to cancel the save procedure due to errors that occurred. Since the entire document is saved in the same transa...
  int:beforeSavePhases Phase // An integer from 0 to 3 that specifies the progress status of the save procedure as detailed at the beginning of the article.
)
{
  if (Phase == PreSave)
  {
    if (deleted)
    {
      for each Attività a in Attività
      {
         a.deleted = true
      }
    }
  }
}


// ──────────────────────────────────

********************************************************* opens the document in browser and writes the document log *********************************************************

// ──────────────────────────────────

// ****************************************
// Describe the purpose of this function...
// ****************************************
public string Attività.GetSummary()
{
  return null
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static IDCollection Attività.getCalendarImpegni(
  Utente utente      // 
  date time fromDate // 
  date time toDate   // 
)
{
  IDCollection calendarImpegniCollection of IDDocument = new()
  for each row (readonly)
  {
    select
      IDATTIVITAAttività = Attività.IDATTIVITA
    from 
      Attività  // master table
      Personale // joined with Attività using key FK_PRG_ATTIVITA03
    where
      Personale.IDUTENTE == utente.IDUTENTE
      Attività.DATAATTIVITA >= fromDate
      Attività.DATAATTIVITA <= toDate
     
    Attività a = new()
    a.IDATTIVITA = IDATTIVITAAttività
    a.loadFromDB(0)
    calendarImpegniCollection.add(a)
  }
  return calendarImpegniCollection
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static Attività Attività.get(
  int idAttivita // 
)
{
  Attività a = new()
  a.IDATTIVITA = idAttivita
  try 
  {
    a.loadFromDB(...)
  }
  catch 
  {
    a = null
    QappCore.DTTLogMessage(formatMessage("unable to load attivita for ID: |1", idAttivita, ...), ..., DTTError)
  }
  return a
}


// ──────────────────────────────────

// ***********************************************************************
// Event raised to the document to determine the value of a named property
// ***********************************************************************
event Attività.OnGetNamedPropertyValue(
  string PropertyName        // The name of the named property whose value to retrieve.
  inout string PropertyValue // An output parameter containing the value of the named property.
)
{
   
  if (PropertyName = "STARTDATE")
  {
    PropertyValue = convert(getDate(DATAATTIVITA))
  }
   
  if (PropertyName = "STARTTIME")
  {
    int minutesfrommidnight = 0
    minutesfrommidnight = Tools.FloatTimeToMinutes(ORAINIZIO - int(ORAINIZIO))
    PropertyValue = convert(toTime(0, minutesfrommidnight, 0))
  }
  if (PropertyName == "STATUS")
  {
    if (PROGRAMMATA == Yes)
    {
      PropertyValue = Open
    }
    else 
    {
      PropertyValue = Closed
    }
  }
   
  if (PropertyName == "DURATION")
  {
    PropertyValue = convert(Tools.FloatTimeToMinutes(ORAFINE - ORAINIZIO))
  }
   
  if (PropertyName == "DESCRIPTION")
  {
    string AttivitàDescription = ""
    string ProgettoCode = ""
    string TipoAttivitàDescription = ""
    select into variables (found variable)
      set ProgettoCode = CODPROGETTO
    from 
      Progetti // master table
    where
      IDPROGETTO = IDPROGETTO
    // 
     
    select into variables (found variable)
      set TipoAttivitàDescription = DESCRTIPOATTIVITA
    from 
      PRGTIPIATTIVITA // master table
    where
      IDTIPOATTIVITA = IDTIPOATTIVITA
     
    AttivitàDescription = Tools.Concatenate(ProgettoCode, TipoAttivitàDescription, ...)
    AttivitàDescription = Tools.Concatenate(AttivitàDescription, DESCRATTIVITA, ...)
     
    PropertyValue = AttivitàDescription
     
  }
   
  // 
  if (PropertyName == "COLOR")
  {
    int vProgettiColor = 0
    select into variables (found variable)
      set vProgettiColor = COLOREPROGETTI
    from 
      DISPARAMETRI // master table
     
     
    PropertyValue = convert(Tools.DxcolorToInde(vProgettiColor))
  }
   
  if (PropertyName == "ICON")
  {
    if (getNamedPropertyValue("STATUS") == Closed)
    {
      PropertyValue = convert(AttivitàConfermata)
    }
    else if (getNamedPropertyValue("STATUS") == Open)
    {
      PropertyValue = convert(AttivitàProgrammata)
       
    }
  }
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event Attività.OnInit()
{
  IDATTIVITA = Sequence.getNextSequence(PRGN_ID_ATTIVITA, ...)
  DESCRATTIVITA = "-"
  DATAATTIVITA = today()
  ORE = 1,0
  COSTOORARIO = 0,0
  IDUTENTEINS = QappCore.Loggeduser.IDUTENTE
  ORAINIZIO = 0,375
  ORAFINE = 0,45833
  DATAINS = now()
  PROGRAMMATA = No
  BLOCCOORE = Yes
}


// ──────────────────────────────────



// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public static IDCollection Attività.getUserAttività(
  Utente Utente     // 
  date StartDate    // 
  date time EndDate // 
)
{
  IDCollection UserAttività of Attività = new()
  select into collection (UserAttività)
    set IDATTIVITA = Attività.IDATTIVITA
    set IDPROGETTO = Attività.IDPROGETTO
    set IDTIPOATTIVITA = Attività.IDTIPOATTIVITA
    set DESCRATTIVITA = Attività.DESCRATTIVITA
    set DATAATTIVITA = Attività.DATAATTIVITA
    set ORE = Attività.ORE
    set COSTOORARIO = Attività.COSTOORARIO
    set IDDIPENDENTE = Attività.IDDIPENDENTE
    set IDFASE = Attività.IDFASE
    set IDUTENTEINS = Attività.IDUTENTEINS
    set ORAINIZIO = Attività.ORAINIZIO
    set ORAFINE = Attività.ORAFINE
    set DATAINS = Attività.DATAINS
    set IDUTENTEULTMOD = Attività.IDUTENTEULTMOD
    set DATAULTIMAMOD = Attività.DATAULTIMAMOD
    set EXTCODE = Attività.EXTCODE
    set PROGRAMMATA = Attività.PROGRAMMATA
    set BLOCCOORE = Attività.BLOCCOORE
  from 
    Attività  // master table
    Personale // joined with Attività using key FK_PRG_ATTIVITA03
  where
    Personale.IDUTENTE = Utente.IDUTENTE
    Attività.DATAATTIVITA >= StartDate && Attività.DATAATTIVITA <= EndDate
   
  return UserAttività
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event TipoPriorita.OnInit()
{
  IDTIPOPRIORITA = Sequence.getNextSequence(PRGN_ID_TABELLE, ...)
  SEQUENZA = 1
  COLOREPRIORITA = 1
  CODDESCRPRIORITA = ""
  DESCRPRIORITA = "Nuova priorità"
  ATTIVO = Yes
  REGISTRAZIONI = Yes
}


// ──────────────────────────────────

// *********************************************************************************************************************
// extend this method if in a mobile first Qapp thre is the need to do some operations starting from the loggged in user
// 
// in the QAppRunTImeBehavior executeCustomPostLoginCode must be set to true or this method will not be executed
// 
// IMPORTANT: set DataValid to True if the extra validation added here is passed!!!
// 
// Note:DataValid and ErrorMessage are designed like in the regular login so the same parameters can be passed here
// *********************************************************************************************************************
public void QappCommandHandler.doCustomPostLoginCode(
  inout boolean DataValid   // 
  inout string ErrorMessage // 
)
{
   
}


// ──────────────────────────────────

// ***********************************************************************************************
// saves in db Qappweb info and Qappcommands data. if qappweb was not stored before it retuns true
// 
// ***********************************************************************************************
private void QappCommandHandler.storeQappWebInDatabaseIfNeeded()
{
  boolean dataWasInsertedForTheFirstTime = false
   
  QappWeb qw = QappWeb.getQappWeb(QappCore.QappWeb.Name)
  if (!(qw))
  {
    // store the 3 flags in three variables so they can be restored below after init who would clear them
    string:flagYN enableInQualibusValue = QappCore.QappWeb.SHOWINQUALIBUSMENU
    string:flagYN enableInQmobileValue = QappCore.QappWeb.SHOWINQMOBILEMENU
    string:neededPrivilegeTypes privilegeToSeeMenuItem = QappCore.QappWeb.PRIVILEGETOSEEQAPPINMENU
    string:flagYN enableQAppDataValue = QappCore.QappWeb.ENABLEQAPPDATA
    string qappName = QappCore.QappWeb.Name
     
    QappCore.QappWeb.init()
     
    QappCore.QappWeb.SHOWINQUALIBUSMENU = enableInQualibusValue
    QappCore.QappWeb.SHOWINQMOBILEMENU = enableInQmobileValue
    QappCore.QappWeb.ENABLEQAPPDATA = enableQAppDataValue
    QappCore.QappWeb.PRIVILEGETOSEEQAPPINMENU = privilegeToSeeMenuItem
    QappCore.QappWeb.Name = qappName
     
    // better to call preprare blobs since sometimes before save was not called, anyway it does not harm
    QappCore.QappWeb.prepareBlobs()
     
    QappCore.QappWeb.saveToDB(...)
     
    dataWasInsertedForTheFirstTime = true
     
    // from now on qw has a good value so the code until the end of the method can rely on qw:
    qw = QappCore.QappWeb
     
    for each QappDataStyle qds in QappCore.QappDataStyles
    {
      qds.QappWebID = qw.ID
      qds.saveToDB(...)
    }
     
  }
  else 
  {
    // store anyway the ID in the global object so it can be accessed easily
    QappCore.QappWeb.ID = qw.ID
     
    // add newly defined QAppData Styles in any case:
    QappCore.QappWeb.resetQAppDataStyles(doNotResetExistingStyles)
  }
   
  IDCollection allCommands of QappCommand = getQappCommands()
   
  // First of all insert all the commands defined by the QApp creator...
  for each QappCommand qc in allCommands
  {
    QappCommand existingCommand = new()
    existingCommand.GUID = qc.GUID
     
    // this extra condition is to make sure we search for a command of the current qapp, since the GUID can in some cases be not unique (when the qapp command is for example dopied from an existing poject and
    // GUID is not changed by duplicaing the command to force GUID change)
    existingCommand.QappWebID = qw.ID
     
    try 
    {
      existingCommand.loadFromDB(...)
    }
    catch 
    {
      existingCommand = null
    }
     
    if (!(existingCommand))
    {
      qc.QappWebID = qw.ID
       
      boolean isCommandGuidUnique = qc.isGUIDReallyUnique()
       
      if (isCommandGuidUnique)
      {
         qc.saveToDB(...)
      }
      else 
      {
         QappCore.DTTLogMessage(formatMessage("Command |1 has not a unique GUID, please modify it", qc.CAPTIONITA, ...), ..., DTTError)
      }
    }
     
    qc.insertCruscottoInDatabaseIfNeeded()
  }
   
  // ... and finally delete the commands that exist in DB for the current QApp but that are not defined anymore
  IDCollection commandInDB of QappCommand = new()
   
  QappCommand qcFilter = new()
  qcFilter.QappWebID = qw.ID
  qcFilter.loadCollectionByExample(commandInDB, ...)
   
  boolean commandFoundInDatabase = false
  for each QappCommand existingCommand in commandInDB
  {
    commandFoundInDatabase = false
    for each QappCommand qc in allCommands
    {
      if (qc.GUID == existingCommand.GUID)
      {
         commandFoundInDatabase = true
         break 
      }
    }
    if (!(commandFoundInDatabase))
    {
      QappCore.DTTLogMessage("removing FOreignKey to cruscotto |1 so the command can be deleted", ..., DTTWarning)
       
      // clear any FK...
      Cruscotto.clearForeignKeyAndMailsenderNotifications(existingCommand.IDQAPPCOMMAND)
       
      // ... so the cruscotto can be deleted
      existingCommand.deleted = true
      existingCommand.saveToDB(...)
    }
  }
   
  // storing the information about qapp has just been inserted in a private boolean
  QappInsertedForTheFirstTime = dataWasInsertedForTheFirstTime
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void QappCommandHandler.reloadDefaultQappDataIfNeeded()
{
  QappWeb qw = new()
  qw.Name = QappCore.QappWeb.Name
  try 
  {
    qw.loadFromDB(1)
  }
  catch 
  {
    return 
  }
   
  // for forceUpdate or in case qapp just being inserted we also update webUrl
  if (qw.ForceUpdate == Yes or QappInsertedForTheFirstTime)
  {
    qw.restoreQappWebDefaultData(true)
  }
   
  for each QappCommand qc in qw.QappsCommands
  {
    if (qc.FORCEUPDATE == Yes or QappInsertedForTheFirstTime)
    {
      qc.restoreQappCommandDefaultData()
    }
  }
}


// ──────────────────────────────────

// *****************************************************************************
// qualibus database is populated or refreshed with the info defined in the Qapp
// *****************************************************************************
public void QappCommandHandler.ProcessDatabase(
  string:forceUpdateModes forceUpdateMode // 
)
{
   
  // clear the private property that stores the error messages of processDatabase
  ProcessDatabaseErrorMessage = ""
   
  // store the passed value in the corresponding class property
  ForceUpdateMode = forceUpdateMode
   
  this.validateQAppData()
  this.validateQAppStyles()
  this.validateCommands()
   
   
  this.storeQappWebInDatabaseIfNeeded()
   
  if (QappInsertedForTheFirstTime)
  {
    QappCore.DTTLogMessage("Inserting Qapp data for the first time", ..., DTTInfo)
  }
   
  "support customization of Qualibus database" arrangement
  {
     
    // this code expects QappWeb.Name is set, so it is called after "storeQappWebInDatabaseIfNeeeded"
     
    // make sure QappVersion exists in Db...
    this.insertQappVersionInDbIfNeeded()
     
    boolean atLeastOneScriptNeedsToBeExecuted = this.atLeastOneScriptMustBeExecuted()
     
    // ... so we can use it
    if (forceUpdateMode == doForce or QappInsertedForTheFirstTime or atLeastOneScriptNeedsToBeExecuted)
    {
       
      // note: the initialization scripts are executed in 3 cases (as per the if above that holds 3 conditions):
      // 1) ForceUpdate: so it is possible to run the scripts even without passing the login
      // 2) Qapp is just being inserted
      // 3) in a normal login and all scripts have no been executed yet
      this.executeInitializationScripts()
    }
    else 
    {
      QappCore.DTTLogMessage("force update not used, init.sql will not be searched and executed", ..., DTTInfo)
    }
  }
   
  if (forceUpdateMode == doForce or QappInsertedForTheFirstTime)
  {
    this.reloadDefaultQappDataIfNeeded()
  }
}


// ──────────────────────────────────

// ****************************************************************
// returns true if not all initN.sql scripts have been executed yet
// ****************************************************************
private boolean QappCommandHandler.atLeastOneScriptMustBeExecuted()
{
  int firstScriptToBeExecuted = QappVersion.DBVERS
  int lastScriptToBeExecuted = QappCore.QappWeb.MaxSupportedScriptNumber
   
  boolean atLeastOneScriptNeedsToBeExecuted = firstScriptToBeExecuted != lastScriptToBeExecuted
   
  return atLeastOneScriptNeedsToBeExecuted
}


// ──────────────────────────────────

// **************************************************************
// if needed an APP DB VERS record is inserted in the db
// QappVersion property of QappCommandHandler is also initialized
// **************************************************************
private void QappCommandHandler.insertQappVersionInDbIfNeeded()
{
  if (QappCore.QappWeb.UsesCustomQualibusTables)
  {
    QappVersion qv = null
    if (!(QappVersion.dataExistsForCurrentQapp()))
    {
      qv = QappVersion.createForCurrentQapp()
       
      QappCore.DTTLogMessage(qv.QAPPDESCR, ...)
       
      QappCore.DTTLogMessage(formatMessage("create the app record", qv.QAPPDESCR, ...), ..., DTTInfo)
      qv.saveToDB(...)
       
    }
    else 
    {
      qv = new()
       
      qv.QAPPDESCR = QappCore.QappWeb.Name
      qv.loadFromDB(...)
    }
     
    QappVersion = qv
     
  }
   
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void QappCommandHandler.executeInitializationScripts()
{
   
  // protection in case the method is moved too early in code
  if (!(QappCore.QappWeb))
    QappCore.DTTLogMessage("executeInitializationScripts must be called only if QappCore.QappWeb is not null", ..., DTTError)
   
  int firstScriptToBeExecuted = QappVersion.DBVERS + 1
  int lastScriptToBeExecuted = QappCore.QappWeb.MaxSupportedScriptNumber
   
  if (firstScriptToBeExecuted <= lastScriptToBeExecuted)
  {
    for (int i = firstScriptToBeExecuted; i <= lastScriptToBeExecuted; i = i + 1)
      this.executeInitializationScript(i)
  }
}


// ──────────────────────────────────

// **************************************
// executes the Nth initialization script
// **************************************
private void QappCommandHandler.executeInitializationScript(
  int scriptNumber // 
)
{
  QappCore.DTTLogMessage(formatMessage("executing script n. |1", scriptNumber, ...), ..., DTTInfo)
   
  string currentScriptFileName = formatMessage("init|1.sql", scriptNumber, ...)
   
  string errorDetails = ""
  boolean successfullyExecuted = this.executeScriptFromFile(currentScriptFileName, errorDetails)
  this.appendProcessDatabaseErrorMessage(errorDetails)
   
  if (successfullyExecuted)
  {
    QappVersion.UPGDATE = now()
    QappVersion.DBVERS = scriptNumber
    QappVersion.saveToDB(...)
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void QappCommandHandler.validateCommands()
{
  IDCollection allCommands of QappCommand = getQappCommands()
  //  
  // First of all insert all the commands defined by the QApp creator...
  for each QappCommand qc in allCommands
  {
    string qappCommandErrorMessage = qc.generateReadableExceptions()
    this.appendProcessDatabaseErrorMessage(qappCommandErrorMessage)
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void QappCommandHandler.validateQAppData()
{
   
  boolean QappDataIsWrong = QappCore.QappWeb.checkIfSetDataIsValid()
   
  if (QappDataIsWrong)
  {
    // if QApp data is wrong we expect a tag with the errorMessage (this is est in checkIfSetDataIsValid)
    string errorMessage = QappCore.QappWeb.getTag("errorMessage")
    string exceptionMessage = formatMessage("Qapp |1 - error: |2", QappCore.QappWeb.Name, errorMessage, ...)
    QappCore.DTTLogMessage(exceptionMessage, ..., DTTError)
     
    this.appendProcessDatabaseErrorMessage(errorMessage)
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void QappCommandHandler.validateQAppStyles()
{
  // First of all insert all the commands defined by the QApp creator...
  for each QappDataStyle qds in QappCore.QappDataStyles
  {
    string qappDataStyleErrorMessage = qds.generateReadableExceptions()
    this.appendProcessDatabaseErrorMessage(qappDataStyleErrorMessage)
     
  }
}


// ──────────────────────────────────

// ****************************************************************************************************************************************************************************************
// Single entry point for all the incoming commands from Qualibus
// this method must not be extended
// mode parameter allows to choose between the Normal Execution (that will eventually call command.execute())  and the Ask For Permission (that will call command.shuoldBeExecuted() )modes
// ****************************************************************************************************************************************************************************************
public static webapi void QappCommandHandler.ForwardCommand(
  string command                                   // 
  string mainId                                    // 
  optional string token = ""                       // 
  optional string:commandExecutionModes mode = "E" // 
)
{
  // IMPORTANT: token is used only when web API is called (it is handled in the on Web API event for authentication)
  // it is anyway added as parameter so the api will accept a token parameter!
   
  if (mode == Ping)
  {
    // ping is handled in onwebapi event only so there is no code here
    return 
  }
  else if (mode == CompleteDeletion)
  {
    // in case of completeDeletion we immediately remove the Qapp
    QappCore.QappCommandHandler.removeQapp("x2U#7s@Kf9G*5t")
    return 
  }
   
  // data contained in parameters is processed...
   
  // if mainID contains datetime info it is stored in dateTimeinfo, if not dateTimeInfo is set to NULL, same for subId
  string cleanMainID = ""
  string subId = ""
  date time dateTimeInfo = #1899/12/30 00:00:00#
  IDArray multiIds = QappCore.QappCommandHandler.extractAdditionalInfoFromMainIDString(mainId, dateTimeInfo, cleanMainID, subId)
   
  // handle the special case of -1 that stands for NULL so the QApp can handle NULL and not -1 who is an odd value
  cleanMainID = if(cleanMainID == "-1", null, cleanMainID)
   
  QappCommandRuntimeParameters runtimeParameters = QappCommandRuntimeParameters.create(cleanMainID, subId, dateTimeInfo, multiIds)
   
  boolean proceedWithCommandExecution = false
  boolean dataValid = false
  string errorMessage = ""
  QappCore.QappCommandHandler.setQappCoreSessionInitiatorType(apiCall)
  if (mode != callShouldBeExecuted)
  {
    boolean customPostLoginCodeExecuted = QappCore.QappCommandHandler.handleCustomPostLoginCodeIfNeeded(dataValid, errorMessage)
    if (dataValid == false)
      QappCore.DTTLogMessage("dataValid was not set in customPostLoginCode", ..., DTTError)
     
    if (customPostLoginCodeExecuted)
    {
      proceedWithCommandExecution = dataValid
    }
    else 
    {
      proceedWithCommandExecution = true
    }
  }
  else 
  {
     
    // inc case of call should be executed we want to execute the command (in callShouldBeExecuted mode) so we set proceed to true
    proceedWithCommandExecution = true
  }
   
  if (proceedWithCommandExecution)
  {
    // ... and finally passed to the method that really handles the command
    QappCore.QappCommandHandler.ExecuteCommand(command, runtimeParameters, mode)
  }
  else 
  {
     
    // Forcefuly setting a silent response so in case the api command supports silent messages we can display a message in caller
    QappCore.QappCommandHandler.setSilentResponse(errorMessage, "message", "17CFAE75-791D-4FD6-B23C-24BB9C04AF75")
  }
}


// ──────────────────────────────────

// *******************************************************************************
// method used internally by QappCore, it should not be called outside of QappCore
// *******************************************************************************
public void QappCommandHandler.setSilentResponse(
  string value                       // 
  optional string key = "message"    // 
  optional string usagePassword = "" // 
)
{
   
  // usage password is a trick to make this method "less public", at the moment of writing it is used only in QappCmmand.showMessageInCaller
  if (usagePassword != "17CFAE75-791D-4FD6-B23C-24BB9C04AF75")
  {
    QappCore.DTTLogMessage("setSilentMessage should not be called directly, use QappCommand.showMessageInCaller instead from a silent command!", ..., DTTError)
    return 
  }
  SilentCommandResponseValue = value
  SilentCommandResponseKey = key
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string QappCommandHandler.getSilentResponseValue()
{
  return SilentCommandResponseValue
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private string QappCommandHandler.prepareJsonResponse()
{
  IDMap idm = new()
   
  // response is added to the response only if it is not empty
  if (ShuoldBeExecutedResponse != "")
    idm.setValue("response", ShuoldBeExecutedResponse)
  idm.setValue(SilentCommandResponseKey, SilentCommandResponseValue)
  string json = JSON.stringify(idm)
   
  return json
}


// ──────────────────────────────────

// *******************************************************************
// helper method that extracts aditional info from the mainiDs string:
// 
// 1) subID is extracted
// 2) dateTime info is extracted and converted into a datetime value
// *******************************************************************
public static IDArray QappCommandHandler.extractAdditionalInfoFromMainIDString(
  string mainIDString          // 
  inout date time dateTimeInfo // 
  inout string cleanMainId     // 
  inout string subId           // 
)
{
  date time extractedDateTime = #1899/12/30 00:00:00#
  string extractedMainId = ""
  string extractedSubId = ""
   
  string dateTimePassedString = SH.rightUpToDelimiter(mainIDString, "@", ...)
   
  // check if datetimeinfo is passed...
   
  if (find(mainIDString, "@", ...) > 0)
  {
    string yearString = left(dateTimePassedString, 4)
    int year = toInteger(yearString)
    string monthString = mid(dateTimePassedString, 6, 2)
    int month = toInteger(monthString)
    string dayString = mid(dateTimePassedString, 9, 2)
    int day = toInteger(dayString)
    string hourString = mid(dateTimePassedString, 12, 2)
    int hour = toInteger(hourString)
    string minutesString = mid(dateTimePassedString, 15, 2)
    int minutes = toInteger(minutesString)
     
    extractedDateTime = dateTime(year, month, day, hour, minutes, 0)
    extractedMainId = SH.leftUpToDelimiter(mainIDString, "@", ...)
  }
  else 
  {
    extractedDateTime = null
    extractedMainId = mainIDString
  }
   
   
  // ...then check if a subid is passed...
  if (find(extractedMainId, "_", ...) > 0)
  {
    extractedSubId = SH.rightUpToDelimiter(extractedMainId, "_", ...)
    extractedMainId = SH.leftUpToDelimiter(extractedMainId, "_", ...)
  }
  else 
  {
    extractedSubId = null
  }
   
  // ...and finally extract multiselected Ids in case there are at least 2 multiselected items
  // note: in case of multiselection MainId is cleared (so MultiId will be used)
  // we parse formultiple tokensevenifno tokens are showthere so we always have an idarray
   
  IDArray multiselectedIds = new()
  StringTokenizer st = new()
  st.setString(extractedMainId, "$", ...)
  while (st.hasNextToken())
  {
    string currentToken = st.nextToken()
    multiselectedIds.addValue(currentToken)
  }
   
  IDArray multiIds = multiselectedIds
   
  // mainiD is cleared in case there is more than one selected, otherwise it contains the only one selected
  if (multiselectedIds.length() >= 2)
  {
    extractedMainId = multiselectedIds.getValue(0)
  }
   
  dateTimeInfo = extractedDateTime
  cleanMainId = extractedMainId
  subId = extractedSubId
   
  return multiIds
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static IDMap QappCommandHandler.createParamtersMap(
  string command         // 
  string mainID          // 
  string subID           // 
  date time dateTimeInfo // 
)
{
  IDMap parametersMap = new()
  parametersMap.setValue("command", command)
  parametersMap.setValue("mainId", mainID)
  parametersMap.setValue("subId", subID)
  parametersMap.setValue("dateTimeInfo", dateTimeInfo)
   
  return parametersMap
}


// ──────────────────────────────────

// *******************************************************************************************************************
// method that causes the execute() method to be called on a command that is created form the name passed as parameter
// *******************************************************************************************************************
private void QappCommandHandler.ExecuteCommand(
  string commandName                             // 
  QappCommandRuntimeParameters runtimeParameters // 
  string:commandExecutionModes mode              // 
)
{
  QappCommand qc = this.CommandFactory(commandName, runtimeParameters, ...)
   
  // initialzie command so we create the parameters
  qc.performInit()
  QappCommandParameters qcp = qc.getQAppCommandParameters()
   
  if (mode == callShouldBeExecuted)
  {
    if (qcp.CallShouldBeExecuted)
    {
      // example of postman call to test Ask Command: (method should be FORWARDCOMMAND, not GET)
      // http://192.168.48.163:1310/TestQapp/QappCore/QappCommandHandler?command=AfterChiudiDisposizionePopup&mainId=452&token=834357F1-29B0-4A3B-B5E2-A7B9A9A80678&mode=A
       
      boolean permissionGranted = qc.shouldBeExecuted()
      string:shuoldBeExecutedReponseTypes response = if(permissionGranted, Proceed, Stop)
      QappCore.QappCommandHandler.setShuoldBeExecutedResponse(response, "17CFAE75-791D-4FD6-B23C-24BB9C04AF75")
    }
  }
  else 
  {
    if (qcp.Async)
    {
      // ask Server Session to execute but call beforeExecute to show a message to the user before starting the command in a session
       
      string sessionUniqueName = formatMessage("asyncCommand_|1_|2_|3", commandName, QappCore.Loggeduser.Username, SH.createGUID(), ...)
       
      string runtimeParametersAsJson = runtimeParameters.ToJson()
       
      string commandString = formatMessage("CMD=executeAsync&commandName=|1&jsonObject=|2&loggedUserId=|3", commandName, runtimeParametersAsJson, QappCore.Loggeduser.IDUTENTE, ...)
       
      string beforeExecuteMessage = ""
      qc.BeforeAsyncExecute(beforeExecuteMessage)
      if (beforeExecuteMessage != "")
      {
         qc.setSilentResponse(beforeExecuteMessage, ...)
      }
       
      QappCore.startSession(sessionUniqueName, commandString)
    }
    else 
    {
      qc.performExecution()
    }
  }
   
}


// ──────────────────────────────────

// *********************************************************************************************************************************************************************
// this method simply creates an object of the proper class,null is returned if the className is not a command
// 
// in case parameters are passed (useful when factory is called before execution, but not in case  of creation for initialization) those are assigned to the command too
// *********************************************************************************************************************************************************************
public QappCommand QappCommandHandler.CommandFactory(
  string className                                        // 
  optional QappCommandRuntimeParameters runtimeParameters // 
  optional string forceLibraryName = ""                   // 
)
{
  string libraryName = this.getLibraryFileName(forceLibraryName)
   
  IDDocument doc = QappCore.createFormFromLibrary(libraryName, className)
   
  QappCommand qc = null
  if (doc)
  {
    if (QappCommand.isMyInstance(doc))
    {
      qc = (QappCommand)doc
       
      // the class name is saved also in the matching property so it can be used later, when loading from DB we set this from CMDPARAMS
      qc.setCommandClassName(className)
    }
  }
  else 
  {
    QappCore.DTTLogMessage("Error creating class from Library " + className, ...)
  }
   
   
  if (runtimeParameters)
  {
    qc.RuntimeParameters = runtimeParameters
  }
   
  return qc
}


// ──────────────────────────────────

// *********************************************************************************************************************************************************************
// this method simply creates an object of the proper class,null is returned if the className is not a command
// 
// in case parameters are passed (useful when factory is called before execution, but not in case  of creation for initialization) those are assigned to the command too
// *********************************************************************************************************************************************************************
private QappDataStyle QappCommandHandler.styleFactory(
  string className // 
)
{
  string libraryName = this.getLibraryFileName(...)
   
  IDDocument doc = QappCore.createFormFromLibrary(libraryName, className)
   
  QappDataStyle qds = null
  if (doc)
  {
    if (QappDataStyle.isMyInstance(doc))
      qds = (QappDataStyle)doc
  }
  else 
  {
    QappCore.DTTLogMessage("Error creating from library for class " + className, ..., DTTInfo)
  }
   
  return qds
}


// ──────────────────────────────────

// ************************************************************************************************************************
// computes the library name (the jar or dll name of the component containing the qapp commands) as it should be at runtime
// it is possible to pass a forcedName so the method computes only the extension
// ************************************************************************************************************************
private string QappCommandHandler.computeLibraryName()
{
  string libraryExtension = "dll"
   
//  java
//  {
//    libraryExtension = "jar"
//  }
   
  string componentName = componentName()
   
  // since the class that extends command handler MUST be in a inde component (while the variable used in InitializeQapp could be in the webapp) componentName will fail, since a non understandable error occurs
  // in that case, this DTTLogMessage gives an helpful hint
  if (componentName == "")
  {
    QappCore.DTTLogMessage("The class that extends QappCommandHandler must be put in a component, not in the web application. If you are seeing an on screen RED error, this is the cause: move the class to a 
          component and the problem will go away.", ..., DTTError)
  }
   
  string libraryName = formatMessage("|1.|2", componentName, libraryExtension, ...)
   
  return libraryName
}


// ──────────────────────────────────

// *****************************************************************************************************************************************************************************
// This method must be called in the specialized QAppCommandHandler in the setQappMainData method, it must be called for each command craeted as a class who extends QappCommand
// *****************************************************************************************************************************************************************************
public void QappCommandHandler.initializeCommand(
  string className                      // 
  optional string forceLibraryName = "" // 
)
{
  QappCommand qc = this.CommandFactory(className, ..., forceLibraryName)
   
  if (qc)
  {
    if (qc.shouldBeConsidered())
    {
      // in onInit the GUID is set
      qc.init()
      qc.performInit()
      qc.endTransaction()
      QappCore.addCommand(qc)
    }
    else 
    {
      QappCore.DTTLogMessage(formatMessage("Qapp Command '|1' is not considered so it won't be initialized and added to the QappCommands", qc.CAPTIONITA, ...), ..., DTTWarning)
    }
  }
   
}


// ──────────────────────────────────

// *****************************************************************************************************************************************************************************
// This method must be called in the specialized QAppCommandHandler in the setQappMainData method, it must be called for each command craeted as a class who extends QappCommand
// *****************************************************************************************************************************************************************************
public void QappCommandHandler.initializeStyle(
  string className // 
)
{
  QappDataStyle qds = this.styleFactory(className)
   
  if (qds)
  {
    qds.init()
    qds.initialize()
    qds.endTransaction()
     
    QappCore.addStyle(qds)
  }
   
}


// ──────────────────────────────────

// ***********************************************************
// method that instantiates all the commands in a single place
// ***********************************************************
public void QappCommandHandler.initializeAllCommands(
  optional string forceLibraryName = "" // 
)
{
  string libraryName = this.getLibraryFileName(forceLibraryName)
   
  IDArray ida = QappCore.getLibraryClassList(libraryName, true)
   
  for (int i = 0; i < ida.length(); i = i + 1)
  {
    string className = ida.getValue(i)
//    QappCore.DTTLogMessage(formatMessage("QappCommandHandler.initializaAllCommands. class: |1", className, ...), ..., DTTInfo)
     
    // avoid QappCommand class to not generate a loop
    if (className != "" && className != "QappCommand")
    {
      QappCore.QappCommandHandler.initializeCommand(className, forceLibraryName)
    }
  }
}


// ──────────────────────────────────

// ***************************************
// method that instantiates all the styles
// ***************************************
public void QappCommandHandler.initializeAllStyles()
{
   
  string libraryName = ""
  try 
  {
    libraryName = this.getLibraryFileName(...)
  }
  catch 
  {
    QappCore.DTTLogMessage("The class that extends QappCommandHandler must be put in a Component. Ask for support. The global variable of the type of that class should be put in the main webapp instead", ..., 
          DTTError)
    return 
  }
   
   
  IDArray ida = QappCore.getLibraryClassList(libraryName, true)
   
  for (int i = 0; i < ida.length(); i = i + 1)
  {
    string className = ida.getValue(i)
    QappCore.QappCommandHandler.initializeStyle(className)
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void QappCommandHandler.initializeAll()
{
  QappCore.QappCommands.clear()
  QappCore.QappDataStyles.clear()
  this.initializeAllCommands(...)
   
  // we initialize all styles only if enableqappdata is set to yes otherwise it is useless
  // since in one qapp we had an odd issue relaetd to style initialization, better to avoid doing if not needed (it is cleaner)
  if (QappCore.QappWeb.ENABLEQAPPDATA == Yes)
    this.initializeAllStyles()
}


// ──────────────────────────────────

// *******************************************************************************
// method used internally by QappCore, it should not be called outside of QappCore
// *******************************************************************************
public void QappCommandHandler.setShuoldBeExecutedResponse(
  string:shuoldBeExecutedReponseTypes response // 
  optional string usagePassword = ""           // 
)
{
   
  // usage password is a trick to make this method "less public", at the moment of writing this is only used with explicit usagePassword call
  if (usagePassword != "17CFAE75-791D-4FD6-B23C-24BB9C04AF75")
  {
    QappCore.DTTLogMessage("setShuoldBeExecutedResponse should not be called directly, you need to provide the usagePassword!", ..., DTTError)
    return 
  }
  ShuoldBeExecutedResponse = response
}


// ──────────────────────────────────

// *******************************************
// call this method to close a popup window
// it will work only if the command is a popup
// *******************************************
public void QappCommandHandler.closePopupForm()
{
  if (SessionQappCommand)
  {
    QappCommandParameters qcp = SessionQappCommand.getQAppCommandParameters()
    if (qcp.Popup)
      SessionQappCommand.performClosePopupForm()
    else 
      QappCore.DTTLogMessage("It is not possible to call closePopupForm if the command is not Popup", ..., DTTError)
  }
  else 
    QappCore.DTTLogMessage("sessionQappCommand not set", ..., DTTError)
}


// ──────────────────────────────────

// *******************************************************************************************************************
// call this method to open a Module in Qualibus, it i will work only for Cruscotti, Pagina di tab control and popups\
// *******************************************************************************************************************
public void QappCommandHandler.openModule(
  int:kordapp kordApp // 
  int mainId          // 
)
{
  if (SessionQappCommand)
  {
    QappCore.DTTLogMessage(formatMessage("opening Module '|1' with id '|2'", decode(kordApp, Kordapp), mainId, ...), ..., DTTInfo)
    SessionQappCommand.performOpenModule(kordApp, mainId)
  }
  else 
  {
    // throw used instead of DTTLog Message so the error is more evident in all cases
    throw 0, "SessionQappCommand not set"
  }
   
}


// ──────────────────────────────────

// **************************************************************************************************************************************************
// retrieves the value (as string) stored in settings.json
// 
// if forcedRefresh is true thesettings file is read again so any recently done changes are applied (this makes sense for parameters like thresholds)
// **************************************************************************************************************************************************
public string QappCommandHandler.getSetting(
  string name                       // 
  optional boolean forceRefresh = 0 // 
)
{
  if (!(QappCore.SettingsLoaded))
  {
    QappCore.DTTLogMessage("It is not possible to read settings, beause the settings have not been initialized yet: you are likely calling this method too early, use it after calling InitializeQapp in the 
          Initialize event of the Qapp", ..., DTTError)
    return null
  }
   
  if (forceRefresh)
  {
    QappCore.DTTLogMessage("All settings are being read again from file", ..., DTTInfo)
    this.readSettings()
  }
   
  string tagValue = getTag(this.getSettingTagPrefix() + name)
  if (tagValue == null)
  {
    QappCore.DTTLogMessage(formatMessage("Setting '|1' not found", name, ...), ..., DTTError)
  }
  return tagValue
   
   
}


// ──────────────────────────────────

// *******************************************************************************
// settings are read from settings.json and stored into tags
// they could then be read at runtime by calling getSetting on the command handler
// *******************************************************************************
private void QappCommandHandler.readSettings()
{
  if (SettingsFileName == "")
  {
    return 
  }
   
  int settingsIndex = QappCore.freeFile()
  string settingsFullPath = QappCore.path() + FH.getSeparator() + SettingsFileName
   
  QappCore.DTTLogMessage(formatMessage("Reading settings file from path: |1", settingsFullPath, ...), ..., DTTInfo)
   
  QappCore.openFileForInput(settingsFullPath, settingsIndex, "UTF-8")
  XMLDocument settings = new()
   
  try 
  {
    settings.load(settingsFullPath, JSON)
    QappCore.SettingsLoaded = true
  }
  catch 
  {
    QappCore.DTTLogMessage(formatMessage("Error when loading JSON file, details: |1", errorMessage(), ...), ..., DTTError)
  }
   
  XMLNode xmln = settings.getRootNode()
   
  while (xmln.hasNextAttribute())
  {
    string name = ""
    string value = ""
    string namespace = ""
    xmln.getNextAttribute(name, value, namespace)
     
    QappCore.DTTLogMessage(formatMessage("Setting '|1' found, value: '|2'", name, value, ...), ..., DTTInfo)
    this.setTag(this.getSettingTagPrefix() + name, value)
  }
   
  QappCore.closeFile(settingsIndex)
}


// ──────────────────────────────────

// *******************************************************************************************
// returns an hardcoded  string that is used to create the tags in which we store the settings
// *******************************************************************************************
private string QappCommandHandler.getSettingTagPrefix()
{
  return "SETTING_"
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void QappCommandHandler.setSettingsFilename(
  string filename // 
)
{
  string fullPath = QappCore.path() + FH.getSeparator() + filename
  if (fileExists(fullPath))
  {
    SettingsFileName = filename
  }
  else 
  {
    QappCore.DTTLogMessage(formatMessage("Settings File not found: '|1'", fullPath, ...), ..., DTTError)
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string QappCommandHandler.getSettingsFilename()
{
  return SettingsFileName
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void QappCommandHandler.setSettingsPassword(
  string password // 
)
{
  SettingsPassword = password
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean QappCommandHandler.isPasswordCorrect(
  string passedPassword // 
)
{
  QappCore.DTTLogMessage(formatMessage("The recieved passowrd is '|1'", passedPassword, ...), ..., DTTInfo)
  if (SettingsPassword == passedPassword)
  {
    QappCore.DTTLogMessage("The settings password is correct", ..., DTTInfo)
    return true
  }
  else 
  {
    QappCore.DTTLogMessage("The password is not correct", ..., DTTError)
    return false
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void QappCommandHandler.writeLoginMessage(
  string message // 
)
{
  QappRuntimeBehavior qrb = getQappRuntimeBehavior()
   
  if (qrb.MobileFirst == true)
  {
    MobileLoginForm.sendMessage("SET_LOGIN_MESSAGE", null, message, ...)
  }
  else 
  {
    IDMap idm = new()
    idm.setValue("LOGIN_MESSAGE", message)
    QappCore.sendAppMessage("SET_LOGIN_MESSAGE", idm)
  }
}


// ──────────────────────────────────

// ****************************************************************
// the script file is processed and returned as an array of batches
// in case problems are encountered null is returned
// ****************************************************************
private IDArray QappCommandHandler.retrieveScriptAsArrayOfBatches(
  string scriptFilename // 
)
{
   
  // scripts are like outputPath/sql/initN.sql or /sql/delete.sql
  string currentScriptExpectedPath = formatMessage("|1|2|3", QappCore.path(), "/sql/", scriptFilename, ...)
  IDArray sqlBatches = new()
  if (fileExists(currentScriptExpectedPath))
  {
    QappCore.DTTLogMessage(formatMessage("|1 file found, performing action...", currentScriptExpectedPath, ...), ..., DTTInfo)
    FH fh = FH.Create(currentScriptExpectedPath, ...)
    IDArray wholeScriptAsArray = fh.contentToArray(...)
     
    sqlBatches = SqlCodeHelper.extractExecutableBatches(wholeScriptAsArray)
     
    if (sqlBatches == null)
    {
      QappCore.DTTLogMessage(formatMessage("|1 does not contain any batch", scriptFilename, ...), ..., DTTError)
    }
  }
  else 
  {
    sqlBatches = null
    QappCore.DTTLogMessage(formatMessage("|1 file NOT found", scriptFilename, ...), ..., DTTError)
  }
   
   
  if (sqlBatches)
  {
    if (sqlBatches.length() == 0)
    {
      QappCore.DTTLogMessage(formatMessage("|1 does not contain any batch", scriptFilename, ...), ..., DTTError)
      sqlBatches = null
    }
  }
   
   
  return sqlBatches
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string QappCommandHandler.getSettingsPassword()
{
  return SettingsPassword
}


// ──────────────────────────────────

// ******************************************************************************************************************************************
// call this method to open the Window default browser at the passed url, it i will work only for Cruscotti, Pagina di tab control and popups
// ******************************************************************************************************************************************
public void QappCommandHandler.openBrowserInWindows(
  string url // 
)
{
  DevTools.ToBeReviewed("this code opens the default MS Windows browser through Delphi shellExecute, once migrating to web what we could do is simply call OpenDocument???")
  if (SessionQappCommand)
  {
    SessionQappCommand.performOpenBrowser(url)
    QappCore.DTTLogMessage(formatMessage("opening browser at url '|1'", url, ...), ..., DTTInfo)
  }
  else 
  {
    // throw used instead of DTTLog Message so the error is more evident in all cases
    throw 0, "SessionQappCommand not set"
  }
}


// ──────────────────────────────────

// *********************************************************************************************************************************
// this method, that in principle should be called from WebApi only, compltely deletes a Qapp, this is why it is password protected 
// *********************************************************************************************************************************
public void QappCommandHandler.removeQapp(
  string password // 
)
{
  this.reloadDefaultQappDataIfNeeded()
   
  if (password != "x2U#7s@Kf9G*5t")
  {
    QappCore.DTTLogMessage("You cannot use removeQapp: wrong password", ..., DTTError)
    return 
  }
   
  string errorDetails = ""
  this.deleteDataFromStandardTables(errorDetails)
  if (errorDetails == "")
  {
    this.deleteDataFromCustomTables(errorDetails)
    this.markQappAsDeleted()
  }
   
  string silentMessageValue = ""
  string silentMessageKey = ""
  if (errorDetails == "")
  {
    silentMessageValue = "deletion completed"
    silentMessageKey = "message"
  }
  else 
  {
    silentMessageValue = formatMessage("There was an error, details: |1", errorDetails, ...)
    silentMessageKey = "errormessage"
  }
   
   
  this.setSilentResponse(silentMessageValue, silentMessageKey, "17CFAE75-791D-4FD6-B23C-24BB9C04AF75")
}


// ──────────────────────────────────

// ******************************************************************************************************
// delete data from NGT_QAPPS_COMMANDS, NGT_QAPPS_DATA, NGT_QAPPS_STYLES, NGT_PRIVILEGI,NGT_APPLICAZIONI 
// ******************************************************************************************************
private void QappCommandHandler.deleteDataFromStandardTables(
  inout string errorMessage // 
)
{
  QappWeb qw = QappWeb.getQappWeb(QappCore.QappWeb.Name)
   
  int IdQapp = qw.ID
   
  try 
  {
    update CRUQUERY
      set IDQAPPCOMMAND = null
    where
      exists(subquery)
         select top 1 // 
           CRUQUERY.IDQAPPCOMMAND
         from 
           NGTQAPPSCOMMANDS // master table
         where
           NGTQAPPSCOMMANDS.IDAPPLICAZIONE == IdQapp
     
    delete from NGTQAPPSCOMMANDS
    where
      IDAPPLICAZIONE == IdQapp
     
    delete from NGTQAPPSDATA
    where
      IDAPPLICAZIONE == IdQapp
     
    delete from NGTQAPPSSTYLES
    where
      IDAPPLICAZIONE == IdQapp
     
    delete from NGTPRIVILEGI
    where
      IDAPPLICAZIONE == IdQapp
     
    delete from NGTAPPLICAZIONI
    where
      IDAPPLICAZIONE == IdQapp
     
    delete from APPDBVERS
    where
      QAPPDESCR == qw.Name
  }
  catch 
  {
    string specificError = errorMessage()
    errorMessage = specificError
  }
   
  string silentMessageContent = ""
  if (errorMessage != "")
  {
    silentMessageContent = formatMessage("Error during standard tables deletion: |1", errorMessage, ...)
  }
   
  QappCore.DTTLogMessage("DATA FROM STANDARD TABLES DELETED", ...)
   
}


// ──────────────────────────────────

// *************************************************************************
// deletion of custom tables created with the initN.sql scripts is performed
// *************************************************************************
private void QappCommandHandler.deleteDataFromCustomTables(
  inout string errorMessage // 
)
{
  string errorDetails = ""
   
  this.executeScriptFromFile("delete.sql", errorDetails)
   
  if (errorDetails != "")
  {
    errorMessage = formatMessage("Error during custom tables deletion: |1", errorDetails, ...)
  }
}


// ──────────────────────────────────

// ***********************************************************************************************
// a delete.me file is created in the app root so that a cron can check for it and delete the Qapp
// ***********************************************************************************************
private void QappCommandHandler.markQappAsDeleted()
{
   
  string deleteMeFilePath = formatMessage("|1/|2", QappCore.path(), "delete.me", ...)
   
  // first of all try to delete the file if already existing (mostly a test condition)
  try 
  {
    QappCore.deleteFile(deleteMeFilePath)
  }
  catch 
  {
    // do nothing in case file not foudn or problem on deletion
     
  }
   
  FH deleteMeFile = FH.Create(deleteMeFilePath, ...)
   
  string timestampinfo = formatMessage("created on: |1", format(now(), "dd/mm/yyyy hh:nn:ss", ...), ...)
  deleteMeFile.addLine(timestampinfo)
}


// ──────────────────────────────────

// ************************************************************************************
// this method reads a file treating it as a sql script and executes it completely
// 
// the filename passed must match with the filename of an existing file in /sql/ folder
// 
// if no errors in script execution true is returned
// 
// ************************************************************************************
private boolean QappCommandHandler.executeScriptFromFile(
  string filename           // 
  inout string errorDetails // 
)
{
  boolean success = true
  IDArray batchesToBeExecuted = this.retrieveScriptAsArrayOfBatches(filename)
   
  if (batchesToBeExecuted)
  {
    for (int i = 0; i < batchesToBeExecuted.length(); i = i + 1)
    {
      string currentbatch = batchesToBeExecuted.getValue(i)
      QappCore.DTTLogMessage(formatMessage("executing batch: '|1'", currentbatch, ...), ..., DTTInfo)
      try 
      {
         QualibusDB.SQLExecute(currentbatch)
      }
      catch 
      {
         success = false
         QappCore.DTTLogMessage(formatMessage("Batch execution failed, please review the batch content in |1", filename, ...), ..., DTTError)
         errorDetails = errorMessage()
         break 
      }
    }
  }
  return success
}


// ──────────────────────────────────

// *******************************************************************************************
// the dll or jar filename of the library where QappComandHandler class is defined is returned
// passing a string (like myQapp.dll allows to force the filename, useful in unit tests)
// *******************************************************************************************
public string QappCommandHandler.getLibraryFileName(
  optional string forceLibraryFileName = "" // 
)
{
   
  // this method is a public wrapper of the private computeLibraryName method
  string computedLibraryName = this.computeLibraryName()
   
  if (forceLibraryFileName == "")
  {
    computedLibraryName = this.computeLibraryName()
  }
  else 
  {
    computedLibraryName = forceLibraryFileName
  }
   
  return computedLibraryName
}


// ──────────────────────────────────

// ****************************************************************
// call this method to close a config Form for customReport command
// 
// ****************************************************************
public void QappCommandHandler.closeConfigForm(
  IDMap configurationMap // 
)
{
  if (SessionQappCommand)
  {
    SessionQappCommand.setConfigurationMap(configurationMap)
     
    QappCommandParameters qcp = SessionQappCommand.getQAppCommandParameters()
    if (qcp.ConfigFormDefined)
      SessionQappCommand.performCloseConfigForm()
    else 
      QappCore.DTTLogMessage("It is not possible to call closeConfigForm if the command parmeters do not have ConfigFormDefined", ..., DTTError)
  }
  else 
    QappCore.DTTLogMessage("sessionQappCommand not set", ..., DTTError)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void QappCommandHandler.appendProcessDatabaseErrorMessage(
  string errorMessage // 
)
{
   
  // do nothing if errorMessage is empty...
  if (errorMessage == "")
    return 
   
   
  // ...otherwise append it
  ProcessDatabaseErrorMessage = SH.Concat(ProcessDatabaseErrorMessage, errorMessage, ", ")
}


// ──────────────────────────────────



// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean QappCommandHandler.getQappInsertedForTheFirstTime()
{
  return QappInsertedForTheFirstTime
}


// ──────────────────────────────────

// *****************************************************************************************************************************************************************************************************
// returns true whether the qappcore session has been started by a command or not
// tipical cases
// FALSE. session started by a login from the login screen
// TRUE session started by hook or by another kind of command (cruscotto or toolbar button)
// 
// 
// NOTE: we have 2 ways of checking whether the App started by Qapp COmmand:
// 1. having a sessionQappCommand set
// 2. LOGIN command was used
// 
// since in assenze Qapp on 30/09/2025 we neede to know better which was the initiator type it has been decided to pass it as inout: this will break existings apps and we need to pass a var to compile
// 
// at the moment of writing (19/05/2025) this seems the most correct way of checking this
// *****************************************************************************************************************************************************************************************************
public boolean QappCommandHandler.sessionStartedByQappCommand(
  inout string:qappCoreSessionInitiatorType qappCoreSessionInitiatorType // inout param to better inspect the initiator type

)
{
  boolean sessionHasAQappCommand = SessionQappCommand != null
  boolean sessionStartedByApiCAll = QappCoreSessionInitiatorType == apiCall
  boolean sessionStartedByLoginCmdWithToken = QappCoreSessionInitiatorType == loginWithQAPP_CMDAndToken
  boolean sessionStartedByQappCommand = sessionStartedByApiCAll or sessionStartedByLoginCmdWithToken or sessionHasAQappCommand
   
  // write the inout parameter
  qappCoreSessionInitiatorType = QappCoreSessionInitiatorType
   
  return sessionStartedByQappCommand
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void QappCommandHandler.setQappCoreSessionInitiatorType(
  string:qappCoreSessionInitiatorType initiatorType // 
)
{
  QappCoreSessionInitiatorType = initiatorType
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string QappCommandHandler.getQappCoreSessionInitiatorType()
{
  return QappCoreSessionInitiatorType
}


// ──────────────────────────────────

// ***********************************************
// returns true if customPostLoginCode is executed
// ***********************************************
public boolean QappCommandHandler.handleCustomPostLoginCodeIfNeeded(
  inout boolean DataValid   // 
  inout string ErrorMessage // 
)
{
  boolean qappNotInserted = false
  QappCore.Loggeduser.loadPrivilegeForCurrentQapp(qappNotInserted)
   
  boolean customPostLoginCodeExecuted = false
  QappRuntimeBehavior qrb = getQappRuntimeBehavior()
  if (qrb.ExecuteCustomPostLoginCode)
  {
    QappCore.QappCommandHandler.doCustomPostLoginCode(DataValid, ErrorMessage)
    customPostLoginCodeExecuted = true
  }
  else 
  {
     
    // if a Qapp does not have post login Code we want to execute commands without any additional check, so we force dataValid to true
    DataValid = true
  }
  return customPostLoginCodeExecuted
}


// ──────────────────────────────────

// *****************************************************************************
// Event raised to the document when one of its methods is called through WebAPI
// *****************************************************************************
event QappCommandHandler.OnWebAPI(
  inout boolean Cancel // Boolean output parameter. If set to true, prevents the WebAPI call.
)
{
  IDMap webapiParams = WebAPIService.getParameters()
   
  string passedToken = ""
   
  // Ping is not authenticated, so token is not used
  if (webapiParams.containsKey("mode"))
  {
    string:commandExecutionModes mode = webapiParams.getValue("mode")
    if (mode == Ping)
    {
      Cancel = true
       
      // a simple string is returned, not even formatted as json for simplicity
      WebAPIService.setResponse("Pong", 200, "application/json")
      return 
    }
  }
   
  if (webapiParams.containsKey("token"))
  {
    QappCore.markSessionAsStartedByWebApi()
    passedToken = webapiParams.getValue("token")
    boolean isValidToken = false
     
    // in case of webAPI a command is for sure involved
    boolean commandContainsAUrlCommand = true
    Utente authenticatedUser = LoginToken.authenticateUserWithToken(passedToken, commandContainsAUrlCommand, isValidToken)
     
    if (authenticatedUser and isValidToken)
    {
      QappCore.Loggeduser = authenticatedUser
       
      WebApiAuthorized = true
    }
    else 
    {
      WebApiAuthorized = false
       
      Cancel = true
       
      WebAPIService.setResponse("{"message":"Non Authorized"}", 400, "application/json")
    }
  }
   
   
   
}


// ──────────────────────────────────

// ******************************************************************************
// Event raised to the document before sending the response to the WebAPI request
// ******************************************************************************
event QappCommandHandler.OnWebAPIResponse(
  inout int StatusCode     // Input/output integer parameter with the HTTP code of the response.
  inout string Content     // Input/output string parameter with the content of the body of the response.
  inout string ContentType // Input/output string parameter with the mime-type of the response.
)
{
  if (!(WebApiAuthorized))
  {
    return 
  }
   
  StatusCode = 200
  ContentType = "application/json"
  string jsonResponse = QappCore.QappCommandHandler.prepareJsonResponse()
  Content = jsonResponse
   
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event QappCommandHandler.OnInit()
{
  // read settings.json and populate the object tags
  this.readSettings()
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event QappWeb.OnInit()
{
  // Note: real ID is set in before save, this is just to pass the auomatic validation!
  ID = Sequence.getNextSequence(NGTN_APPLICAZIONI, ...)
  SEQAPPLICAZIONE = 1
  MENUINITGRUPPO = No
  ISQAPPWEB = Yes
   
  // QappWeb has no kordapp so we set it to 0 to be sure it has the desired value
  KORDAPP = 0
   
  // set some mandatory fields
  PERCORSOFILE = ""
  NOMEFILE = ""
  AUTOSTART = No
}


// ──────────────────────────────────

// **************************************************************************************
// Event raised to the document during the initial steps of the document saving procedure
// **************************************************************************************
event throws exception QappWeb.BeforeSave(
  inout boolean Skip         // A boolean output parameter. If set to True, tells the framework not to perform operations on the database, because they will be managed independently.
  inout boolean Cancel       // A boolean output parameter. If set to True, tells the framework to cancel the save procedure due to errors that occurred. Since the entire document is saved in the same transa...
  int:beforeSavePhases Phase // An integer from 0 to 3 that specifies the progress status of the save procedure as detailed at the beginning of the article.
)
{
  if (Phase == 0)
  {
    if (inserted)
    {
      Sequence.getNextSequence(NGTN_APPLICAZIONI, ...)
    }
  }
}


// ──────────────────────────────────

// ************************************************************
// Event raised to the document during the validation procedure
// ************************************************************
event QappWeb.OnValidate(
  int:validateReasons Reason // An integer specifying the reason the document is being validated. The most common values are 0 to indicate complete validation before saving, or 1 to indicate a quick validati...
  inout boolean Error        // A boolean output parameter. If set to True, specifies an error condition and stops the validation immediately.
  inout boolean Skip         // A boolean output parameter. If set to True, does not check the rules for automatic validation of the document (see notes).
)
{
  if (deleted)
    return 
   
  if (SHOWINQUALIBUSMENU == Yes)
  {
    if (isNull(MenuIcon))
    {
      this.addDocumentError("Qualibus Menu Icon must be set if enable in Qualibus is set")
      Error = true
    }
  }
  if (SHOWINQMOBILEMENU == Yes)
  {
    if (isNull(QmobileMenuIcon))
    {
      this.addDocumentError("Qmobile Menu Icon must be set if enable in Qmobile is set")
      Error = true
    }
  }
}


// ──────────────────────────────────

// ***************************************************
// given a name an object Qappweb is returned if found
// ***************************************************
public static QappWeb QappWeb.getQappWeb(
  string name // 
)
{
  QappWeb q = new()
  q.Name = name
  try 
  {
    q.loadFromDB(...)
  }
  catch 
  {
    q = null
  }
  return q
}


// ──────────────────────────────────

// ********************************************************************************************************************************************************************
// The blob fields for mainIcon and MenuIcon (menuIcon has a Qualibus and a Qmobile version) are filled with the blob coming from the files names set in InitializeQApp
// ********************************************************************************************************************************************************************
public void QappWeb.prepareBlobs()
{
  this.setIconsFullpath()
   
  // we check for file existance for each file
  if (fileExists(MainIconFullpath))
  {
    MainIcon = loadBlobFile(MainIconFullpath)
  }
  else 
  {
    QappCore.DTTLogMessage(formatMessage("|1 not found: |2", "main icon", MainIconFullpath, ...), ..., DTTInfo)
  }
  if (fileExists(MenuIconFullpath))
  {
    MenuIcon = loadBlobFile(MenuIconFullpath)
  }
  else 
  {
    QappCore.DTTLogMessage(formatMessage("|1 not found: |2", "menu icon", MenuIconFullpath, ...), ..., DTTInfo)
  }
  if (fileExists(MenuQmobileIconFullpath))
  {
    QmobileMenuIcon = loadBlobFile(MenuQmobileIconFullpath)
  }
  else 
  {
    QappCore.DTTLogMessage(formatMessage("|1 not found: |2", "menu Qmobile icon", MenuQmobileIconFullpath, ...), ..., DTTInfo)
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void QappWeb.setIconsFullpath()
{
  string basePath = this.getIconsBasePath()
   
  MainIconFullpath = basePath + MainIconFilename
  MenuIconFullpath = basePath + MenuIconFilename
  MenuQmobileIconFullpath = basePath + MenuQmobileIconFilename
}


// ──────────────────────────────────

// **************************************************************************************************************************************************************************
// sets the properties of the QappWeb object by reading them from the in common in memory object, the boolean paraameter is to manage the updation of WebUrl separately.
// At the moment of writing this method is always called by passing "true", but we leave it like this so it is more evident that WebUrl is changeable only if really required
// **************************************************************************************************************************************************************************
public void QappWeb.restoreQappWebDefaultData(
  boolean updateWebUrl // 
)
{
  QappCore.QappWeb.prepareBlobs()
   
  // only update WEBURL when explicitly requested
  // on normal logins with FORCE_UPDATE=Y in DB we must NOT overwrite
  // the URL configured in AppMan with the current session URL
  if (updateWebUrl)
  {
    string retrievedApplicationUrl = X.GetApplicationUrl(...)
    WEBURL = retrievedApplicationUrl
  }
   
  Description = QappCore.QappWeb.Description
  SHOWINQUALIBUSMENU = QappCore.QappWeb.SHOWINQUALIBUSMENU
  SHOWINQMOBILEMENU = QappCore.QappWeb.SHOWINQMOBILEMENU
  PRIVILEGETOSEEQAPPINMENU = QappCore.QappWeb.PRIVILEGETOSEEQAPPINMENU
  ENABLEQAPPDATA = QappCore.QappWeb.ENABLEQAPPDATA
   
  MenuIcon = QappCore.QappWeb.MenuIcon
  QmobileMenuIcon = QappCore.QappWeb.QmobileMenuIcon
   
  DEFAULTQAPP = QappCore.QappWeb.DEFAULTQAPP
   
  this.resetQAppDataStyles(resetExistingStyles)
   
  for each QappCommand qc in QappsCommands
  {
    qc.restoreQappCommandDefaultData()
  }
   
  ForceUpdate = No
  this.saveToDB(...)
}


// ──────────────────────────────────

// ******************************************************************************************************************************************************
// checks whether the set data by the QApp creator is ok
// 
// Note: we do not use OnValidate since the QAppWeb is handled in a special way by the SDK and we do not want to deal with the mandatory fields on the db
// 
// Note that the method uses the setTag method to pass the errorMessage to the caller
// ******************************************************************************************************************************************************
public boolean QappWeb.checkIfSetDataIsValid()
{
  this.prepareBlobs()
   
  boolean inError = false
  string errorMessage = ""
   
  if (length(Name) > 20)
  {
    errorMessage = formatMessage("QApp Name (|1) must be less than 20 chars", Name, ...)
    inError = true
  }
   
  if (SHOWINQUALIBUSMENU == Yes)
  {
    if (isNull(MenuIcon))
    {
      errorMessage = "Qualibus Menu Icon must be set if enable in Qualibus is set"
      inError = true
    }
  }
  if (!(inError))
  {
    if (SHOWINQMOBILEMENU == Yes)
    {
      if (isNull(QmobileMenuIcon))
      {
         errorMessage = "Qmobile Menu Icon must be set if enable in Qmobile is set"
         inError = true
      }
    }
  }
  this.setTag("errorMessage", errorMessage)
   
  return inError
}


// ──────────────────────────────────



// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void QappWeb.resetQAppDataStyles(
  string:optionsFields mode // 
)
{
  boolean resetExistingStyles = (mode == resetExistingStyles)
   
  QappWeb qw = this.getQappWeb(QappCore.QappWeb.Name)
   
  if (!(qw))
  {
    return 
  }
   
  int qappWebId = qw.ID
   
  IDCollection storedStyles of QappDataStyle = new()
  select into collection (storedStyles)
  from 
    QappDataStyle // master table
  where
    QappWebID == qappWebId
   
  // if the defined style does not exist yet in db we insert it...
  for each QappDataStyle userDefinedStyle in QappCore.QappDataStyles
  {
    // if not found insert
    // if found update
     
    boolean definedStyleAlreadyExists = false
     
    for each QappDataStyle styleInDatabase in storedStyles
    {
      if (userDefinedStyle.STYLEID == styleInDatabase.STYLEID)
      {
         definedStyleAlreadyExists = true
         break 
      }
    }
    if (!(definedStyleAlreadyExists))
    {
      userDefinedStyle.QappWebID = qappWebId
      userDefinedStyle.saveToDB(...)
    }
  }
   
  // ... else if it exists we update it...
  for each QappDataStyle storedStyle in storedStyles
  {
    boolean matchingStyleFound = false
    for each QappDataStyle definedStyle in QappCore.QappDataStyles
    {
      if (storedStyle.STYLEID == definedStyle.STYLEID)
      {
         if (resetExistingStyles)
         {
           storedStyle.restoreDefaults()
         }
          
          
         matchingStyleFound = true
         break 
      }
    }
     
    // ... and in DB we found one that is not defined we delete it
    if (!(matchingStyleFound))
    {
      // first we delete all data with that style (to avoid FK errors)...
      QappData.deleteAllDataOfGivenStyle(storedStyle.STYLEID)
       
       
      storedStyle.deleted = true
      storedStyle.saveToDB(...)
    }
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void QappWeb.setMainIconFilename(
  string filename // 
)
{
  MainIconFilename = filename
}


// ──────────────────────────────────



// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void QappWeb.setMenuQMobileIconFilename(
  string filename // 
)
{
  MenuQmobileIconFilename = filename
}


// ──────────────────────────────────

// ***************************************************
// given a name an object Qappweb is returned if found
// ***************************************************
public static QappWeb QappWeb.get(
  int idApplicazione // 
)
{
  QappWeb q = new()
  q.ID = idApplicazione
  try 
  {
    q.loadFromDB(...)
  }
  catch 
  {
    q = null
  }
  return q
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event QappVersion.OnInit()
{
   
  // on a new record we initialize the mandatory fields with distinctive values"...
  DBVERS = -1
   
  // ... and set the "zero" date as UPGDATE so we see that it is not there
  UPGDATE = toDateTime(0)
   
  // ... so when script 0 is actually executed DBVERS is really 0 and UPGDATE has a meaningful value
   
  string qualibusMajorVersion = TabParametri.getQualibusMajorVersion()
  QBVERSION = qualibusMajorVersion
}


// ──────────────────────────────────

// ****************************************************************
// returns true if an APP DB VERS record exists for the passed Qapp
// ****************************************************************
public static boolean QappVersion.dataExistsForCurrentQapp()
{
  boolean dataExists = false
   
  QappVersion qv = new()
   
  qv.QAPPDESCR = QappCore.QappWeb.Name
   
  try 
  {
    qv.loadFromDB(...)
    dataExists = true
  }
  catch 
  {
    dataExists = false
  }
   
  return dataExists
}


// ──────────────────────────────────

// ****************************************************************
// returns true if an APP DB VERS record exists for the passed Qapp
// ****************************************************************
public static QappVersion QappVersion.createForCurrentQapp()
{
  QappVersion qv = new()
  qv.init()
  qv.QAPPKORDID = QappCore.QappWeb.ID
   
  qv.QAPPDESCR = QappCore.QappWeb.Name
   
  return qv
}


// ──────────────────────────────────

// *****************************************************
// Extend this method to set the main data for the style
// *****************************************************
public void QappDataStyle.initialize()
{
  QappCore.DTTLogMessage("Abstract Error", ..., DTTError)
}


// ──────────────────────────────────

// ****************************************************************************************************************************************************************************************************************
// static constructor of a QApp Data Style, it does most of the heavy lifting related to creation, just mind these parameters: 
// - styleID: it must be an unique ID in the whole table, this is why it must be passed as QAPpWebName+name (e.g. "MyAppNameOkStyle" - it is the OkStyle for the MyAppName QApp)
// - qualibusIconFileName (and qmobileIconFileName): it must be a png and be in the form 'name.png', the file with the same name must be in the custom\QAppIcons\ folder o the QApp
// - Filter is optional and must be in the form "1;2" (e.g to publish the command for Infrastructure (=1) and Instruments (=2) only) - iy it id used also for eventi to haave comma separated list of ID_CLASSE
// 
// NOTE: it is meant to be used in the implementation of a class who extends QappCommandHandler, in the extended method setQappMainData, so the created QappCommand must be added to the public coomponent's "Qapp
// Commands" collection
// ****************************************************************************************************************************************************************************************************************
public static QappDataStyle QappDataStyle.createDEPRECATED(
  string styleID              // 
  int color                   // 
  string qualibusIconFilename // 
  string qmobileIconFilename  // 
  int sequence                // 
)
{
  QappDataStyle qds = new()
   
  qds.init()
   
   
   
  qds.STYLEID = styleID
  qds.COLOR = color
  qds.QualibusIconFilename = qualibusIconFilename
  qds.QmobileIconFilename = qmobileIconFilename
  qds.SEQ = sequence
  qds.endTransaction()
   
  return qds
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void QappDataStyle.setIconsFullpath()
{
  string basePath = QappCore.QappWeb.getIconsBasePath()
   
  QualibusIconFullpath = basePath + QualibusIconFilename
  QmobileIconFullpath = basePath + QmobileIconFilename
}


// ──────────────────────────────────

// *******************************************************************************************************************
// The blob fields for Qualibus Command Icon and QMobile Command Icon are populated from file, only if the files exist
// *******************************************************************************************************************
private void QappDataStyle.prepareBlobs()
{
  this.setIconsFullpath()
  if (fileExists(QualibusIconFullpath))
  {
    ICONQUALIBUS = loadBlobFile(QualibusIconFullpath)
  }
  if (fileExists(QmobileIconFullpath))
  {
    ICONQMOBILE = loadBlobFile(QmobileIconFullpath)
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private string QappDataStyle.getErrorMessages()
{
  string errors = ""
  Collection c = new()
  this.getDocumentErrors(c)
  int i = 0
  while (i < c.count())
  {
    errors = Tools.Concatenate(errors, c.getString(i), " - ")
    i = i + 1
  }
  return errors
}


// ──────────────────────────────────

// **************************************************************************************************
// validates the documetn and gives a DTTError useful for the Qapp creator, error message is returned
// 
// **************************************************************************************************
public string QappDataStyle.generateReadableExceptions()
{
  string exceptionMessage = ""
  boolean validDocument = validate(...)
  string documentErrors = this.getErrorMessages()
  if (!(validDocument))
  {
    exceptionMessage = formatMessage("style |1 - Errors: |2", STYLEID, documentErrors, ...)
    QappCore.DTTLogMessage(exceptionMessage, ..., DTTError)
  }
  return exceptionMessage
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void QappDataStyle.restoreDefaults()
{
  QappDataStyle foundStyle = null
  for each QappDataStyle qds in QappCore.QappDataStyles
  {
    if (qds.STYLEID == STYLEID)
    {
      foundStyle = qds
      break 
    }
  }
   
  if (!(foundStyle))
    return 
   
  ICONQUALIBUS = foundStyle.ICONQUALIBUS
  ICONQMOBILE = foundStyle.ICONQMOBILE
  COLOR = foundStyle.COLOR
  SEQ = foundStyle.SEQ
   
  this.saveToDB(...)
}


// ──────────────────────────────────

// *******************************************************
// helper method to set all the main parameters of a style
// *******************************************************
public void QappDataStyle.SetData(
  string styleID              // 
  int color                   // 
  string qualibusIconFilename // 
  string qmobileIconFilename  // 
  optional int sequence = 1   // 
)
{
  STYLEID = styleID
  COLOR = color
  QualibusIconFilename = qualibusIconFilename
  QmobileIconFilename = qmobileIconFilename
  SEQ = sequence
}


// ──────────────────────────────────

// ***********************************************************
// Event raised to the document when the transaction is closed
// ***********************************************************
event QappDataStyle.OnEndTransaction()
{
  if (QualibusIconFilename != "")
  {
    boolean qualibusIconIsPng = right(QualibusIconFilename, 3) == "png"
    if (!(qualibusIconIsPng))
    {
      QappCore.DTTLogMessage(formatMessage("Icons must be png, qualibusIconFileName should be like 'name.png' - found "|1", QualibusIconFilename, ...), ..., DTTError)
    }
  }
  if (QmobileIconFilename != "")
  {
    boolean qmobileIconIsPng = right(QmobileIconFilename, 3) == "png"
    if (!(qmobileIconIsPng))
    {
      QappCore.DTTLogMessage(formatMessage("Icons must be png, qmobileIconFileName should be like 'name.png' - found "|1", QmobileIconFilename, ...), ..., DTTError)
    }
  }
   
  this.prepareBlobs()
}


// ──────────────────────────────────

// ************************************************************
// Event raised to the document during the validation procedure
// ************************************************************
event QappDataStyle.OnValidate(
  int:validateReasons Reason // An integer specifying the reason the document is being validated. The most common values are 0 to indicate complete validation before saving, or 1 to indicate a quick validati...
  inout boolean Error        // A boolean output parameter. If set to True, specifies an error condition and stops the validation immediately.
  inout boolean Skip         // A boolean output parameter. If set to True, does not check the rules for automatic validation of the document (see notes).
)
{
  if (deleted)
  {
    return 
  }
   
  if (Reason == Complete)
  {
    string QAppName = QappCore.QappWeb.Name
     
    string leftPartOfStyleId = left(STYLEID, length(QAppName))
     
    if (leftPartOfStyleId != QAppName)
    {
      this.addDocumentError(formatMessage("STYLE_ID '|1' must start with the QAppName (|2)", STYLEID, QAppName, ...))
      Error = true
    }
     
     
     
    if (QappCore.QappWeb.SHOWINQUALIBUSMENU == Yes)
    {
      if (blobSize(ICONQUALIBUS) == 0)
      {
         string additionalErrorInfo = ""
         if (QualibusIconFilename != "")
         {
           additionalErrorInfo = formatMessage("; the file '|1' was not found in the output directory", QualibusIconFilename, ...)
         }
          
         this.addDocumentError(formatMessage("STYLE_ID '|1' must have a Qualibus icon|2.", STYLEID, additionalErrorInfo, ...))
         Error = true
      }
    }
     
    if (QappCore.QappWeb.SHOWINQMOBILEMENU == Yes)
    {
      if (blobSize(ICONQMOBILE) == 0)
      {
         string additionalErrorInfo = ""
         if (QmobileIconFilename != "")
         {
           additionalErrorInfo = formatMessage("; the file '|1' was not found in the output directory", QmobileIconFilename, ...)
         }
         this.addDocumentError(formatMessage("STYLE_ID '|1' must have a Qmobile icon|2.", STYLEID, additionalErrorInfo, ...))
         Error = true
      }
    }
     
  }
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event QappDataStyle.OnInit()
{
  SEQ = 1
  this.initialize()
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event QappData.OnInit()
{
  GUID = docIDToGuid(newDocID())
  ISSCADUTO = No
  IDAPPLICAZIONE = QappCore.QappWeb.ID
  QappCore.DTTLogMessage(formatMessage("Initializing QApp Data: |1", IDQAPPDATA, ...), ..., DTTInfo)
}


// ──────────────────────────────────

// ************************************************************
// Event raised by the document after loading from the database
// ************************************************************
event QappData.AfterLoad(
  boolean AlreadyLoaded     // A boolean parameter that specifies whether the document was previously loaded.
  IDArray LoadedCollections // IDArray type parameter that contains the collections loaded by the framework. If the value of the Already Loaded parameter is False, the Loaded Collections parameter is NULL.
)
{
   
  STARTTIME = Tools.FloatToTime(STARTTIMEFLOAT)
  ENDTIME = Tools.FloatToTime(ENDTIMEFLOAT)
  CLOSURETIME = Tools.FloatToTime(CLOSURETIMEFLOAT)
   
  this.setOriginal()
}


// ──────────────────────────────────



// ──────────────────────────────────

// ****************************************************
// Raised to the document during duplication operations
// ****************************************************
event QappData.OnDuplicate(
  IDDocument SourceDocument // This parameter contains the original document when duplicating the document header. Otherwise, its value is Null Object. This way, it is possible to distinguish the two cases, ...
)
{
  GUID = docIDToGuid(newDocID())
}


// ──────────────────────────────────

// ************************************************************
// Event raised to the document during the validation procedure
// ************************************************************
event QappData.OnValidate(
  int:validateReasons Reason // An integer specifying the reason the document is being validated. The most common values are 0 to indicate complete validation before saving, or 1 to indicate a quick validati...
  inout boolean Error        // A boolean output parameter. If set to True, specifies an error condition and stops the validation immediately.
  inout boolean Skip         // A boolean output parameter. If set to True, does not check the rules for automatic validation of the document (see notes).
)
{
   
  DevTools.ToBeReviewed("write a test")
  if (IDAPPLICAZIONE == null)
  {
    this.setPropertyError("È necessario impostare un id applicazione", IDAPPLICAZIONE)
    Error = true
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static void QappData.deleteAllDataOfGivenStyle(
  string styleId // 
)
{
  IDCollection stylesToBeDeleted of QappData = new()
  select into collection (stylesToBeDeleted)
  from 
    QappData // master table
  where
    STYLEID == styleId
   
  for each QappData qd in stylesToBeDeleted
  {
    qd.deleted = true
    qd.saveToDB(0, ...)
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void QappData.computeBackgroundColor()
{
  int qappDataDxColor = 0
  select into variables (found variable)
    set qappDataDxColor = COLOR
  from 
    NGTQAPPSSTYLES // master table
  where
    STYLEID == STYLEID
   
  BackgroundColor = Misc.dxColorToInde(qappDataDxColor)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean QappCommand.isAfterSaveHook()
{
  boolean isAfterSaveHookCommand = false
  IDMap idm = cast(JSON.parse(CMDPARAMS))
  string:customHooks customHook = idm.getValue("customHook")
  isAfterSaveHookCommand = customHook == AfterSave
   
  return isAfterSaveHookCommand
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event QappCommand.OnInit()
{
  ACTIVE = Yes
  SEQUENCE = 1
  IDDocumentStructure idds = getStructure()
  GUID = idds.GUID
   
   
}


// ──────────────────────────────────

// ***********************************************************
// Event raised to the document when the transaction is closed
// ***********************************************************
event QappCommand.OnEndTransaction()
{
  QappCore.DTTLogMessage("end transaction for " + UrlCommand, ...)
   
  this.computeDefaultCaptions()
   
  this.prepareCmdParams()
   
  this.prepareBlobs()
}


// ──────────────────────────────────

// ************************************************************
// Event raised to the document during the validation procedure
// ************************************************************
event QappCommand.OnValidate(
  int:validateReasons Reason // An integer specifying the reason the document is being validated. The most common values are 0 to indicate complete validation before saving, or 1 to indicate a quick validati...
  inout boolean Error        // A boolean output parameter. If set to True, specifies an error condition and stops the validation immediately.
  inout boolean Skip         // A boolean output parameter. If set to True, does not check the rules for automatic validation of the document (see notes).
)
{
  if (!(this.isNameWellWritten()))
  {
    Error = true
    this.addDocumentError(formatMessage("The command name '|1' contains bad chars", ...))
    return 
  }
   
  if (length(CAPTIONITA) > 100)
  {
    Error = true
    this.addDocumentError("CaptionITA is too long")
    return 
  }
  if (length(CAPTIONENG) > 100)
  {
    Error = true
    this.addDocumentError("CaptionENG is too long")
    return 
  }
   
  // validation of filter
   
  if (FILTER != null)
  {
    if ((KORDID == AltreAnagrafiche) or (KORDID == Eventi))
    {
      boolean FilterHasTheProperFormat = this.filterIsASemiColonSeparatedListOfIntegers()
       
      if (!(FilterHasTheProperFormat))
      {
         Error = true
         this.addDocumentError(formatMessage("Filter '|1' is not a semicolon separated list of integers >= 0, a correct one is '342;12;231'.", FILTER, ...))
      }
    }
    else 
    {
      Error = true
      this.addDocumentError(formatMessage("Filter is not supported in |1.", decode(KORDID, Kordapp), ...))
    }
  }
   
  // validation of icon filenames
   
  if (CommandQualibusIconFilename != "")
  {
    boolean qualibusIconIsPng = right(CommandQualibusIconFilename, 3) == "png"
    if (!(qualibusIconIsPng))
    {
      Error = true
      this.addDocumentError(formatMessage("Icons must be png, CommandQualibusIconFileName should be like 'name.png' - found name "|1"", CommandQualibusIconFilename, ...))
    }
  }
  if (CommandQmobileIconFilename != "")
  {
    boolean qmobileIconIsPng = right(CommandQmobileIconFilename, 3) == "png"
    if (!(qmobileIconIsPng))
    {
      Error = true
      this.addDocumentError(formatMessage("Icons must be png, CommandQmobileIconFileName should be like 'name.png' - found name "|1"", CommandQmobileIconFilename, ...))
    }
  }
   
  // validation of Parameters
   
  if (QappCommandParameters)
  {
    boolean validParameters = QappCommandParameters.validate(Reason, ...)
     
    if (!(validParameters))
    {
      QappCore.DTTLogMessage("QappCommandParameters Object Has Errors", ..., DTTError)
    }
  }
   
  // validation of Needed Privilege
   
  if (NEEDEDPRIVILEGE != NeededPrivilegeNone and NEEDEDPRIVILEGE != NeededPrivilegePers1 and NEEDEDPRIVILEGE != NeededPrivilegePers2 and NEEDEDPRIVILEGE != NeededPrivilegePers3 and NEEDEDPRIVILEGE !=
        NeededPrivilegePers4 and NEEDEDPRIVILEGE != NeededPrivilegePers5)
  {
    Error = true
    this.addDocumentError("NeededPrivilege must have one of the supported values")
     
  }
}


// ──────────────────────────────────

// ************************************************************
// Event raised by the document after loading from the database
// ************************************************************
event QappCommand.AfterLoad(
  boolean AlreadyLoaded     // A boolean parameter that specifies whether the document was previously loaded.
  IDArray LoadedCollections // IDArray type parameter that contains the collections loaded by the framework. If the value of the Already Loaded parameter is False, the Loaded Collections parameter is NULL.
)
{
   
  // commandClassName is set so later it can be used
  string retrieveClassName = this.retrieveClassNamefromCMDPARAMS()
  this.setCommandClassName(retrieveClassName)
  this.setOriginal()
}


// ──────────────────────────────────

// *******************************************************
// extend this method to set the main command data:
// 1) call setCommandMainData by passing the proper valeus
// 2) prepare a QappCommandParameters object
// *******************************************************
public void QappCommand.initialize()
{
  QappCore.DTTLogMessage("Abstract Error", ..., DTTError)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void QappCommand.execute()
{
  QappCore.DTTLogMessage("Abstract error", ..., DTTError)
}


// ──────────────────────────────────

// *******************************************************************************************************
// method to be extended to compute a message to be shown to the user before the async command is executed
// the method must compute a string and set it in the inout parameter so it will be shown to the user
// it makes sense only for async commands as the name suggests
// *******************************************************************************************************
public void QappCommand.BeforeAsyncExecute(
  inout string message // 
)
{
  QappCore.DTTLogMessage("method that can be optionially extended but it is not an error not to extend it, it is used only in async commands", ..., DTTInfo)
}


// ──────────────────────────────────

// ************************************************************************************
// this method will be called only if qappCommandParameter.callShouldBeExecuted is true
// ************************************************************************************
public boolean QappCommand.shouldBeExecuted()
{
  return true
}


// ──────────────────────────────────

// ***************************************************************************************************************************************************************
// this method should be extended, only when there is the need to decide at runtime whether a QappCommand should be inserted in the Database or not
// 
// the tipical example is:
// a Qapp is shared across more customers and a specific command deals only with a customer, so the method returns true for that customer and false for the others
// 
// if true is retruned the QappCommand is inserted in the database (true is the default)
// 
// This method should not be extended in a normal case
// ***************************************************************************************************************************************************************
public boolean QappCommand.shouldBeConsidered()
{
  return true
}


// ──────────────────────────────────

// ****************************************************************************************************************************************************************************************************************
// This method is deprecated, it is kept only for unit testing and should not be used, to create a command use the qappcommandhandler.createCommand method
// 
// static constructor of a QApp Command, it does most of the heavy lifting related to creation, just mind these parameters: 
// - commandGUID: a unique GUID generated once only from the Qapp developer
// - urlCommand: will be turned to uppercase so consider using always uppercase commands
// - qualibusIconFileName (and qmobileIconFileName): it must be a png and be in the form 'name.png', the file with the same name must be in the custom\QAppIcons\ folder o the QApp
// - Filter is optional and must be in the form "1;2" (e.g to publish the command for Infrastructure (=1) and Instruments (=2) only) - iy it id used also for eventi to haave comma separated list of ID_CLASSE
// - Execution Mode tells how the command is run
// - Require confirmation must set to true if user is expected to confirm
// NOTE: it is meant to be used in the implementation of a class who extends QappCommandHandler, in the extended method setQappMainData, so the created QappCommand must be added to the public coomponent's "Qapp
// Commands" collection
// ****************************************************************************************************************************************************************************************************************
public static QappCommand QappCommand.CreateDeprecatedBroken(
  string commandGUID                                   // 
  string captionITA                                    // 
  string captionENG                                    // 
  int:kordapp kordApp                                  // 
  string:qappCommandDestinations destination           // 
  string urlCommand                                    // 
  string qualibusIconFilename                          // 
  string qmobileIconFilename                           // 
  optional boolean showInTools = 0                     // 
  optional string:qappCommandExecutionModes executionMode = "B" // 
  optional boolean requireConfirmation = 0             // 
  optional string:neededPrivilegeTypes neededPrivilege = "N" // 
  optional int sequence = 1                            // 
  optional string Filter = ""                          // 
  optional QappCommandParameters qappCommandParameters // 
)
{
  QappCommand qc = new()
   
  qc.init()
   
   
  qc.GUID = commandGUID
  qc.CAPTIONITA = captionITA
  qc.CAPTIONENG = captionENG
  qc.KORDID = kordApp
  qc.UrlCommand = upper(urlCommand)
  qc.SEQUENCE = sequence
  qc.FILTER = Filter
  qc.CommandQualibusIconFilename = qualibusIconFilename
  qc.CommandQmobileIconFilename = qmobileIconFilename
  qc.NEEDEDPRIVILEGE = neededPrivilege
  if (qappCommandParameters)
  {
    qc.setQAppCommandParameters(qappCommandParameters)
  }
  qc.endTransaction()
   
  return qc
}


// ──────────────────────────────────



// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void QappCommand.setIconsFullpath()
{
  string basePath = QappCore.QappWeb.getIconsBasePath()
   
  CommandQualibusIconFullpath = basePath + CommandQualibusIconFilename
  CommandQmobileIconFullpath = basePath + CommandQmobileIconFilename
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void QappCommand.restoreQappCommandDefaultData()
{
   
  // set again each property value, including blobs
   
  QappCommand foundCommand = null
  IDCollection allCommands of QappCommand = getQappCommands()
  for each QappCommand qc in allCommands
  {
    if (qc.GUID == GUID)
    {
      foundCommand = qc
      break 
    }
  }
   
  if (!(foundCommand))
  {
    return 
  }
   
  CAPTIONITA = foundCommand.CAPTIONITA
  CAPTIONENG = foundCommand.CAPTIONENG
  ICON = foundCommand.ICON
  ICONQMOBILE = foundCommand.ICONQMOBILE
  SEQUENCE = foundCommand.SEQUENCE
  ACTIVE = foundCommand.ACTIVE
   
  KORDID = foundCommand.KORDID
  UrlCommand = foundCommand.UrlCommand
  FILTER = foundCommand.FILTER
  NEEDEDPRIVILEGE = foundCommand.NEEDEDPRIVILEGE
   
  if (foundCommand.CMDPARAMS != "")
  {
    CMDPARAMS = foundCommand.CMDPARAMS
    QappCommandParameters qcp = foundCommand.getQAppCommandParameters()
    this.setQAppCommandParameters(qcp)
  }
  FORCEUPDATE = No
   
  this.saveToDB(...)
}


// ──────────────────────────────────

// ********************************************************************
// Check if Filter is in the format 324;23;23 excluding all other cases
// ********************************************************************
private boolean QappCommand.filterIsASemiColonSeparatedListOfIntegers()
{
  return Tools.ValidCharSeparatedListOfPositiveIntegers(FILTER, ...)
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private string QappCommand.getErrorMessages()
{
  string errors = ""
  Collection c = new()
  this.getDocumentErrors(c)
  int i = 0
  while (i < c.count())
  {
    errors = Tools.Concatenate(errors, c.getString(i), " - ")
    i = i + 1
  }
  return errors
}


// ──────────────────────────────────

// **************************************************************************************************
// validates the documetn and gives an DTTError useful for the Qapp creator, errorMessage is returned
// **************************************************************************************************
public string QappCommand.generateReadableExceptions()
{
  string exceptionMessage = ""
   
  boolean validDocument = validate(...)
  string documentErrors = DOHelper.getDocumentErrorsAsString(this, ...)
  if (!(validDocument))
  {
    exceptionMessage = formatMessage("Command |1 - Errors: |2", UrlCommand, documentErrors, ...)
    QappCore.DTTLogMessage(exceptionMessage, ..., DTTError)
  }
  return exceptionMessage
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void QappCommand.setQAppCommandParameters(
  QappCommandParameters qappCommandParameters // 
)
{
  QappCommandParameters = qappCommandParameters
  QappCommandParameters.endTransaction()
  QappCommandParameters.setObjectTag("parentCommand", this)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public QappCommandParameters QappCommand.getQAppCommandParameters()
{
   
  // to make sure Parameters exist we initialize the command, initializing does not harm and it makes sure the parameters are ready
  if (!(QappCommandParameters))
  {
    this.performInit()
  }
   
  return QappCommandParameters
}


// ──────────────────────────────────

// ***************************************************
// The CMD_PARAMS field is populated with json content
// ***************************************************
private void QappCommand.prepareCmdParams()
{
  if (QappCommandParameters)
  {
    // passing false makes each property to be written into the json even the null ones
    string jsonParams = QappCommandParameters.toJson(...)
    CMDPARAMS = jsonParams
  }
}


// ──────────────────────────────────

// ****************************************************************************************************************************************************************************************************************
// THis method is tipiacally called in the extended initialize of QappCommand, it is anhelper method to set all the main parameters of a command, just mind these parameters: 
// - QappCOmmandParameters: here the developer must pass an object that he just created
// - qualibusIconFileName (and qmobileIconFileName): it must be a png and be in the form 'name.png', the file with the same name must be in the custom\QAppIcons\ folder o the QApp, it could be null for silent or
// CustomHook commadns
// - Filter is optional and must be in the form "1;2" (e.g to publish the command for Infrastructure (=1) and Instruments (=2) only) - it is used also for eventi to have a semicolon separated list of ID_CLASSE
// - needed privilege is to limit the execution of command to users with a specific pers on Qapp
// - Active to change the active status (a non active command won't be executed, it makes sense to create some commands non active by default)
// ****************************************************************************************************************************************************************************************************************
public void QappCommand.SetData(
  QappCommandParameters qappCommandParameters // 
  int:kordapp kordApp                         // 
  string captionITA                           // 
  string captionENG                           // 
  string qualibusIconFilename                 // 
  string qmobileIconFilename                  // 
  optional string:neededPrivilegeTypes neededPrivilege = "N" // 
  optional int sequence = 1                   // 
  optional string Filter = ""                 // 
  optional boolean active = -1                // default Active value (it can be used to unactivate by default a command)
)
{
  CAPTIONITA = captionITA
  CAPTIONENG = captionENG
  KORDID = kordApp
  UrlCommand = typeName()
  SEQUENCE = sequence
  FILTER = Filter
  CommandQualibusIconFilename = qualibusIconFilename
  CommandQmobileIconFilename = qmobileIconFilename
  NEEDEDPRIVILEGE = neededPrivilege
  ACTIVE = if(active, Yes, No)
  if (qappCommandParameters)
  {
    this.setQAppCommandParameters(qappCommandParameters)
  }
}


// ──────────────────────────────────

// *******************************************************************
// if captions are not set they are filled with the command class name
// *******************************************************************
private void QappCommand.computeDefaultCaptions()
{
  if (CAPTIONITA == "")
  {
    CAPTIONITA = typeName()
  }
  if (CAPTIONENG == "")
  {
    CAPTIONENG = typeName()
  }
}


// ──────────────────────────────────

// ********************************************************************************************************************************************
// passes the passed value to the caller app (either Qualibus or QMobile), it makes sense only for commands with silent = true, key is optional
// ********************************************************************************************************************************************
public void QappCommand.setSilentResponse(
  string value                    // the value to be set in the json response
  optional string key = "message" // the key used for the value in the json response
)
{
  QappCommandParameters qcp = this.getQAppCommandParameters()
  if (qcp)
  {
    string:customHooks customHook = qcp.getCustomHook()
     
    boolean isSilentBeforeLoadLinks = qcp.Silent and customHook == BeforeLoadLinks
     
    if (qcp.Async or qcp.CallShouldBeExecuted or !(isSilentBeforeLoadLinks))
    {
       
      // in this case it is ok to use showMessageInCaller
       
    }
    else 
    {
      QappCore.DTTLogMessage("setSilentResponse can be used only in an async command or if command is Silent and customHook is not BeforeLoadLinks!", ..., DTTError)
      return 
    }
     
  }
   
  QappCore.QappCommandHandler.setSilentResponse(value, key, "17CFAE75-791D-4FD6-B23C-24BB9C04AF75")
}


// ──────────────────────────────────

// **********************************************************************************
// if the command is about a cruscotto this method inserts a Cruscotto in Qualibus DB
// **********************************************************************************
public void QappCommand.insertCruscottoInDatabaseIfNeeded()
{
  QappCommandParameters qcp = this.getQAppCommandParameters()
   
  if (qcp.Destination != Cruscotto)
    return 
   
  // return if auto insertion is off
  if (!(qcp.getInsertCruscottoAutomatically()))
    return 
   
  // if reached up to here we are in a Cruscotto Command
  string title = qcp.getCruscottoTitle()
  string description = qcp.getCruscottoDescription()
  string qappwebName = if(qcp.getCruscottoGroup() != "", qcp.getCruscottoGroup(), QappCore.QappWeb.Name)
   
  int idCommandInDatabase = this.getIdOfCommandStoredInDatabase()
   
  int vCount = 0
  select into variables (found variable)
    set vCount = count(...)
  from 
    CRUQUERY // master table
  where
    IDQAPPCOMMAND = idCommandInDatabase
    TIPOTYPE == Qapp
   
  boolean cruscottoAlreadyExists = (vCount > 0)
   
  if (!(cruscottoAlreadyExists))
  {
    boolean isCompanyDashboard = qcp.getIsCompanyDashboard()
    Cruscotto c = Cruscotto.create(title, description, qappwebName, Qapp, isCompanyDashboard)
    c.IDQAPPCOMMAND = idCommandInDatabase
    c.saveToDB(...)
  }
   
}


// ──────────────────────────────────

// ********************************************************************************************************************
// returns the NGT_QAPPS_COMMANDS.ID_QAPP_COMMNAND of the Command stored in the DB, returns null if no command is found
// ********************************************************************************************************************
public int QappCommand.getIdOfCommandStoredInDatabase()
{
  int vIDQAPPCOMMAND = 0
  select into variables (found variable)
    set vIDQAPPCOMMAND = IDQAPPCOMMAND
  from 
    NGTQAPPSCOMMANDS // master table
  where
    GUID == GUID
   
  return vIDQAPPCOMMAND
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void QappCommand.performClosePopupForm()
{
  this.performInit()
   
  QappRuntimeBehavior qrb = getQappRuntimeBehavior()
   
  boolean supportEnabled = qrb.SupportInteractionWithQualibusForms
   
  if (!(supportEnabled))
  {
    QappCore.DTTLogMessage("supportInteractionWithQualibusForms is not enabled!", ..., DTTError)
    return 
  }
   
  if (CanCloseForm)
  {
    string callCloseAppJs = "closePopup()"
     
    QappCore.executeOnClient(callCloseAppJs)
    CloseQappTimer.enabled = true
  }
  else 
  {
    QappCore.DTTLogMessage(formatMessage("Command |1 cannot close popup forms", this.typeName(), ...), ..., DTTError)
  }
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void QappCommand.performOpenModule(
  int:kordapp kordApp // 
  int mainId          // 
)
{
  this.performInit()
   
  QappRuntimeBehavior qrb = getQappRuntimeBehavior()
   
  boolean supportEnabled = qrb.SupportInteractionWithQualibusForms
   
   
  if (!(supportEnabled))
  {
    QappCore.DTTLogMessage("supportInteractionWithQualibusForms is not enabled!", ..., DTTError)
    return 
  }
   
  if (CanOpenModule)
  {
    string openModuleJs = formatMessage("openModule(|1,|2)", kordApp, mainId, ...)
     
    QappCore.DTTLogMessage("JS executed to communicate with Qualibus forms: " + openModuleJs, ...)
     
     
    QappCore.executeOnClient(openModuleJs)
  }
}


// ──────────────────────────────────

// **************************************************************************************************
// performInit initializes the command in the best possible way, neve call initialize but performInit
// **************************************************************************************************
public void QappCommand.performInit()
{
  this.initializeReportsFolder()
   
  this.initialize()
   
  QappCommandParameters qcp = this.getQAppCommandParameters()
   
  if (qcp != null)
  {
    CanCloseForm = qcp.Popup
    CanCloseConfigForm = qcp.ConfigFormDefined
     
    CanOpenModule = (qcp.Destination == PaginaDiTabControl) or (qcp.Destination == Cruscotto) or (CanCloseForm)
  }
}


// ──────────────────────────────────

// **************************************************************************************************
// performExecute executes the command in the best possible way, neve call execute but performExecute
// **************************************************************************************************
public void QappCommand.performExecution()
{
  QappCore.QappCommandHandler.SessionQappCommand = this
   
  QappCommandParameters qcp = this.getQAppCommandParameters()
   
  if (!(qcp.ConfigFormDefined))
  {
    this.execute()
  }
  else 
  {
    this.showConfigForm()
  }
}


// ──────────────────────────────────

// ********************************************************************************
// returns true if the command class name does not contain accents or strange chars
// ********************************************************************************
private boolean QappCommand.isNameWellWritten()
{
  // confronto il nome classe con la sua versione ripulita per capire se ci sono degli accenti
  string originalClassName = typeName()
  string cleanedClassName = SH.RemoveBadFileNameChars(originalClassName)
   
  boolean nameWellWritten = originalClassName == cleanedClassName
   
  return nameWellWritten
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void QappCommand.performOpenBrowser(
  string url // 
)
{
  this.performInit()
   
   
  QappRuntimeBehavior qrb = getQappRuntimeBehavior()
   
  boolean supportEnabled = qrb.SupportInteractionWithQualibusForms
   
   
  if (!(supportEnabled))
  {
    QappCore.DTTLogMessage("supportInteractionWithQualibusForms is not enabled!", ..., DTTError)
    return 
  }
   
  string openBrowserJs = formatMessage("openBrowser('|1')", url, ...)
   
  QappCore.DTTLogMessage("JS executed to communicate with Qualibus forms: " + openBrowserJs, ...)
   
  QappCore.executeOnClient(openBrowserJs)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string QappCommand.getCommandUrl(
  string mainId                     // 
  string token                      // 
  string:commandExecutionModes mode // 
)
{
   
  QappWeb qw = QappWeb.get(QappWebID)
  string extraPArt = SH.rightUpToDelimiter(qw.WEBURL, "/", ...)
  string baseURlEndingInSlash = replace(qw.WEBURL, extraPArt, "")
   
  string commandName = this.getURlCommandFromCmdParamsJson()
   
  string completeCommandUrl = formatMessage("|1QappCore/QappCommandHandler?command=|2&mainId=|3&token=|4&mode=|5", baseURlEndingInSlash, commandName, mainId, token, mode)
  return completeCommandUrl
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string QappCommand.getURlCommandFromCmdParamsJson()
{
  IDMap idm = cast(JSON.parse(CMDPARAMS))
  string urlCommand = idm.getValue("urlCommand")
  return urlCommand
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public IDForm QappCommand.retrieveSpecificConfigForm()
{
   
  string libraryName = QappCore.QappCommandHandler.getLibraryFileName(...)
   
  string commandName = CommandClassName
   
  string expectedCongigFormName = this.getExpectedConfigFormName()
   
  IDForm retrievedForm = QappCore.createFormFromLibrary(libraryName, expectedCongigFormName)
   
  if (retrievedForm != null)
  {
    QappCore.DTTLogMessage(formatMessage("ConfigForm found for Qapp Command |1", commandName, ...), ..., DTTInfo)
  }
  else 
  {
    QappCore.DTTLogMessage(formatMessage("ConfigForm NOT found for Qapp Command |1", commandName, ...), ..., DTTWarning)
  }
   
  return retrievedForm
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string QappCommand.getExpectedConfigFormName()
{
  return CommandClassName + "Form"
}


// ──────────────────────────────────

// *************************************
// className is extracted from CMDPARAMS
// *************************************
public string QappCommand.retrieveClassNamefromCMDPARAMS()
{
  IDMap idm = cast(JSON.parse(CMDPARAMS))
  string commandClassName = idm.getValue("urlCommand")
  return commandClassName
}


// ──────────────────────────────────

// *******************************************************************************************************************
// setter of commandClassName: since this property is delicate, better keep it private so accessing is more controlled
// *******************************************************************************************************************
public void QappCommand.setCommandClassName(
  string value // 
)
{
  CommandClassName = value
}


// ──────────────────────────────────



// ──────────────────────────────────

// ****************************************************************************
// centralized access to the reports folder name (in case we want to rename it)
// ****************************************************************************
private static string QappCommand.getReportsFolderName()
{
  return "Reports"
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private static string QappCommand.getReportsFolderPath()
{
  return QappCore.path() + FH.getSeparator() + this.getReportsFolderName()
}


// ──────────────────────────────────

// *********************************************************
// it returns http://BASEURL/Reports (ending without slash!)
// *********************************************************
private static string QappCommand.getUrlOfReportsFolder()
{
  string baseurl = X.GetApplicationUrl(false, true, ...)
  string reportsFolderUrl = baseurl + this.getReportsFolderName()
   
  return reportsFolderUrl
}


// ──────────────────────────────────

// ***************************************
// /Reports folder is created if not found
// ***************************************
private void QappCommand.initializeReportsFolder()
{
  string expectedReportsFolder = this.getReportsFolderPath()
  boolean folderExists = directoryExists(expectedReportsFolder)
  if (!(folderExists))
  {
    QappCore.makeDirectory(expectedReportsFolder)
  }
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void QappCommand.setConfigurationMap(
  IDMap configurationMap // 
)
{
  ConfigurationMap = configurationMap
}


// ──────────────────────────────────

// *******************************************************************************************
// pass the file path of the generated file
// 
// this method will turn it into a eoncoded url and pass it to caller (API or browser session)
// *******************************************************************************************
public void QappCommand.sendDocumentToCaller(
  string filepath // 
)
{
  string computetReportUrl = this.prepareValidReportDownloadUrlFromFilePath(filepath)
  DownloadUrl = computetReportUrl
   
  QappCommandParameters qcp = this.getQAppCommandParameters()
   
  string actualFileExtension = SH.rightUpToDelimiter(DownloadUrl, ".", ...)
  string espectedFileExtension = qcp.ReportFileExtension
  boolean fileExtensionMatchesWithTheExpectedOne = actualFileExtension == espectedFileExtension
   
  if (!(fileExtensionMatchesWithTheExpectedOne))
    QappCore.DTTLogMessage(formatMessage("The report being prepared has '|1' extension but the expected one is '|2'. This is a non blocking error, but please check the extensions.", actualFileExtension, qcp.
          ReportFileExtension, ...), ..., DTTError)
   
  if (qcp.ConfigFormDefined)
  {
     
    // handle Cconfig Form / Browser session path
     
  }
  else 
  {
     
    // handle pure API call
    this.setSilentResponse(DownloadUrl, "downloadUrl")
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void QappCommand.showConfigForm()
{
  IDForm retrievedConfigForm = this.retrieveSpecificConfigForm()
  if (retrievedConfigForm)
  {
    retrievedConfigForm.show(...)
    retrievedConfigForm.hasCloseButton = false
    QappCore.widgetMode = true
  }
  else 
  {
    QappCore.DTTLogMessage("Impossible to show The Config form since it was not found in the library. Make sure a form whose name is QappCommand name + 'Form' exists in the Qapp main component", ..., DTTError)
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void QappCommand.performCloseConfigForm()
{
  this.performInit()
   
  QappRuntimeBehavior qrb = getQappRuntimeBehavior()
   
  boolean supportEnabled = qrb.SupportInteractionWithQualibusForms
   
  if (!(supportEnabled))
  {
    QappCore.DTTLogMessage("supportInteractionWithQualibusForms is not enabled!", ..., DTTError)
    return 
  }
   
  if (CanCloseConfigForm)
  {
     
    // calling execute will make the downloadUrl to be populated
    this.execute()
     
    // so we retrieve it and passed to the js method
    DevTools.ToBeReviewed("once Delphi won't be used this will be done differently!")
     
    // change this flag to move between "write console msg delphi ready" or not
    // when false it is production code, when true it is to see in the browser console the message that in the production case it is sent to delphi
    boolean testMode = false
     
    string jsCode = if(testMode, "closeConfigFormConsoleLog("|1")", "closeConfigForm("|1")")
     
    // and pass it to the javascript function that will allow us to send it back to Delphi
    string callCloseConfigJs = formatMessage(jsCode, DownloadUrl, ...)
     
    QappCore.executeOnClient(callCloseConfigJs)
    CloseQappTimer.enabled = !(testMode)
  }
  else 
  {
    QappCore.DTTLogMessage(formatMessage("Command |1 cannot close config forms", this.typeName(), ...), ..., DTTError)
  }
   
}


// ──────────────────────────────────

// ****************************************************************************
// helper method that encapsulate:
// 1. moving the passed file to the Reports folder, renaming the file to a GUID
// 2. computes a valid url to download the file from
// 3. return the url
// ****************************************************************************
private string QappCommand.prepareValidReportDownloadUrlFromFilePath(
  string filepath // 
)
{
  // PUT GUID FOR FILENAME
  string localPAth = filepath
  string fileExtension = SH.rightUpToDelimiter(localPAth, ".", ...)
  string newUniqueFileName = docIDToGuid(newDocID()) + "." + fileExtension
  string newFileLocation = this.getReportsFolderPath() + FH.getSeparator() + newUniqueFileName
  QappCore.copyFile(filepath, newFileLocation)
  QappCore.deleteFile(filepath)
   
  string ReportUrl = this.getUrlOfReportsFolder()
  string goodurl = formatMessage("|1/|2", ReportUrl, newUniqueFileName, ...)
  return goodurl
}


// ──────────────────────────────────

// *****************************************************
// all files in the Reports folder are deleted (cleaning
// *****************************************************
public static void QappCommand.deleteAllFilesInReportsFolder()
{
  string reportsFodler = this.getReportsFolderPath()
  FolderHandler.CleanFolder(reportsFodler, 0)
}


// ──────────────────────────────────

// ******************************************
// returns the FILTER as an array of integers
// ******************************************
public IDArray QappCommand.getFilterAsArrayOfIntegers()
{
  boolean validFilter = this.filterIsASemiColonSeparatedListOfIntegers()
   
  IDArray ida = new()
   
  if (validFilter)
  {
    StringTokenizer st = new()
    st.setString(FILTER, ";", ...)
    while (st.hasNextToken())
    {
      string currentToken = st.nextToken()
      if (isNumber(currentToken))
      {
         int tokenAsInt = toInteger(currentToken)
         ida.addValue(tokenAsInt)
      }
    }
  }
  else 
  {
    ida.clear()
  }
   
  return ida
}


// ──────────────────────────────────

// *******************************************************************************************************************
// checks if the passed value is allowee in FILTER: ok  either there is no filter or the value is in the filter string
// *******************************************************************************************************************
public boolean QappCommand.isValueAllowedInFilter(
  int value // 
)
{
  IDArray ida = this.getFilterAsArrayOfIntegers()
   
  boolean valueContainedInFilter = ida.findValue(value, ...) != -1
   
  boolean filterIsEmpty = FILTER == ""
   
  boolean passedValueIsAllowed = (valueContainedInFilter and !(filterIsEmpty)) or filterIsEmpty
   
  return passedValueIsAllowed
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static QappCommandParameters QappCommand.getQappCommandParametersGivenCommandName(
  string commandName // 
)
{
  QappCommand qc = QappCore.QappCommandHandler.CommandFactory(commandName, null, ...)
  qc.initialize()
   
  QappCommandParameters qcp = qc.getQAppCommandParameters()
  return qcp
}


// ──────────────────────────────────

// ************************************************************************************************
// checks if the command has a GUID not yet used in the DB
// 
// note: if this occurs it would be better to change the command GUID by duplicating the inde class
// ************************************************************************************************
public boolean QappCommand.isGUIDReallyUnique()
{
  boolean sameGuidExistingInAnotherCommand = false
  if (QappWebID > 0)
  {
    int vCount = 0
    select into variables (found variable)
      set vCount = count(...)
    from 
      NGTQAPPSCOMMANDS // master table
    where
      IDAPPLICAZIONE != QappWebID
      GUID == GUID
     
    sameGuidExistingInAnotherCommand = vCount > 1
  }
  else 
  {
    QappCore.DTTLogMessage("cannot use isGUIDReallyUnique if QappWebID is unknown", ..., DTTError)
  }
   
  boolean isCommandGuidUnique = !(sameGuidExistingInAnotherCommand)
  return isCommandGuidUnique
}


// ──────────────────────────────────

// *************************************************************************************************************
// from all the object properties a json file is returned
// 
// {
// prop1="value1"
// prop2="value2"
// }
// 
// styles being at the moment of writing the only object property is handled separately at the end of the method
// *************************************************************************************************************
public string QappCommandParameters.toJson(
  optional boolean uppercasePropertyNames = 0 // 
)
{
  this.computeUrlCommand()
   
  IDMap properties = new()
   
  IDDocumentStructure idds = getStructure()
   
  int propertyCount = idds.getPropertyCount()
  for (int i = 1; i <= propertyCount; i = i + 1)
  {
    IDPropertyDefinition idpd = idds.getPropertyDefinition(i)
    string propertyName = idpd.UIName
     
    if (uppercasePropertyNames)
      propertyName = upper(propertyName)
     
    string propertyValue = getProperty(i)
     
    // make sure the null value takes a good default
    if (isNull(propertyValue))
    {
      if (idpd.dataType == Boolean or idpd.dataType == Integer or idpd.dataType == Float)
      {
         propertyValue = "0"
      }
      else if (idpd.dataType == Character)
      {
         propertyValue = ""
      }
       
    }
     
    // finally add the value in the json
    if (idpd.dataType == Boolean)
    {
      boolean booleanValue = toInteger(propertyValue)
      properties.setBoolean(propertyName, booleanValue)
    }
    else 
    {
      properties.setValue(propertyName, propertyValue)
    }
  }
   
  // styles array is added only when it contains data
  IDArray definedIDStyles = this.getStylesArray(true)
  if (definedIDStyles.length() > 0)
    properties.setObject("styles", definedIDStyles)
   
   
  // being customHook private it is not handled in the loop above, we add customHook only when destination == customHook, for clarity
  if (Destination == CustomHook)
  {
    properties.setValue("customHook", CustomHook)
  }
   
  string json = JSON.stringify(properties)
   
  return json
}


// ──────────────────────────────────

// ******************************************************************
// returns the QappCommand to which the parameters object is assigned
// ******************************************************************
public QappCommand QappCommandParameters.getParentCommand()
{
  QappCommand qc = getObjectTag("parentCommand")
  return qc
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void QappCommandParameters.computeUrlCommand()
{
  QappCommand qc = this.getParentCommand()
   
  UrlCommand = qc.UrlCommand
}


// ──────────────────────────────────

// ****************************************************************************************************************************
// assign a styleId to the command so that the command will be available in the popup for the passed style (passed as style id)
// 
// note: this makes sense and it is valid only for commands with destination RigaDiCollegamenti and ImpergnoDiCalendar!
// 
// ****************************************************************************************************************************
public void QappCommandParameters.addStyleId(
  string styleId // 
)
{
  Styles.addValue(styleId)
}


// ──────────────────────────────────

// ************************************************************************************************************************************************************
// returns an array containing the styles being set for the QappCommandParameters object
// 
// since the array might be used to create a JSON object there are two ways to compute the array
// 
// 1) useIDMapAsElement = false: in this case only strings are added to the array and the final JSON will contain "styles":["style1","style2"]
// 
// 2) useIDMapAsElement = true: in this case IDMaps are added to the array and the final JSON will contain "styles":["styleName":"style1","styleName":"style2"]
// 
// Note: initially only (1) was available but since with Delphi it is simpler to parse (2), it was added.
// ************************************************************************************************************************************************************
private IDArray QappCommandParameters.getStylesArray(
  optional boolean useIdMapAsElement = 0 // 
)
{
  IDArray stylesArray = new()
   
  if (useIdMapAsElement)
  {
    for (int i = 0; i < Styles.length(); i = i + 1)
    {
      string currentStyleName = Styles.getValue(i)
      IDMap currentStyleMap = new()
      currentStyleMap.setValue("styleName", currentStyleName)
      stylesArray.addObject(currentStyleMap)
    }
  }
  else 
  {
    stylesArray = Styles
  }
   
  return stylesArray
}


// ──────────────────────────────────

// *************************************************************************************************************************************************************************************
// sets a customHook, when using this method there is no need to set the Destination to customHook, since this is handled automatically (in onEndTransaction, that is called internally)
// *************************************************************************************************************************************************************************************
public void QappCommandParameters.setCustomHook(
  string:customHooks customHook // 
)
{
  CustomHook = customHook
}


// ──────────────────────────────────

// ********************
// reads the customHook
// ********************
public string QappCommandParameters.getCustomHook()
{
  return CustomHook
}


// ──────────────────────────────────

// ****************************************************************************************************
// call this in the commmand initialize to make the qapp insert the cruscotto in Qualibus automatically
// ****************************************************************************************************
public void QappCommandParameters.setupCruscottoForAutoInsertion(
  string cruscottoTitle               // 
  string cruscottoDescription         // 
  boolean visibleToAll                // 
  optional string cruscottoGroup = "" // if not set the group will be the Qapp name
)
{
   
  InsertCruscottoAutomatically = true
  CruscottoTitle = cruscottoTitle
  CruscottoDescription = cruscottoDescription
  CompanyDashoboard = visibleToAll
  CruscottoGroup = cruscottoGroup
}


// ──────────────────────────────────

// **************************************************************************
// getter for the private property that needs to be read outside of the class
// **************************************************************************
public boolean QappCommandParameters.getIsCompanyDashboard()
{
  return CompanyDashoboard
}


// ──────────────────────────────────

// *******************************************************************************
// only getter for the private property that needs to be read outside of the class
// *******************************************************************************
public boolean QappCommandParameters.getInsertCruscottoAutomatically()
{
  return InsertCruscottoAutomatically
}


// ──────────────────────────────────

// **************************************************************************
// getter for the private property that needs to be read outside of the class
// **************************************************************************
public string QappCommandParameters.getCruscottoDescription()
{
  return CruscottoDescription
}


// ──────────────────────────────────

// **************************************************************************
// getter for the private property that needs to be read outside of the class
// **************************************************************************
public string QappCommandParameters.getCruscottoGroup()
{
  return CruscottoGroup
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void QappCommandParameters.initializeParentQappCommand()
{
  // onValidate attaches the document errors directly on the parent Command, we popuplate the dedicated property so it can be accessed in all sub methods
  ParentQappCommand = this.getParentCommand()
   
  // "just in case" assertion
  if (!(ParentQappCommand))
  {
    QappCore.DTTLogMessage("parentCommand not found, this is an unexpected situation, plaese contact the developers", ..., DTTError)
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void QappCommandParameters.validateMandatoryDestination(
  inout boolean Error // 
)
{
  if (Destination == "")
  {
    Error = true
    ParentQappCommand.addDocumentError("Destination must be set in the parameters!")
     
    // if destination is not set we stop validation since we already have a major error
    return 
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void QappCommandParameters.validateCorrectDestinationAndKordPair(
  inout boolean Error // 
)
{
  boolean badCombinationOfKordAndDestination = false
  string additionalErrorMessage = ""
   
  switch (Destination)
  {
    case ToolbarDiScheda:
      if ((ParentQappCommand.KORDID == Articoli) or (ParentQappCommand.KORDID == AltreAnagrafiche) or (ParentQappCommand.KORDID == ClientiFornitori) or (ParentQappCommand.KORDID == Documenti) or (
             ParentQappCommand.KORDID == Eventi) or (ParentQappCommand.KORDID == Progetti) or (ParentQappCommand.KORDID == Privati) or (ParentQappCommand.KORDID == Funzioni) or (ParentQappCommand.KORDID ==
             Interventi) or (ParentQappCommand.KORDID == Personale) or (ParentQappCommand.KORDID == Calendar))
      {
         badCombinationOfKordAndDestination = false
      }
      else 
      {
         badCombinationOfKordAndDestination = true
      }
    break
    case ToolbarDiAlbero:
      if ((ParentQappCommand.KORDID == Progetti) or (ParentQappCommand.KORDID == AltreAnagrafiche))
      {
         badCombinationOfKordAndDestination = false
      }
      else 
      {
         badCombinationOfKordAndDestination = true
      }
    break
    case RigaDiCollegamenti:
      if ((ParentQappCommand.KORDID == Articoli) or (ParentQappCommand.KORDID == AltreAnagrafiche) or (ParentQappCommand.KORDID == ClientiFornitori) or (ParentQappCommand.KORDID == Progetti) or (
             ParentQappCommand.KORDID == Privati) or (ParentQappCommand.KORDID == Funzioni) or (ParentQappCommand.KORDID == Interventi) or (ParentQappCommand.KORDID == Personale) or (ParentQappCommand.KORDID
             == Eventi))
      {
         badCombinationOfKordAndDestination = false
      }
      else 
      {
         badCombinationOfKordAndDestination = true
      }
    break
    case ImpegnoCalendar:
      if (ParentQappCommand.KORDID == Calendar)
      {
         badCombinationOfKordAndDestination = false
      }
      else 
      {
         badCombinationOfKordAndDestination = true
      }
       
    break
    case PaginaDiTabControl:
      DevTools.ToBeReviewed("For eventi and anais we should support filter too")
       
      if ((ParentQappCommand.KORDID == Articoli) or (ParentQappCommand.KORDID == AltreAnagrafiche) or (ParentQappCommand.KORDID == ClientiFornitori) or (ParentQappCommand.KORDID == Eventi) or (ParentQappC­
             ommand.KORDID == Progetti) or (ParentQappCommand.KORDID == Privati) or (ParentQappCommand.KORDID == Funzioni) or (ParentQappCommand.KORDID == Interventi) or (ParentQappCommand.KORDID == Personale)
         )
      {
         badCombinationOfKordAndDestination = false
      }
      else 
      {
         badCombinationOfKordAndDestination = true
          
      }
    break
    case CustomHook:
      if ((ParentQappCommand.KORDID == Articoli) or (ParentQappCommand.KORDID == AltreAnagrafiche) or (ParentQappCommand.KORDID == ClientiFornitori) or (ParentQappCommand.KORDID == Documenti) or (
             ParentQappCommand.KORDID == Eventi) or (ParentQappCommand.KORDID == Progetti) or (ParentQappCommand.KORDID == Privati) or (ParentQappCommand.KORDID == Funzioni) or (ParentQappCommand.KORDID ==
             Interventi) or (ParentQappCommand.KORDID == Personale) or (ParentQappCommand.KORDID == Calendar) or (ParentQappCommand.KORDID == Documenti))
      {
         badCombinationOfKordAndDestination = false
      }
      else 
      {
         badCombinationOfKordAndDestination = true
      }
    break
    case Backend:
      badCombinationOfKordAndDestination = false
    break
    case API:
      badCombinationOfKordAndDestination = false
    break
    case Cruscotto:
      if ((ParentQappCommand.KORDID == Cruscotti))
      {
         badCombinationOfKordAndDestination = false
      }
      else 
      {
         badCombinationOfKordAndDestination = true
         additionalErrorMessage = "only 'Cruscotti' is a valid KordApp data for a Command with Destination 'Cruscotto' "
      }
    break
    case customReport:
      if ((ParentQappCommand.KORDID == Articoli) or (ParentQappCommand.KORDID == AltreAnagrafiche) or (ParentQappCommand.KORDID == ClientiFornitori) or (ParentQappCommand.KORDID == Documenti) or (
             ParentQappCommand.KORDID == Eventi) or (ParentQappCommand.KORDID == Progetti) or (ParentQappCommand.KORDID == Privati) or (ParentQappCommand.KORDID == Funzioni) or (ParentQappCommand.KORDID ==
             Interventi) or (ParentQappCommand.KORDID == Personale) or (ParentQappCommand.KORDID == Documenti))
      {
         badCombinationOfKordAndDestination = false
      }
      else 
      {
         badCombinationOfKordAndDestination = true
      }
    break
    default:
      QappCore.DTTLogMessage(formatMessage("unsupported QappCommand Destination (|1)", Destination, ...), ..., DTTError)
    break
  }
   
  if (badCombinationOfKordAndDestination)
  {
    Error = true
    if (additionalErrorMessage != "")
      additionalErrorMessage = formatMessage("; |1", additionalErrorMessage, ...)
    ParentQappCommand.addDocumentError(formatMessage("Module '|1' is not supported for a command with destination '|2'|3.", decode(ParentQappCommand.KORDID, Kordapp), decode(Destination, QappCommandDestinati­
          ons), additionalErrorMessage, ...))
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void QappCommandParameters.validationOfShowInTools(
  inout boolean Error // 
)
{
  if (ShowInTools)
  {
    if (Destination != ToolbarDiScheda)
    {
      Error = true
      ParentQappCommand.addDocumentError("'SHOWINTOOLS' is supported only for Commands with DESTINATION 'ToolbarDiScheda'.")
    }
     
    if (ParentQappCommand.KORDID != Calendar)
    {
      if (Silent and Refresh)
      {
         Error = true
         ParentQappCommand.addDocumentError("Silent and Refresh are not both supported for 'In Tools' except than in Calendar. Refresh is not needed in other modules, consider using refresh = false.")
      }
    }
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void QappCommandParameters.validationOfPopupForm(
  inout boolean Error // 
)
{
  if (Popup or ConfigFormDefined)
  {
    if ((Width == 0) or (Height == 0))
    {
      Error = true
      ParentQappCommand.addDocumentError(formatMessage("It is mandatory to set a value for width (set value: |1) and height (set value: |2) if popup or configFormDefined", nullValue(Width, 0), nullValue(Height
            , 0), ...))
      return 
    }
     
    if (Destination == PaginaDiTabControl or Destination == Backend)
    {
      Error = true
      ParentQappCommand.addDocumentError(formatMessage("destination |1 does not support popup", decode(Destination, QappCommandDestinations), ...))
      return 
    }
     
    if (ForbidClosure)
    {
      QappRuntimeBehavior qrb = getQappRuntimeBehavior()
      if (!(qrb.SupportInteractionWithQualibusForms))
      {
         Error = true
         ParentQappCommand.addDocumentError(formatMessage("ForbidClosure is true, but SupportFormClosure is not enabled in QappRunTimeBehavior, this will lead to the impossibility to close the popup form. It 
              is necessary to set supportFormClsoure to true", decode(Destination, QappCommandDestinations), ...))
         return 
      }
    }
  }
  else 
  {
    if (ForbidClosure)
    {
      Error = true
      ParentQappCommand.addDocumentError("ForbidClosure is supported only for popup commands")
      return 
    }
    if (Width != 0 or Height != 0)
    {
      Error = true
      ParentQappCommand.addDocumentError("Height and Width are supported only for popup commands")
      return 
    }
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void QappCommandParameters.validationOfStyles(
  inout boolean Error // 
)
{
  // 4. styles validation
  IDArray definedStyleIds = this.getStylesArray(...)
  if (definedStyleIds.length() > 0)
  {
    if (Destination != RigaDiCollegamenti and Destination != ImpegnoCalendar)
    {
      Error = true
      ParentQappCommand.addDocumentError("styles can be added only to commands with destination RigaDiCollegamenti or impegnoCalendar")
    }
     
    for (int i = 0; i < definedStyleIds.length(); i = i + 1)
    {
      string currentStyle = definedStyleIds.getValue(i)
      boolean styleIdFound = false
      for each QappDataStyle officialStyle in QappCore.QappDataStyles
      {
         if (officialStyle.STYLEID == currentStyle)
         {
           styleIdFound = true
           break 
         }
      }
       
      if (!(styleIdFound))
      {
         Error = true
         ParentQappCommand.addDocumentError(formatMessage("style '|1' is not a valid style", currentStyle, ...))
      }
    }
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void QappCommandParameters.validationOfSupportedApp(
  inout boolean Error // 
)
{
  // 5. supported app validation
  if (SupportedApp != Qualibus)
  {
    if (Destination == RigaDiCollegamenti or Destination == ToolbarDiAlbero or Destination == PaginaDiTabControl)
    {
      Error = true
      ParentQappCommand.addDocumentError(formatMessage("Commands with Destination '|1', are not supported in Qmobile, supportedApp must be Qualibus to use that Destination", decode(Destination, QappCommandDe­
            stinations), ...))
      return 
    }
     
    if (ShowInTools == true)
    {
      Error = true
      ParentQappCommand.addDocumentError(formatMessage("ShowInTools is not supported in Qmobile", decode(Destination, QappCommandDestinations), ...))
      return 
    }
     
    if (Popup)
    {
      Error = true
      ParentQappCommand.addDocumentError("Popup commands are supported in Qualibus only")
      return 
    }
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void QappCommandParameters.validationOfCustomHook(
  inout boolean Error // 
)
{
  // 6. custom hook validation
  if (Destination == CustomHook)
  {
    if (CustomHook == "")
    {
      string currentStyle = ""
      Error = true
      ParentQappCommand.addDocumentError(formatMessage("it is mandatory to set a customHook when destination is cusomHook, use the setCustomHook in the command.initialize method", currentStyle, ...))
      return 
    }
     
    if (Refresh)
    {
      Error = true
      ParentQappCommand.addDocumentError("Refresh must be false for a customHook")
      return 
    }
    if (RequireConfirmation)
    {
      Error = true
      ParentQappCommand.addDocumentError("RequireConfirmation must be false for a customHook")
      return 
    }
     
    boolean badCombinationOFCustomHookAndDestionation = false
     
    switch (CustomHook)
    {
      case AfterSave:
         if ((ParentQappCommand.KORDID == Articoli) or (ParentQappCommand.KORDID == AltreAnagrafiche) or (ParentQappCommand.KORDID == ClientiFornitori) or (ParentQappCommand.KORDID == Eventi) or (
               ParentQappCommand.KORDID == Progetti) or (ParentQappCommand.KORDID == Privati) or (ParentQappCommand.KORDID == Funzioni) or (ParentQappCommand.KORDID == Interventi) or (ParentQappCommand.KORDID
               == Personale) or (ParentQappCommand.KORDID == Documenti))
         {
           badCombinationOFCustomHookAndDestionation = false
         }
         else 
         {
           badCombinationOFCustomHookAndDestionation = true
         }
      break
      case AfterLoad:
         if ((ParentQappCommand.KORDID == Articoli) or (ParentQappCommand.KORDID == AltreAnagrafiche) or (ParentQappCommand.KORDID == ClientiFornitori) or (ParentQappCommand.KORDID == Eventi) or (
               ParentQappCommand.KORDID == Progetti) or (ParentQappCommand.KORDID == Privati) or (ParentQappCommand.KORDID == Funzioni) or (ParentQappCommand.KORDID == Interventi) or (ParentQappCommand.KORDID
               == Personale))
         {
           badCombinationOFCustomHookAndDestionation = false
         }
         else 
         {
           badCombinationOFCustomHookAndDestionation = true
         }
      break
      case ApprovaDocumento:
         if ((ParentQappCommand.KORDID == Documenti))
         {
           badCombinationOFCustomHookAndDestionation = false
         }
         else 
         {
           badCombinationOFCustomHookAndDestionation = true
         }
      break
      case ChiudiEvento:
         if ((ParentQappCommand.KORDID == Eventi))
         {
           badCombinationOFCustomHookAndDestionation = false
         }
         else 
         {
           badCombinationOFCustomHookAndDestionation = true
         }
      break
      case ChiudIntervento:
         if ((ParentQappCommand.KORDID == Interventi))
         {
           badCombinationOFCustomHookAndDestionation = false
         }
         else 
         {
           badCombinationOFCustomHookAndDestionation = true
         }
      break
      case ChiudiDisposizoine:
         if ((ParentQappCommand.KORDID == Eventi))
         {
           badCombinationOFCustomHookAndDestionation = false
         }
         else 
         {
           badCombinationOFCustomHookAndDestionation = true
         }
      break
      case DisplayChiudiDisposizione:
         if ((ParentQappCommand.KORDID == Eventi))
         {
           badCombinationOFCustomHookAndDestionation = false
         }
         else 
         {
           badCombinationOFCustomHookAndDestionation = true
         }
      break
      case ChiudiDisposizioneConfirmation:
         if ((ParentQappCommand.KORDID == Eventi))
         {
           badCombinationOFCustomHookAndDestionation = false
         }
         else 
         {
           badCombinationOFCustomHookAndDestionation = true
         }
      break
      case ConfermaAttività:
         if ((ParentQappCommand.KORDID == Calendar))
         {
           badCombinationOFCustomHookAndDestionation = false
         }
         else 
         {
           badCombinationOFCustomHookAndDestionation = true
         }
      break
      case BeforeLoadLinks:
         if ((ParentQappCommand.KORDID == Articoli) or (ParentQappCommand.KORDID == AltreAnagrafiche) or (ParentQappCommand.KORDID == ClientiFornitori) or (ParentQappCommand.KORDID == Eventi) or (
               ParentQappCommand.KORDID == Progetti) or (ParentQappCommand.KORDID == Privati) or (ParentQappCommand.KORDID == Funzioni) or (ParentQappCommand.KORDID == Interventi) or (ParentQappCommand.KORDID
               == Personale))
         {
           badCombinationOFCustomHookAndDestionation = false
         }
         else 
         {
           badCombinationOFCustomHookAndDestionation = true
         }
      break
      default:
         QappCore.DTTLogMessage(formatMessage("unsupported customHook |1", decode(CustomHook, CustomHooks), ...), ..., DTTError)
      break
    }
     
    if (badCombinationOFCustomHookAndDestionation)
    {
      Error = true
      ParentQappCommand.addDocumentError(formatMessage("customHook '|1' is not supported for a command with kordapp '|2'.", decode(CustomHook, CustomHooks), decode(ParentQappCommand.KORDID, Kordapp), ...))
      return 
    }
     
    // specific hook validations
     
    boolean unsupportedInQmobile = false
    boolean silentOnlyInQmobile = false
     
    switch (CustomHook)
    {
      case AfterLoad:
      case AfterSave:
         if (SupportedApp != Qualibus)
         {
           if (!(Silent) and (ParentQappCommand.KORDID != Progetti or ParentQappCommand.KORDID != Eventi))
           {
             silentOnlyInQmobile = true
           }
         }
      break
      case BeforeLoadLinks:
         if (SupportedApp != Qualibus)
         {
           unsupportedInQmobile = true
         }
         if (!(Silent) and !(Async))
         {
           Error = true
           ParentQappCommand.addDocumentError(formatMessage("customHook '|1' supports only silent or async commands.", decode(CustomHook, CustomHooks), ...))
           return 
         }
      break
      case DisplayChiudiDisposizione:
         if (SupportedApp != Qualibus)
         {
           unsupportedInQmobile = true
         }
      break
      case ChiudiDisposizioneConfirmation:
         if (SupportedApp != Qualibus)
         {
           unsupportedInQmobile = true
         }
         if (!(Silent))
         {
           Error = true
           ParentQappCommand.addDocumentError(formatMessage("customHook '|1' supports only silent commands.", decode(CustomHook, CustomHooks), ...))
           return 
         }
         //  
         // info to be added in the debug to help Qapp developer
         QappCore.DTTLogMessage(formatMessage("INFO: customHook '|1' does not support shouldBeExecuted, to avoid shoing the confirmation message it is enough not to set a SilentMessage or to set an empty 
              string as silent message.", decode(CustomHook, CustomHooks), ...), ..., DTTInfo)
      break
    }
     
    if (unsupportedInQmobile)
    {
      Error = true
      ParentQappCommand.addDocumentError(formatMessage("customHook '|1' is not supported for Qmobile, SupportedApp must be Qualibus.", decode(CustomHook, CustomHooks), ...))
      return 
    }
     
    if (silentOnlyInQmobile)
    {
      Error = true
      ParentQappCommand.addDocumentError(formatMessage("customHook '|1' is supported for Qmobile only for Silent commands and KordId is Progetti or Eventi, SupportedApp must be Qualibus if you want to use a 
            non Silent command.", decode(CustomHook, CustomHooks), ...))
      return 
    }
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void QappCommandParameters.validationOfCruscotto(
  inout boolean Error // 
)
{
  if (Destination == Cruscotto)
  {
    if (ParentQappCommand.CommandQualibusIconFilename != "")
    {
      ParentQappCommand.addDocumentError("a command with destination 'Cruscotto' does not support icons, do not pass iconfile names")
      Error = true
    }
     
    if (InsertCruscottoAutomatically)
    {
      if (CruscottoDescription == "" or CruscottoTitle == "")
      {
         ParentQappCommand.addDocumentError("a command with destination 'Cruscotto' with autoInsertion needs a cruscottoDescritpion and a cruscottoTitle")
         Error = true
      }
    }
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void QappCommandParameters.validationOfAsync(
  inout boolean Error // 
)
{
  // 8. async validation
  if (Async)
  {
    if (ParentQappCommand.CommandQualibusIconFilename != "" or ParentQappCommand.CommandQmobileIconFilename != "")
    {
      ParentQappCommand.addDocumentError("async command does not support QualibusIconFilename and QmobileIconFilename")
      Error = true
    }
    if (Silent)
    {
      ParentQappCommand.addDocumentError("an async command cannot be also silent, silent makes sense to show a response to the user")
      Error = true
    }
    if (Popup)
    {
      ParentQappCommand.addDocumentError("an async command cannot be also popup")
      Error = true
    }
    if (Destination != CustomHook)
    {
      ParentQappCommand.addDocumentError(formatMessage("async is compatible only with Destination "CustomHook", you cannot use it with Destionation "|1"", decode(CustomHook, CustomHooks), ...))
      Error = true
    }
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void QappCommandParameters.validationOfApi(
  inout boolean Error // 
)
{
  if (Destination == API)
  {
    if (APIDocumentation == "")
    {
      ParentQappCommand.addDocumentError("APIDocumentation cannot be empty for a command with destination API")
      Error = true
    }
    if (ParentQappCommand.CommandQualibusIconFilename != "" or ParentQappCommand.CommandQmobileIconFilename != "")
    {
      ParentQappCommand.addDocumentError("commands with API destination do not support QualibusIconFilename and QmobileIconFilename")
      Error = true
    }
    if (ParentQappCommand.NEEDEDPRIVILEGE != NeededPrivilegeNone)
    {
      ParentQappCommand.addDocumentError("commands with API destination do not support Needed Privilege")
      Error = true
    }
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void QappCommandParameters.validationOfBrowser(
  inout boolean Error // 
)
{
  // 10. browser validation
  if (Browser)
  {
    if (Popup)
    {
      ParentQappCommand.addDocumentError("a browser command cannot be popup too")
      Error = true
    }
    if (Silent or Async)
    {
      ParentQappCommand.addDocumentError("a browser command cannot be silent or async")
      Error = true
    }
    if (Destination == API or Destination == PaginaDiTabControl or Destination == Cruscotto)
    {
      ParentQappCommand.addDocumentError(formatMessage("Browser command is supported only when destination is ToolbarDiScheda or CustomHook", decode(Destination, QappCommandDestinations), ...))
      Error = true
    }
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void QappCommandParameters.validationOfCallShouldBeExecuted(
  inout boolean Error // 
)
{
  if (CallShouldBeExecuted)
  {
    if (!(Browser) and !(Popup) and !(Destination == PaginaDiTabControl))
    {
      ParentQappCommand.addDocumentError("shuoldBeExecuted is supported only by Browser, Popup commands and for PaginaDiTabControl destination ones")
      Error = true
    }
    if (Silent)
    {
      ParentQappCommand.addDocumentError("shouldBeExecuted is not supported by silent commands")
      Error = true
    }
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void QappCommandParameters.validateAdditionalConditions(
  inout boolean Error // 
)
{
  if (Destination != API and Destination != PaginaDiTabControl and Destination != Cruscotto and Destination != Backend and Destination != customReport)
  {
    if (!(Async))
    {
      if (!(Browser) and !(Popup) and !(Silent))
      {
         ParentQappCommand.addDocumentError(formatMessage("A non async command with destination |1 must be either Browser, Popup or Silent, otherwise no action will be executed.", decode(Destination, 
              QappCommandDestinations), ...))
         Error = true
      }
    }
  }
   
  if (Destination != CustomHook)
  {
    if (ExecuteAlways)
    {
      ParentQappCommand.addDocumentError("ExecuteAlways is supported only by commands with desination customHook.")
      Error = true
    }
  }
   
  if (Destination = RigaDiCollegamenti)
  {
    if (Styles.length() == 0)
    {
      ParentQappCommand.addDocumentError("For Destination RigaDiCollegamenti it is mandatory to addStyleId in the Iniitlialize.")
      Error = true
    }
  }
   
  if (ParentQappCommand.FILTER != "")
  {
     
    if (Destination == API)
    {
      ParentQappCommand.addDocumentError("A command with Destination API cannot have a FILTER")
      Error = true
    }
  }
  if (Popup == true and Destination == CustomHook)
  {
    if (CustomHook == ChiudiDisposizoine)
    {
      if (!(Modal))
      {
         ParentQappCommand.addDocumentError(formatMessage("The popup in case of customHook '|1' must also be modal", decode(CustomHook, CustomHooks), ...))
         Error = true
      }
    }
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void QappCommandParameters.createDttLogMessagesToHelpDeveloper()
{
  if (Popup)
  {
    QappCore.DTTLogMessage(formatMessage("Command |1: To debug a Popup command one must use the debug to file and check the Log folder", ParentQappCommand.CAPTIONITA, ...), ..., DTTInfo)
  }
   
  if (ForbidClosure)
  {
    QappCore.DTTLogMessage("ForbidClosure is set so the user will not be able to close the window without putting a call ro CloseApplication inside a procedure attached to a button!", ..., DTTInfo)
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void QappCommandParameters.ValidateCustomReport(
  inout boolean Error        // 
  int:validateReasons Reason // 
)
{
  if (ConfigFormDefined)
  {
    boolean validCombination = Destination == customReport
    if (!(validCombination))
    {
      ParentQappCommand.addDocumentError(formatMessage("ConfigformDefined can be true only if command has Destination '|1'", decode(Destination, QappCommandDestinations), ...))
      Error = true
    }
  }
  boolean customReportWithoutFileExtension = Destination == customReport and ReportFileExtension == ""
  boolean destinationOtherThanCustomReportAndFileExtensionSet = Destination != customReport and ReportFileExtension != ""
  boolean wrongUsageOfFileExtension = customReportWithoutFileExtension or destinationOtherThanCustomReportAndFileExtensionSet
  if (wrongUsageOfFileExtension)
  {
    ParentQappCommand.addDocumentError("ReportFileExtension is mandatory for cusomReport commands and cannot be used in other cases")
    Error = true
  }
   
   
  // skipValidationOfConfigForm is a reason used in tests to avoid checking in DLL, we could do this once we have mocks working in IDUnit
  if (Destination == customReport and Reason != skipValidationOfConfigForm)
  {
    if (ConfigFormDefined)
    {
      IDForm idf = ParentQappCommand.retrieveSpecificConfigForm()
      if (idf == null)
      {
         string expectedConfigFormName = ParentQappCommand.getExpectedConfigFormName()
          
         ParentQappCommand.addDocumentError(formatMessage("A command with Destination '|1' and with configFormDefined must have a corresponding form: the Qapp component must have a form whose name is exactly 
              '|2'", decode(Destination, QappCommandDestinations), expectedConfigFormName, ...))
         Error = true
      }
    }
  }
  if (DirectFileDownload)
  {
    if (Destination != customReport)
    {
      ParentQappCommand.addDocumentError(formatMessage("DirectFileDownload can be used only with customReport destination ('|1' has been set instead) ", decode(Destination, QappCommandDestinations), ...))
      Error = true
    }
    if (ConfigFormDefined)
    {
      ParentQappCommand.addDocumentError(formatMessage("DirectFileDownload (|1) can not be used together with ConfigFormDefined (|2)", DirectFileDownload, ConfigFormDefined, ...))
      Error = true
    }
  }
}


// ──────────────────────────────────

// ************************************************************
// Event raised to the document during the validation procedure
// ************************************************************
event QappCommandParameters.OnValidate(
  int:validateReasons Reason // An integer specifying the reason the document is being validated. The most common values are 0 to indicate complete validation before saving, or 1 to indicate a quick validati...
  inout boolean Error        // A boolean output parameter. If set to True, specifies an error condition and stops the validation immediately.
  inout boolean Skip         // A boolean output parameter. If set to True, does not check the rules for automatic validation of the document (see notes).
)
{
  this.initializeParentQappCommand()
  this.validateMandatoryDestination(Error)
  this.validateCorrectDestinationAndKordPair(Error)
  this.validationOfShowInTools(Error)
  this.validationOfPopupForm(Error)
  this.validationOfStyles(Error)
  this.validationOfSupportedApp(Error)
  this.validationOfCustomHook(Error)
  this.validationOfCruscotto(Error)
  this.validationOfAsync(Error)
  this.validationOfApi(Error)
  this.validationOfBrowser(Error)
  this.validationOfCallShouldBeExecuted(Error)
  this.ValidateCustomReport(Error, Reason)
  this.validateAdditionalConditions(Error)
   
  this.createDttLogMessagesToHelpDeveloper()
}


// ──────────────────────────────────

// ***********************************************************
// Event raised to the document when the transaction is closed
// ***********************************************************
event QappCommandParameters.OnEndTransaction()
{
  // default for App is set (we do not use Init for this class since we do not persist it in the db in the normal way, so we use the endtransaction
   
  if (SupportedApp == "")
  {
    SupportedApp = Qualibus
  }
   
  // set the destination to customhook in case destination has not been set and an hook has been set
   
  if (Destination == "")
  {
    if (CustomHook != "")
    {
      Destination = CustomHook
    }
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static QappCommandRuntimeParameters QappCommandRuntimeParameters.create(
  string mainId          // 
  string subId           // 
  date time dateTimeInfo // 
  IDArray multiIDs       // 
)
{
  QappCommandRuntimeParameters qcrp = new()
  qcrp.Mainid = mainId
  qcrp.Subid = subId
  qcrp.Datetimeinfo = dateTimeInfo
  qcrp.Multiids = multiIDs
   
  return qcrp
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string QappCommandRuntimeParameters.ToJson()
{
  DevTools.ToBeReviewed("saveToXML does not save the multiids array, if we want to save it it should be likely saved to a comma separated string and handled in to json and from json")
   
  string objectAsJson = saveToXML(false, "", 99, JSON, true, ...)
  return objectAsJson
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static QappCommandRuntimeParameters QappCommandRuntimeParameters.createFromJson(
  string serializedObject // 
)
{
  QappCommandRuntimeParameters qcrp = new()
  qcrp.loadFromXML(serializedObject, false, JSON, true)
   
  return qcrp
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void QappcoreOnCommandEventHandler.processLoginCommand()
{
  QappCore.sendAppMessage("CloseMobileLogin", null)
   
  boolean remoteCommandContainedInUrl = (length(RemoteCommand) > 0)
   
  boolean isValidToken = false
  Utente authenticatedUser = LoginToken.authenticateUserWithToken(Token, remoteCommandContainedInUrl, isValidToken)
  string nonMatchingDBVersionErrorMessage = ""
   
  // DB Version check
  int supportedDBVersion = VersionInfo.getSupportedScriptNumber()
  boolean DBVersionIsOk = SW9DB.isTheExpectedScriptNumber(supportedDBVersion, nonMatchingDBVersionErrorMessage)
   
  if (!(DBVersionIsOk))
  {
    QappCore.QappCommandHandler.writeLoginMessage(nonMatchingDBVersionErrorMessage)
    return 
  }
   
  if (authenticatedUser and isValidToken)
  {
    QappCore.Loggeduser = authenticatedUser
    QappCore.Loggeduser.loadPrivilegeForCurrentQapp(...)
     
    // check for commands with exceuteALways: we need to bypass the canExecute check
    boolean executeAlways = false
    if (remoteCommandContainedInUrl)
    {
       
      QappCore.QappCommandHandler.setQappCoreSessionInitiatorType(loginWithQAPP_CMDAndToken)
       
      QappCommandParameters qcp = QappCommand.getQappCommandParametersGivenCommandName(RemoteCommand)
      if (qcp)
      {
         executeAlways = qcp.ExecuteAlways
      }
    }
    else 
    {
      QappCore.QappCommandHandler.setQappCoreSessionInitiatorType(loginWithTokenNoCommand)
       
    }
     
    if (!(executeAlways))
    {
      boolean userCanExecute = QappCore.Loggeduser.canExecuteCurrentQapp()
      if (!(userCanExecute))
      {
         QappCore.QappCommandHandler.writeLoginMessage("L'utente non ha il privielgio Esegui.")
         return 
      }
    }
     
     
     
    QappCore.CommandData.clear()
    //  
    // values stored in he map so hey are available in the after login evebt
    QappCore.CommandData.setValue("Command", RemoteCommand)
    QappCore.CommandData.setValue("MainId", RemoteSourceID)
     
    // this arrangement allows to execute a QApp command even if a session is already running in browser
    if (QappCore.userRole != null)
    {
      // But in this case we close all the forms
      QappCore.closeAllForms(...)
      if (RemoteCommand != null)
      {
         QappCommandHandler.ForwardCommand(RemoteCommand, RemoteSourceID, ...)
      }
    }
    else 
    {
      this.storeBackUrl()
       
      // succesfully logged in with token
      QappCore.userRole = Administrator
      QappCore.setSessionAsStartedFRomLOGINURLCommand(true)
    }
     
    boolean dataValid = false
    string errorMessage = ""
    boolean customPostLoginCodeExecuted = QappCore.QappCommandHandler.handleCustomPostLoginCodeIfNeeded(dataValid, errorMessage)
    if (customPostLoginCodeExecuted)
    {
      if (errorMessage != "" and !(dataValid))
      {
         QappCore.setLoginMessageForNextSession(errorMessage)
         Tools.performExit(...)
      }
    }
    CookieHelper.SaveLastLoggedUsrIdCookie(QappCore.Loggeduser.IDUTENTE)
  }
  else 
  {
    QappCore.DTTLogMessage("Token non valido", ...)
    QappCore.QappCommandHandler.writeLoginMessage("Token di accesso non valido, effettuare il login manualmente")
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void QappcoreOnCommandEventHandler.processForceupdateCommand()
{
  boolean success = true
   
   
  string:forceUpdateModes forceUpdateMode = doNotForce
  if (Command == "FORCEUPDATE")
  {
    forceUpdateMode = doForce
  }
   
  // PROCESS DATABASE PART: if errros are found in the Qapp Content we handle it
  string processDatabaseErrorMessages = null
   
  QappCore.QappCommandHandler.initializeAll()
   
  boolean QappInsertedForTheFirstTime = QappCore.QappCommandHandler.getQappInsertedForTheFirstTime()
  QappCore.QappCommandHandler.ProcessDatabase(forceUpdateMode)
  processDatabaseErrorMessages = QappCore.QappCommandHandler.getProcessDatabaseErrorMessages()
  if (processDatabaseErrorMessages != "")
  {
    success = false
  }
  if (!(QappInsertedForTheFirstTime))
  {
    QappCore.QappWeb.resetQAppDataStyles(doNotResetExistingStyles)
  }
  this.handleLoginMessageForWakeUpCommand(QappInsertedForTheFirstTime, success, forceUpdateMode, processDatabaseErrorMessages)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void QappcoreOnCommandEventHandler.processExecuteAsyncCommand()
{
  string commandName = CommandName
   
  // handling of logged user from command string
  int loggedUserId = LoggedUserID
  QappCore.Loggeduser = Utente.get(loggedUserId)
  QappCore.DTTLogMessage(formatMessage("LoggedUser for Async Command is: |1", QappCore.Loggeduser.Username, ...), ..., DTTInfo)
   
  string qappRunTimeParametersAsJson = JsonObject
  QappCommandRuntimeParameters qcrp = QappCommandRuntimeParameters.createFromJson(qappRunTimeParametersAsJson)
   
  QappCommand qc = QappCore.QappCommandHandler.CommandFactory(commandName, qcrp, ...)
  qc.performInit()
  qc.performExecution()
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void QappcoreOnCommandEventHandler.processEditSettingsCommand()
{
  int:editsettingsCommandStatuses status = this.computePasswordStatus()
   
  // Finally based on the status we take action:
  switch (status)
  {
    case proceed:
      QappCore.userRole = Administrator
       
      // to remember the fact we are logged in or not we set a tag
      QappCore.QappCommandHandler.setTag("loggedIdUtente", QappCore.Loggeduser.IDUTENTE)
      EditSettingsForm.openFor(QappCore.QappCommandHandler)
    break
    case wrongPassword:
      this.setLoginMessageWithAppMessage("the provided password for the EDITSETTTINGS is not correct")
    break
    case passwordNotPassed:
      this.setLoginMessageWithAppMessage("EDITSETTINGS command must be called with the PASSWORD parameter: the access to the settings file is password proteced")
    break
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private int QappcoreOnCommandEventHandler.computePasswordStatus()
{
  int:editsettingsCommandStatuses passwordStatus = 0
   
  boolean passwordHasBeenPassed = (ProvidedPassword != null)
   
  string expectedPassword = QappCore.QappCommandHandler.getSettingsPassword()
  if (expectedPassword == "")
  {
    // if a password has not been specified we can proceed
    passwordStatus = proceed
  }
  else 
  {
    // in case the access to settings is passowrd protected we check if a password has benn passed in the URL...
    if (passwordHasBeenPassed)
    {
      boolean passwordIsCorrect = QappCore.QappCommandHandler.isPasswordCorrect(ProvidedPassword)
      if (passwordIsCorrect)
      {
         //  ... and if the password is correct we can proceed...
         passwordStatus = proceed
      }
      else 
      {
         // ...but if the passed password is wrong we enter the wrongPassword status...
         passwordStatus = wrongPassword
      }
    }
    else 
    {
      // ...and if a password was not passed, since a password was required we move into passwordNotPassed staus
      passwordStatus = passwordNotPassed
    }
  }
  return passwordStatus
}


// ──────────────────────────────────

// ********************************************************************
// this method allows to authenticate if valid auth parameter is passed
// ********************************************************************
public void QappcoreOnCommandEventHandler.processAuthParameter()
{
   
  // check to avoid processing that could lead to exit! if no auth
  if (AuthInfo == "")
    return 
   
  boolean validToken = false
  Utente u = LoginToken.authenticateUserWithToken(AuthInfo, false, validToken)
  if (validToken)
  {
    int cookieUserId = CookieHelper.ReadLastLoggedUserIdFromCookie()
    boolean userSwitchNeeded = cookieUserId > 0 and cookieUserId != u.IDUTENTE
     
    if (userSwitchNeeded)
    {
      QappCore.DTTLogMessage(formatMessage("user switch needed: cookie=|1 token=|2", cookieUserId, u.IDUTENTE, ...), ..., DTTInfo)
       
      //  save pending cmd info in cookies for next session and exit
      CookieHelper.savePendingCommand(Command, u, Kordapp, toInteger(RemoteSourceID))
       
      Tools.performExit(...)
    }
    else 
    {
      QappCore.Loggeduser = u
      QappCore.userRole = Administrator
      QappCore.LoggedInFromQmobile = true
      CookieHelper.SaveLastLoggedUsrIdCookie(u.IDUTENTE)
    }
  }
  else 
  {
    QappCore.DTTLogMessage("Impossible to login with AUTH URL parameter", ..., DTTError)
    //  
    // we should write in cookies so at the name
    QappCore.setLoginMessageForNextSession("Informazioni di autenticazione contenute nel parametro AUTH non valide")
    Tools.performExit(...)
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void QappcoreOnCommandEventHandler.setLoginMessageWithAppMessage(
  string message // 
)
{
  IDMap idm = new()
  idm.setValue("LOGIN_MESSAGE", message)
  QappCore.sendAppMessage("SET_LOGIN_MESSAGE", idm)
}


// ──────────────────────────────────

// *********************************************************
// generates the proper login message for the WAKEUP command
// *********************************************************
private void QappcoreOnCommandEventHandler.handleLoginMessageForWakeUpCommand(
  boolean dataInsertedForTheFirstTime     // 
  boolean success                         // 
  string:forceUpdateModes forceUpdateMode // 
  string errorMessage                     // 
)
{
  string loginMessage = ""
  string qappWebName = ""
   
  string licensedCompanyName = ""
  select into variables (found variable)
    set licensedCompanyName = DESCRDITTA1
  from 
    TABPARAMETRI1 // master table
   
  if (find(licensedCompanyName, " ", ...) > 0)
  {
    licensedCompanyName = "'" + licensedCompanyName + "'"
  }
   
  if (!(success))
  {
    loginMessage = formatMessage("C'è stato un errore inserendo i dati Qapp |1 nel database |2; dettagli: |3", qappWebName, licensedCompanyName, errorMessage, ...)
  }
  else 
  {
    if (dataInsertedForTheFirstTime)
    {
      loginMessage = formatMessage("I dati della Qapp |1 sono stati scritti per la prima volta sul database |2", qappWebName, licensedCompanyName, ...)
    }
    else 
    {
      if (forceUpdateMode != doNotForce)
         loginMessage = formatMessage("I dati della Qapp |1 sono stati sovrascritti con ForceUpdate (in modalità '|2') su Qapp e Comandi sul database |3", qappWebName, decode(forceUpdateMode, ForceUpdateModes)
                  , licensedCompanyName, ...)
      else 
         loginMessage = formatMessage("I dati della Qapp |1 sono stati sovrascritti senza ForceUpdate sul database |2", qappWebName, licensedCompanyName, ...)
    }
  }
  QappCore.QappCommandHandler.writeLoginMessage(loginMessage)
   
}


// ──────────────────────────────────

// **********************************************************************************
// helper method that reads BackUrl from the url and stores it in the command handler
// **********************************************************************************
private void QappcoreOnCommandEventHandler.storeBackUrl()
{
  // retrieve from URL and store in CommandHandler the caller url, if found
  if (length(BackUrl) > 0)
  {
    QappCore.QappCommandHandler.CallerUrl = BackUrl
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void QappcoreOnCommandEventHandler.ReadUrlParameters(
  IDMap readUrlParameters // 
)
{
  Token = readUrlParameters.getValue("TKN")
  RemoteCommand = readUrlParameters.getValue("QAPP_CMD")
  RemoteSourceID = readUrlParameters.getValue("MAIN_ID")
  BackUrl = readUrlParameters.getValue("BACK_URL")
  CommandName = readUrlParameters.getValue("commandName")
  LoggedUserID = toInteger(readUrlParameters.getValue("loggedUserId"))
  JsonObject = readUrlParameters.getValue("jsonObject")
  ProvidedPassword = readUrlParameters.getValue("PASSWORD")
  AuthInfo = readUrlParameters.getValue("AUTH")
  Kordapp = toInteger(readUrlParameters.getValue("KORDAPP"))
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void QappcoreOnCommandEventHandler.processCommand()
{
  if (Command == "LOGIN")
  {
    this.processLoginCommand()
  }
  else if (Command == "WAKEUP" or Command == "FORCEUPDATE")
  {
    this.processForceupdateCommand()
  }
  else if (Command == "executeAsync")
  {
    this.processExecuteAsyncCommand()
  }
  else if (Command == "EDITSETTINGS")
  {
    this.processEditSettingsCommand()
  }
  else if (Command == "EXIT")
  {
     
    // EXIT is a convenient way to terminate a browser session (useful expecially when in widget mode: typing ?CMD=EXIT in the url will terminate the session)
    Tools.performExit(...)
  }
  else if (Command == "QAPPVERSION")
  {
    string qappVersion = getQappVersion()
    this.setLoginMessageWithAppMessage(formatMessage("QappVersion: |1", qappVersion, ...))
  }
  else 
  {
    string message = formatMessage("Command '|1' not supported by QappCore", Command, ...)
    QappCore.DTTLogMessage(message, ..., DTTInfo)
  }
   
  // only qmobile will add a REDIRECT_URL parameter
  boolean calledFromQmobile = QappCore.getURLParam("REDIRECT_URL") != ""
   
  boolean processAuthUrlParameter = this.shouldProcessAuthParameter(calledFromQmobile)
   
  if (processAuthUrlParameter)
  {
    this.processAuthParameter()
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static QappcoreOnCommandEventHandler QappcoreOnCommandEventHandler.create(
  string command         // 
  IDMap urlParametersMap // 
)
{
  QappcoreOnCommandEventHandler qoceh = new()
  qoceh.init()
  qoceh.ReadUrlParameters(urlParametersMap)
  qoceh.Command = command
  return qoceh
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean QappcoreOnCommandEventHandler.shouldProcessAuthParameter(
  boolean calledFromQmobile // 
)
{
  boolean openModuleHasAuth = Command == "OPEN_MODULE" and AuthInfo != ""
  return openModuleHasAuth or calledFromQmobile
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void QappcoreOnCommandEventHandler.setAuthInfo(
  string token // 
)
{
  AuthInfo = token
}


// ──────────────────────────────────

// ***********************************************************************************
// This function  returns the DataType (integer, ideally from RefCDataType value list)
// of the n-th RefCData for a given Reference Type
// It has been made to make the caller code more readable
// ***********************************************************************************
private int Riferimento.GetDataType(
  int IdRefType  // ID of the Ref CUstom Data for which we want to know the datatype
  int CDataIndex // n-th ref CData for the given Referecne Type
)
{
   
  int:refCdataType RefCDataType = 0
   
  Recordset datiRiferimenti = new()
  //  
  // here we use the global IDTipoRiferimento set previously. it is he only way to access that ID
  select into recordset (datiRiferimenti)
    Datatype as DataTypeCdataRef
    Sequenza as SEQUENZA
    Nome as EVA_REF_CDATA_NAME
  from 
    DatipersRiferimenti // master table
  where
    IDTipoRiferimento = IdRefType
  order by
    Sequenza
    Nome
  //  
   
  // Scrivi un commento per questo ciclo o premi backspace per eliminare questo commento
  datiRiferimenti.moveFirst()
  for (int i = 0; i < (CDataIndex - 1); i = i + 1)
  {
    datiRiferimenti.moveNext()
    if (datiRiferimenti.EOF())
      break 
  }
  // 
  if (datiRiferimenti.EOF())
  {
    RefCDataType = null
  }
  else 
  {
    RefCDataType = convert(datiRiferimenti.getFieldValue("DataTypeCdataRef"))
  }
  //  
  // Switch on DataType. Each variable is initialized to null so that if the query returns no value the field is correctly visualized as empty
  return RefCDataType
}


// ──────────────────────────────────

// ****************************************************************************************
// The method returns a Map with information about the CData of Riferimento, in particular:
// 1) the map contains an 1D array with fixed position info
// at 0 ID of custom data ref
// at 1 Caption of cdr
// at 2 DataType of cdata
// at 3 "Valore" used for combo elements and precision of float
// 
// it is a static method that needs IdTIpoRiferimento parameter to be used
// ****************************************************************************************
public static IDMap Riferimento.GetInfoCdataRef(
  int IdTipoRiferimento // 
)
{
  IDMap mapInfoCDataRef = new()
  int i = 0
  for each row (readonly)
  {
    select
      ID = ID
      DESCR = Nome
      SEQ = Sequenza
      DataType = Datatype
      ComboValue = CMBVALUE
    from 
      DatipersRiferimenti // master table
    where
      IDTipoRiferimento = IdTipoRiferimento
      Attivo = Yes
    order by
      Sequenza
      Nome
    // 
    i = i + 1
     
    IDArray ida = new()
    ida.setValue(0, ID)
    ida.setValue(1, DESCR)
    ida.setValue(2, DataType)
    ida.setValue(3, ComboValue)
    mapInfoCDataRef.setObject(i, ida)
  }
   
   
  return mapInfoCDataRef
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static Riferimento Riferimento.create(
  int idRefType                          // 
  int IdSource                           // 
  int IdDestination                      // 
  optional string:flagYN IsModello = "N" // 
)
{
  Riferimento r = new()
  r.IDRiferimento = Sequence.getNextSequence(EVAN_ID_EVA_REFERENCES, ...)
  r.IDEvento = IdSource
  r.IDTipoRiferimento = idRefType
  r.IDForAll = IdDestination
  r.ISMODELLO = IsModello
  r.inserted = true
  r.computeAdditionalModuleValue()
  r.DestinationDescription = r.getDestinationDescription()
   
  return r
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public MainModule Riferimento.GetDestination()
{
  if (!(Destination))
    this.computeDestination()
   
  return Destination
}


// ──────────────────────────────────

// **************************************************************************************************************************************************
// all destinations are loaded and set, quickLoad is used to maximize speed, it is always possible to manually reload fully the destination if needed
// **************************************************************************************************************************************************
public void Riferimento.computeDestination()
{
  ReferenceType rt = new()
  if (nullValue(IDTipoRiferimento, 0) > 0)
  {
    rt.IDTipoRiferimento = IDTipoRiferimento
    try 
    {
      rt.loadFromDB(...)
      int kordAppDestination = rt.SourceKordapp
      switch (kordAppDestination)
      {
         case Articoli:
           Articolo a = Articolo.getFromDB(IDForAll, quickLoad)
           Destination = a
         break
         case Eventi:
           Evento e = Evento.getFromDB(IDForAll, quickLoad)
           Destination = e
         break
         case ClientiFornitori:
           Clifor cf = Clifor.getFromDB(IDForAll, quickLoad)
           Destination = cf
         break
         case Progetti:
           Progetto p = Progetto.getFromDB(IDForAll, quickLoad)
           Destination = p
         break
         case AltreAnagrafiche:
           AltreAnagrafiche c = AltreAnagrafiche.getFromDB(IDForAll, quickLoad)
           Destination = c
         break
         case Funzioni:
           Funzione f = Funzione.getFromDB(IDForAll, quickLoad)
           Destination = f
         break
         case Personale:
           Personale p = Personale.getFromDB(IDForAll, quickLoad)
           Destination = p
            
           // assign tipo personale
           TipoPersonale tp = TipoPersonale.get(p.IDTIPOANAGR)
           AdditionalModuleValue = tp.DESCRTIPOANAGR
         break
         case Privati:
           Privati p = Privati.getFromDB(IDForAll, quickLoad)
           Destination = p
         break
      }
    }
  }
  else 
  {
    QappCore.DTTLogMessage(formatMessage("cannot retrieve destination because IDTipoRiferimento has not a good value (|1)", IDTipoRiferimento, ...), ..., DTTError)
    Destination = null
  }
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public static Riferimento Riferimento.CreateRiferimento(
  MainModule Origin      // 
  MainModule Destination // 
  ReferenceType RefType  // 
)
{
  Riferimento newRif = new()
  boolean resultCheckKordApp = this.CheckKordApp(Origin, Destination, RefType)
  if (resultCheckKordApp)
  {
    newRif.init()
    newRif.IDRiferimento = Sequence.getNextSequence(EVAN_ID_EVA_REFERENCES, ...)
     
    int inIDSource = Origin.getMainID()
    int inIDDestination = Destination.getMainID()
    newRif.IDEvento = inIDSource
    newRif.IDForAll = inIDDestination
    //  
    // The object Destination will be created on the After Save event
     
    newRif.IDTipoRiferimento = RefType.IDTipoRiferimento
     
    newRif.ISMODELLO = No
    newRif.inserted = true
  }
  return newRif
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
private static boolean Riferimento.CheckKordApp(
  MainModule SourceObj      // 
  MainModule DestinationObj // 
  ReferenceType refType     // 
)
{
  boolean result = false
   
  if (SourceObj and DestinationObj and refType)
  {
    int inSourceObjKordApp = SourceObj.getKordApp()
    int inDestinationObjKordApp = DestinationObj.getKordApp()
     
    int refSourceKordApp = refType.DestinationKordapp
    int refDestinationKordApp = refType.SourceKordapp
     
    if ((inSourceObjKordApp = refSourceKordApp) and (inDestinationObjKordApp = refDestinationKordApp))
    {
      result = true
    }
  }
   
  return result
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static Riferimento Riferimento.createNewFromPanel(
  int IdSource  // 
  int idRefType // 
)
{
  Riferimento r = new()
  int vLast = 0
  //  
  // Temporary Value, should be checked di BeforeUpdate
  select into variables (found variable)
    set vLast = LASTID
  from 
    SW9SEQUENCES // master table
  where
    SEQNAME = EVAN_ID_EVA_REFERENCES
  r.IDRiferimento = vLast + 1
  r.IDEvento = IdSource
  r.IDTipoRiferimento = idRefType
  r.ISMODELLO = No
  r.inserted = true
   
   
  return r
}


// ──────────────────────────────────

// *******************************************************************
// Performs saving on NamedProps, so on CustomData of Riferimenti
// 1. Checks "O_" to understand is property was modified
// 2. Determines which property it is (just like in getNamedPropValue)
// 3. Switch on dataType
// 4. UPDATE (the INSERT part is still missing
// 
// REQUIRES HEAVY REFACTORING, MAYBE
//  
// *******************************************************************
public void Riferimento.saveNamedProperties()
{
  IDArray ArrayKeys = NamedProperties.getKeys()
   
  if (ArrayKeys.length() > 0)
  {
    for (int i = 0; i < ArrayKeys.length(); i = i + 1)
    {
      string key = ArrayKeys.getValue(i)
      if (left(key, 2) = "O_")
         continue 
      // 
      if (not(NamedProperties.containsKey("O_" + key)))
         continue 
       
      string newValue = NamedProperties.getValue(key)
       
      string s = mid(key, 9, 2)
      int indexOfRunTimeProperty = toInteger(s)
      IDMap idm = null
      idm = this.GetInfoCdataRef(IDTipoRiferimento)
       
      IDArray ida = (IDArray)idm.getObject(indexOfRunTimeProperty)
      //  
      // get
      int CurrentRefCDataFieldID = ida.getValue(0)
      int:refCdataType vRefCDataType = 0
       
      select into variables (found variable)
         set vRefCDataType = Datatype
      from 
         DatipersRiferimenti // master table
      where
         ID = CurrentRefCDataFieldID
       
      // Switch on DataType. Each variable is initialized to null so that if the query returns no value the field is correctly visualized as empty
      switch (vRefCDataType)
      {
         case IntegerRefDataType:
            
           update ValoriDatipersRiferimentiINTEGER
             set Value = toInteger(newValue)
           where
             IDRiferimento = IDRiferimento
             IDDatopersRiferimento = CurrentRefCDataFieldID
         break
         case CalcFloatRefDataType:
         case FloatRefDataType:
            
           update ValoriDatipersRiferimentiFLOAT
             set Value = toFloat(newValue)
           where
             IDRiferimento = IDRiferimento
             IDDatopersRiferimento = CurrentRefCDataFieldID
         break
         case TextRefDataType:
            
           update ValoriDatipersRiferimentiTEXT
             set Value = newValue
           where
             IDRiferimento = IDRiferimento
             IDDatopersRiferimento = CurrentRefCDataFieldID
         break
         case MemoRefDataType:
            
           update ValoriDatipersRiferimentiMEMO
             set Value = newValue
           where
             IDRiferimento = IDRiferimento
             IDDatopersRiferimento = CurrentRefCDataFieldID
         break
         case ComboRefDataType:
            
           update ValoriDatipersRiferimentiCOMBO
             set Value = newValue
           where
             IDRiferimento = IDRiferimento
             IDDatopersRiferimento = CurrentRefCDataFieldID
         break
         case DateRefDataType:
            
           update ValoriDatipersRiferimentiDATE
             set Value = toDateTime(newValue)
           where
             IDRiferimento = IDRiferimento
             IDDatopersRiferimento = CurrentRefCDataFieldID
         break
          
         // Boolean are missing now
          
      }
    }
  }
   
   
   
}


// ──────────────────────────────────

// ****************************************************
// creation of refCDSnaphot for the current Riferimento
// ****************************************************
private RefCDSnapshot Riferimento.CreateRefCDsnapshot()
{
  RefCDSnapshot refCDSnapshot = new()
  refCDSnapshot.init()
  //  
  // IDEVAREFERENCES is the ID of the Reference (like ID_EVA_REFERENCES in EVENTS)
  refCDSnapshot.IDEVAREFERENCES = IDRiferimento
  refCDSnapshot.Timestamp = now()
   
  if (QappCore.Loggeduser)
  {
    refCDSnapshot.IDUTENTE = QappCore.Loggeduser.IDUTENTE
  }
   
  // if parent instance is ModelloEvento we set Y otherwise no
  boolean isModelloEventoParentInstance = ModelloEvento.isMyInstance(parent)
  refCDSnapshot.ISMODELLO = if(isModelloEventoParentInstance, Yes, No)
   
  return refCDSnapshot
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public void Riferimento.createAndSaveCdataSnapshots(
  optional boolean skipSaveToDb = 0 // for unit tests
)
{
  RefCDSnapshot refCDSnap = null
  if (ReferenceCDIsModified)
  {
     
    refCDSnap = this.CreateRefCDsnapshot()
    refCDSnap = cast(DatoPersonalizzatoSnapshot.factory(this))
    for each DatoPersonalizzato dp in RdatiPersonalizzati
    {
      refCDSnap.setValue(dp)
    }
    if (!(skipSaveToDb))
    {
      refCDSnap.saveToDB(1, ...)
    }
    RefCDSnapshot.add(refCDSnap)
     
    // after the saveToDb of refCDSnapshot collection the referenceCDisMOdified could be reset to false. It will return true in case of Mod of RdataPers of this riferimento.
    ReferenceCDIsModified = false
  }
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public void Riferimento.loadRDatiPersonalizzati()
{
  if (RdatiPersonalizzati.count() > 0)
    return 
   
  DatoPersonalizzato.initializeCollection(this)
   
//  IDCollection collRI of RiferimentoDatoPersonalizzatoInfo = new()
//  select into collection (collRI)
//  from 
//    RiferimentoDatoPersonalizzatoInfo // master table
//  where
//    IDTipoRiferimento == IDTipoRiferimento
//   
//  for each RiferimentoDatoPersonalizzatoInfo ri in collRI
//  {
//    RdatoPersonalizzato rp = cast(DatoPersonalizzato.factory(this, ri))
//    RdatiPersonalizzati.add(rp)
//  }
   
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public string Riferimento.getRDatoPersonalizzato(
  RiferimentoDatoPersonalizzatoInfo refDI // filter 
)
{
  string result = ""
   
  // load the collection RDataPers of the current Riferimento only if it has never been loaded
  int countRdatiPersLoaded = RdatiPersonalizzati.count()
  if (countRdatiPersLoaded == 0)
  {
    this.loadRDatiPersonalizzati()
  }
   
  // prepare the search filter
  IDCollection collRDatiPers of RdatoPersonalizzato = RdatiPersonalizzati
  int filterRefdiID = refDI.ID
   
  // filter the RDatoPers needed
  for each RdatoPersonalizzato rp in collRDatiPers
  {
    RiferimentoDatoPersonalizzatoInfo ri = cast(rp.getCustomDataInfo())
    if (ri.ID == filterRefdiID)
    {
      result = rp.getValueAsString()
    }
  }
   
  return result
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public void Riferimento.setRDatoPersonalizzato(
  RiferimentoDatoPersonalizzatoInfo rdi // 
  string value                          // 
)
{
  this.loadRDatiPersonalizzati()
   
  DatoPersonalizzato foundDatoPersonalizzato = null
  for each DatoPersonalizzato dp in RdatiPersonalizzati
  {
    RiferimentoDatoPersonalizzatoInfo ri = cast(dp.getCustomDataInfo())
    if (ri.ID == rdi.ID)
    {
      foundDatoPersonalizzato = dp
      break 
    }
  }
  string valueToBeInserted = value
  if (foundDatoPersonalizzato)
  {
    string debugInfo = formatMessage("Ref Cdata Setting value "|1" in Customdata "|2" (ID:|3)", value, rdi.Nome, rdi.ID, ...)
    QappCore.DTTLogMessage(debugInfo, ..., DTTInfo)
    foundDatoPersonalizzato.setValueAsString(valueToBeInserted)
    if (foundDatoPersonalizzato.updated)
    {
      ReferenceCDIsModified = true
      this.updated = true
    }
  }
  else 
  {
    QappCore.DTTLogMessage("Ref Custom Data NOT found in DatoPersonalizzato collection in Riferimenti, setting is not performed", ..., DTTWarning)
  }
   
  if (foundDatoPersonalizzato)
  {
    IDCollection observers of DatoPersonalizzato = foundDatoPersonalizzato.getObservers()
    for each DatoPersonalizzato observer in observers
    {
      DatoPersonalizzatoInfo observerCdataInfo = observer.getCustomDataInfo()
      if (observerCdataInfo.getCustomDataType() == CalcFloatCDataType)
      {
         observer.computeCalcFloat()
      }
      else if (observerCdataInfo.getCustomDataType() == ComboCDataType)
      {
         observer.computeDependsOnElements()
      }
    }
  }
   
}


// ──────────────────────────────────

// **************************************************************************************************************************************************
// this method loads the inner collection of RefCDSnapshot, if forcedReload is passed the collection is forcefully reloaded even if already in memory
// **************************************************************************************************************************************************
public void Riferimento.loadRefCdSnapshotCollection(
  optional boolean forcedReload = 0 // 
)
{
  if (forcedReload)
  {
    RefCDSnapshot.clear()
    RefCDSnapshot.loaded = false
  }
   
  if (!(RefCDSnapshot.loaded))
  {
    this.loadCollectionFromDB(RefCDSnapshot, ...)
  }
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public ReferenceType Riferimento.getReferenceType()
{
  ReferenceType rt = new()
  rt.IDTipoRiferimento = IDTipoRiferimento
  try 
  {
    rt.loadFromDB(...)
  }
  catch 
  {
    QappCore.DTTLogMessage(formatMessage("cannot load Reference type for this Riferimento ID: |1", IDRiferimento, ...), ...)
  }
  return rt
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public MainModule Riferimento.getSource()
{
  ReferenceType rt = new()
  MainModule source = new()
  if (nullValue(IDTipoRiferimento, 0) > 0)
  {
    rt.IDTipoRiferimento = IDTipoRiferimento
    try 
    {
      rt.loadFromDB(...)
      int kordAppDestination = rt.DestinationKordapp
      switch (kordAppDestination)
      {
         case Articoli:
           Articolo a = new()
           a.IDARTICOLO = IDEvento
           try 
           {
             a.loadFromDB(0)
           }
           catch 
           {
             a = null
           }
           source = a
         break
         case Eventi:
           Evento e = new()
           e.IDEVENTO = IDEvento
           try 
           {
             e.loadFromDB(0)
           }
           catch 
           {
             e = null
           }
           source = e
         break
         case ClientiFornitori:
           Clifor cf = new()
           cf.IDCONTO = IDEvento
           try 
           {
             cf.loadFromDB(0)
           }
           catch 
           {
             cf = null
           }
           source = cf
         break
         case Progetti:
           Progetto p = new()
           p.IDPROGETTO = IDEvento
           p.loadFromDB(0)
            
           source = p
         break
         case AltreAnagrafiche:
           AltreAnagrafiche c = new()
           c.ID = IDForAll
           try 
           {
             c.loadFromDB(0)
           }
           catch 
           {
             c = null
           }
           source = c
         break
         case Funzioni:
           Funzione f = new()
           f.IDFUNZIONE = IDEvento
           try 
           {
             f.loadFromDB(0)
           }
           catch 
           {
             f = null
           }
           source = f
         break
         case Personale:
           Personale p = new()
           p.IDDIPENDENTE = IDEvento
           try 
           {
             p.loadFromDB(0)
           }
           catch 
           {
             p = null
           }
           source = p
         break
         case Privati:
           Privati p = new()
           p.IDCITTADINI = IDEvento
           try 
           {
             p.loadFromDB(0)
           }
           catch 
           {
             p = null
           }
           source = p
         break
      }
    }
  }
  return source
}


// ──────────────────────────────────

// ***********************************************************************
// validate reference cdata by checking it's existence if it is Mandatory 
// ***********************************************************************
private boolean Riferimento.eachMandatoryReferenceCustomDataHasValue()
{
  IDCollection mandatoryRefCDs of RiferimentoDatoPersonalizzatoInfo = this.getReferenceMandatoryCDataInfo()
   
  boolean mandatoryRefCDHasValue = true
  for each RiferimentoDatoPersonalizzatoInfo ri in mandatoryRefCDs
  {
    string mandataoryRefCDValue = this.getRDatoPersonalizzato(ri)
    if (mandataoryRefCDValue == "")
    {
      mandatoryRefCDHasValue = false
      QappCore.DTTLogMessage(formatMessage("The value for refcdata field (|1) not found in riferimento (|2).", ri.Nome, ri.ID, ...), ..., DTTWarning)
    }
  }
   
  return mandatoryRefCDHasValue
}


// ──────────────────────────────────

// ****************************************************************
// get all mandatory Reference CD Information (EVA_REFERENCE_CDATA)
// ****************************************************************
public IDCollection Riferimento.getReferenceMandatoryCDataInfo()
{
  IDCollection mandatoryRefCD of RiferimentoDatoPersonalizzatoInfo = new()
   
  // load the referenceType...
  ReferenceType rt = ReferenceType.get(IDTipoRiferimento)
   
  if (rt)
  {
    // load the collection of refCdataInfo (we make an explcit call to avoid to use after load)
    rt.loadRefCdataInfo()
     
    // ...check for Obbligatorio oneach refcdata
    for each RiferimentoDatoPersonalizzatoInfo ri in rt.DatipersRiferimenti
    {
      if (ri.Obbligatorio == Yes)
      {
         mandatoryRefCD.add(ri)
      }
    }
  }
   
   
  return mandatoryRefCD
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Riferimento.computeRuntimeCustomData(
  Riferimento source // 
)
{
  for (int i = 1; i <= RdatainfoMapping.length(); i = i + 1)
  {
    string propertyCode = formatMessage("VALUE|1", i, ...)
    RiferimentoDatoPersonalizzatoInfo ri = (RiferimentoDatoPersonalizzatoInfo)RdatainfoMapping.getObject(propertyCode)
    if (ri and ri.IDTipoRiferimento > 0)
    {
      int currentValuePropertyIndex = source.getPropertyIndex(propertyCode, true, true, true, true)
      string currentValue = source.getProperty(currentValuePropertyIndex)
      this.setRDatoPersonalizzato(ri, currentValue)
    }
  }
}


// ──────────────────────────────────

// *************************************************************
// simple Getter of a private collection (needed for unit tests)
// *************************************************************
public IDCollection Riferimento.getRefCDSnapShotCollection()
{
  return RefCDSnapshot
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string Riferimento.getDestinationDescription()
{
  string destinationDescription = ""
  if (Destination == null)
  {
    this.computeDestination()
  }
   
  if (Destination != null)
  {
    destinationDescription = Destination.getDescription()
  }
   
  return destinationDescription
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Riferimento.computeAdditionalModuleValue()
{
  try 
  {
    ReferenceType ri = ReferenceType.get(IDTipoRiferimento)
    switch (ri.SourceKordapp)
    {
      case ClientiFornitori:
         Contatto c = Contatto.get(IDCONTATTO)
         AdditionalModuleValue = c.getFullDescription()
      break
      case Personale:
         Personale p = Personale.getFromDB(IDForAll, quickLoad)
         TipoPersonale tp = TipoPersonale.get(p.IDTIPOANAGR)
         AdditionalModuleValue = tp.DESCRTIPOANAGR
      break
      case AltreAnagrafiche:
         if (IDCONTATTO > 0)
         {
           Intervento i = Intervento.getFromDB(IDCONTATTO, quickLoad)
           AdditionalModuleValue = i.getDescription()
         }
      break
    }
  }
  catch 
  {
    QappCore.DTTLogMessage("Cannot compute additional module value", ...)
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static Riferimento Riferimento.get(
  int idRiferimento // 
)
{
  Riferimento r = new()
  r.IDRiferimento = idRiferimento
  try 
  {
    r.loadFromDB(...)
  }
  catch 
  {
    r = null
    QappCore.DTTLogMessage(formatMessage("unable to load from DB riferimento with ID |1", idRiferimento, ...), ..., DTTError)
  }
  return r
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Riferimento.updateDatoPersonalizzato(
  int fieldIndex        // 
  string valueFieldName // 
)
{
  string value = getProperty(fieldIndex)
   
  RiferimentoDatoPersonalizzatoInfo ri = (RiferimentoDatoPersonalizzatoInfo)RdatainfoMapping.getObject(valueFieldName)
   
  this.setRDatoPersonalizzato(ri, value)
   
  RdatoPersonalizzato rp = this.findMatchingDatoPersonalizzato(ri)
  if (rp)
  {
    rp.notifyUiResponsibleAboutUpdatedObsevers()
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public RdatoPersonalizzato Riferimento.findMatchingDatoPersonalizzato(
  RiferimentoDatoPersonalizzatoInfo refDataInfo // 
)
{
  RdatoPersonalizzato matchingRdatoPersonalizzato = null
  for each DatoPersonalizzato dp in RdatiPersonalizzati
  {
    RiferimentoDatoPersonalizzatoInfo ri = cast(dp.getCustomDataInfo())
    if (ri.ID = refDataInfo.ID)
    {
      matchingRdatoPersonalizzato = (RdatoPersonalizzato)dp
      break 
    }
  }
  if (matchingRdatoPersonalizzato == null)
  {
    QappCore.DTTLogMessage(formatMessage("Matching RdatoPersonalizzato not found for RefDataInfo (ID: |1)", refDataInfo.ID, ...), ..., DTTWarning)
  }
  return matchingRdatoPersonalizzato
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public int Riferimento.getIndexOfValueField(
  RiferimentoDatoPersonalizzatoInfo refdataInfo // 
)
{
  int fieldIndex = 0
  for (int i = 1; i <= RdatainfoMapping.length(); i = i + 1)
  {
    string fieldName = formatMessage("VALUE|1", i, ...)
     
    RiferimentoDatoPersonalizzatoInfo rdataInfoInMap = (RiferimentoDatoPersonalizzatoInfo)RdatainfoMapping.getObject(fieldName)
    if (rdataInfoInMap.ID == refdataInfo.ID)
    {
      fieldIndex = getPropertyIndex(fieldName, true, true, true, true)
      QappCore.DTTLogMessage(formatMessage("Field ID: |1,Name: |2 found at Index: |3, FieldName=|4", refdataInfo.ID, refdataInfo.Nome, fieldIndex, fieldName, ...), 67676767, ...)
      break 
    }
  }
  if (fieldIndex == 0)
  {
    QappCore.DTTLogMessage(formatMessage("Index for RefDataInfo Id: |1 not found in RDataInfoMapping", refdataInfo.ID, ...), ..., DTTWarning)
  }
  return fieldIndex
}


// ──────────────────────────────────

// ***********************************************************************
// Event raised to the document to determine the value of a named property
// ***********************************************************************
event Riferimento.OnGetNamedPropertyValue(
  string PropertyName        // The name of the named property whose value to retrieve.
  inout string PropertyValue // An output parameter containing the value of the named property.
)
{
  if (NamedProperties.getValue(PropertyName) != null)
  {
    PropertyValue = NamedProperties.getValue(PropertyName)
    return 
  }
  if (PropertyName == "REF_NAME")
  {
    string RefName = ""
    select into variables (found variable)
      set RefName = REFDESCRIPTION
    from 
      TipiRiferimento // master table
    where
      IDTipoRiferimento = IDTipoRiferimento
     
    PropertyValue = RefName
  }
   
  if (PropertyName = "DESCRIPTION")
  {
    int:referenceTypeKordModules vSourceKordappTipoRiferimento = 0
    select into variables (found variable)
      set vSourceKordappTipoRiferimento = SourceKordapp
    from 
      TipiRiferimento // master table
    where
      IDTipoRiferimento = IDTipoRiferimento
    // 
    switch (vSourceKordappTipoRiferimento)
    {
      case Personale:
         string vCOGNOMEEmployee = ""
         string vNOMEEmployee = ""
         // 
         select into variables (found variable)
           set vCOGNOMEEmployee = COGNOME
           set vNOMEEmployee = NOME
         from 
           Personale // master table
         where
           IDDIPENDENTE = IDForAll
          
         PropertyValue = Tools.Concatenate(vCOGNOMEEmployee, vNOMEEmployee, " ")
      break
      case ClientiFornitori:
         string vRAGIONESOCIALEGCFANAGRAFICA = ""
         select into variables (found variable)
           set vRAGIONESOCIALEGCFANAGRAFICA = RAGIONESOCIALE
         from 
           ClientiFornitori // master table
         where
           IDCONTO = IDForAll
         PropertyValue = vRAGIONESOCIALEGCFANAGRAFICA
      break
      case Eventi:
         string vNROEVENTO = ""
         string vDESCRTITOLO = ""
         int vIDEVENTO = 0
         select into variables (found variable)
           set vIDEVENTO = IDEVENTO
           set vNROEVENTO = NROEVENTO
           set vDESCRTITOLO = DESCRTITOLO
         from 
           Eventi // master table
         where
           IDEVENTO = IDForAll
          
         PropertyValue = Tools.Concatenate(toString(vIDEVENTO), vNROEVENTO, ...)
         if (vDESCRTITOLO != "")
         {
           PropertyValue = Tools.Concatenate(PropertyValue, vDESCRTITOLO, ...)
         }
      break
      case AltreAnagrafiche:
         string vCodeCespite = ""
         string vDescriptionCespite = ""
         select into variables (found variable)
           set vCodeCespite = Code
           set vDescriptionCespite = Description
         from 
           AltreAnagrafiche // master table
         where
           ID = IDForAll
          
         PropertyValue = Tools.Concatenate(vCodeCespite, vDescriptionCespite, ...)
      break
      case Progetti:
         string vCODPROGETTO = ""
         string vDESCRPROGETTO = ""
         select into variables (found variable)
           set vCODPROGETTO = CODPROGETTO
           set vDESCRPROGETTO = DESCRPROGETTO
         from 
           Progetti // master table
         where
           IDPROGETTO = IDForAll
          
         PropertyValue = Tools.Concatenate(vCODPROGETTO, vDESCRPROGETTO, ...)
      break
      case Articoli:
         string vCODARTICOLO = ""
         string vDESCRARTICOLO = ""
         select into variables (found variable)
           set vCODARTICOLO = CODARTICOLO
           set vDESCRARTICOLO = DESCRARTICOLO
         from 
           Articoli // master table
         where
           IDARTICOLO = IDForAll
          
         PropertyValue = Tools.Concatenate(vCODARTICOLO, vDESCRARTICOLO, ...)
      break
      case Funzioni:
         string vCODFUNZIONE = ""
         string vDESCRFUNZIONE = ""
         select into variables (found variable)
           set vCODFUNZIONE = CODFUNZIONE
           set vDESCRFUNZIONE = DESCRFUNZIONE
         from 
           Funzioni // master table
         where
           IDFUNZIONE = IDForAll
          
         PropertyValue = Tools.Concatenate(vCODFUNZIONE, vDESCRFUNZIONE, ...)
      break
      case Privati:
         string vCOGNOME = ""
         string vNOME = ""
         // 
         select into variables (found variable)
           set vCOGNOME = COGNOME
           set vNOME = NOME
         from 
           Privati // master table
         where
           IDCITTADINI = IDForAll
          
         PropertyValue = Tools.Concatenate(vCOGNOME, vNOME, " ")
      break
      default:
         PropertyValue = "Riferimento Description To Be Done"
      break
    }
  }
  else if (find(PropertyName, "REFCDATA", 0) > 0)
  {
    string s = mid(PropertyName, 9, 2)
    int indexOfRunTimeProperty = toInteger(s)
    IDMap idm = null
    idm = this.GetInfoCdataRef(IDTipoRiferimento)
     
    if (not(idm.containsKey(indexOfRunTimeProperty)))
    {
      return 
    }
     
    IDArray ida = (IDArray)idm.getObject(indexOfRunTimeProperty)
    //  
    // get
    int CurrentRefCDataFieldID = ida.getValue(0)
    int:refCdataType vRefCDataType = 0
     
    select into variables (found variable)
      set vRefCDataType = Datatype
    from 
      DatipersRiferimenti // master table
    where
      ID = CurrentRefCDataFieldID
    //  
    // Switch on DataType. Each variable is initialized to null so that if the query returns no value the field is correctly visualized as empty
    switch (vRefCDataType)
    {
      case IntegerRefDataType:
         int vRefCDataValue = null
         // 
         select into variables (found variable)
           set vRefCDataValue = Value
         from 
           ValoriDatipersRiferimentiINTEGER // master table
         where
           IDRiferimento = IDRiferimento
           IDDatopersRiferimento = CurrentRefCDataFieldID
         PropertyValue = convert(vRefCDataValue)
      break
      case CalcFloatRefDataType:
      case FloatRefDataType:
         decimal vRefCDataValue = null
         // 
         select into variables (found variable)
           set vRefCDataValue = Value
         from 
           ValoriDatipersRiferimentiFLOAT // master table
         where
           IDRiferimento = IDRiferimento
           IDDatopersRiferimento = CurrentRefCDataFieldID
         PropertyValue = convert(vRefCDataValue)
      break
      case BooleanRefDataType:
         boolean vRefCDataValue = null
         // 
         select into variables (found variable)
           set vRefCDataValue = if(Value = Yes, true, false)
         from 
           ValoriDatipersRiferimentiBOOLEAN // master table
         where
           IDRiferimento = IDRiferimento
           IDDatopersRiferimento = CurrentRefCDataFieldID
         PropertyValue = convert(vRefCDataValue)
      break
      case ComboRefDataType:
         string vRefCDataValue = null
         // 
         select into variables (found variable)
           set vRefCDataValue = Value
         from 
           ValoriDatipersRiferimentiCOMBO // master table
         where
           IDRiferimento = IDRiferimento
           IDDatopersRiferimento = CurrentRefCDataFieldID
         PropertyValue = convert(vRefCDataValue)
          
      break
      case TextRefDataType:
         string vRefCDataValue = null
         // 
         select into variables (found variable)
           set vRefCDataValue = Value
         from 
           ValoriDatipersRiferimentiTEXT // master table
         where
           IDRiferimento = IDRiferimento
           IDDatopersRiferimento = CurrentRefCDataFieldID
         PropertyValue = convert(vRefCDataValue)
      break
      case MemoRefDataType:
         string vRefCDataValue = null
         // 
         select into variables (found variable)
           set vRefCDataValue = Value
         from 
           ValoriDatipersRiferimentiMEMO // master table
         where
           IDRiferimento = IDRiferimento
           IDDatopersRiferimento = CurrentRefCDataFieldID
         PropertyValue = convert(vRefCDataValue)
      break
      case DateRefDataType:
         date vRefCDataValue = null
         // 
         select into variables (found variable)
           set vRefCDataValue = Value
         from 
           ValoriDatipersRiferimentiDATE // master table
         where
           IDRiferimento = IDRiferimento
           IDDatopersRiferimento = CurrentRefCDataFieldID
         PropertyValue = convert(vRefCDataValue)
      break
    }
  }
  //  
  // Computes at runtime the content of a CData field in Riferimenti
  NamedProperties.setValue(PropertyName, PropertyValue)
   
  // 
   
   
   
}


// ──────────────────────────────────

// ****************************************************************************
// Event raised to the document to determine the definition of a named property
// ****************************************************************************
event Riferimento.OnGetNamedPropertyDefinition(
  string PropertyName                     // The name of the named property whose definition is sought.
  IDPropertyDefinition PropertyDefinition // The object of the IDPropertyDefinition type that will be used to communicate the property definition to the caller.
)
{
  if (PropertyName = "REF_NAME")
  {
    PropertyDefinition.dataType = Character
  }
  if (PropertyName = "DESCRIPTION")
  {
    PropertyDefinition.dataType = Character
  }
  if (find(PropertyName, "REFCDATA", 0) > 0)
  {
    string s = mid(PropertyName, 9, 2)
    int indexOfRunTimeProperty = toInteger(s)
     
     
    //  
    // Scrivi un commento per questa variabile o premi backspace per eliminare questo commento
     
     
    int:refCdataType vRefCDataType = 0
     
    if (QappCore.IDTipoRiferimento > 0)
    {
      // Classes.IDTipoRiferimeno is used here it is passed as global by trusting the events sequeniality
      vRefCDataType = this.GetDataType(QappCore.IDTipoRiferimento, indexOfRunTimeProperty)
    }
    //  
    // Warning: the switch below assignes datatypes between DIFFERENT Value lists, do not be fooled!
    switch (vRefCDataType)
    {
      case IntegerRefDataType:
         PropertyDefinition.dataType = Integer
      break
      case CalcFloatRefDataType:
      case FloatRefDataType:
         PropertyDefinition.dataType = Float
      break
      case BooleanRefDataType:
         PropertyDefinition.dataType = Boolean
      break
      case DateRefDataType:
         PropertyDefinition.dataType = Date
      break
      default:
         PropertyDefinition.dataType = Character
      break
    }
  }
   
   
}


// ──────────────────────────────────

// ************************************************************
// Event raised by the document after loading from the database
// ************************************************************
event Riferimento.AfterLoad(
  boolean AlreadyLoaded     // A boolean parameter that specifies whether the document was previously loaded.
  IDArray LoadedCollections // IDArray type parameter that contains the collections loaded by the framework. If the value of the Already Loaded parameter is False, the Loaded Collections parameter is NULL.
)
{
   
  // set the Destination Object
  this.computeDestination()
  this.setOriginal()
   
  // set the destination description
  DestinationDescription = this.getDestinationDescription()
}


// ──────────────────────────────────

// *****************************************************************
// Event raised to the document to set the value of a named property
// *****************************************************************
event Riferimento.OnSetNamedPropertyValue(
  string PropertyName  // The name of the named property whose value to set.
  string PropertyValue // The value that should be set for the named property.
)
{
  if (IDForAll > 0)
  {
    NamedProperties.setValue("O_" + PropertyName, getNamedPropertyValue(PropertyName))
    NamedProperties.setValue(PropertyName, PropertyValue)
  }
   
   
   
   
}


// ──────────────────────────────────

// ************************************************************
// Event raised to the document during the validation procedure
// ************************************************************
event Riferimento.OnValidate(
  int:validateReasons Reason // An integer specifying the reason the document is being validated. The most common values are 0 to indicate complete validation before saving, or 1 to indicate a quick validati...
  inout boolean Error        // A boolean output parameter. If set to True, specifies an error condition and stops the validation immediately.
  inout boolean Skip         // A boolean output parameter. If set to True, does not check the rules for automatic validation of the document (see notes).
)
{
  for each DatoPersonalizzato dp in RdatiPersonalizzati
  {
    DatoPersonalizzatoInfo dpi = dp.getCustomDataInfo()
    QappCore.DTTLogMessage(formatMessage("Id: |1, caption: |2, modified: |3", dpi.getFieldID(), dpi.getCaption(), dp.updated, ...), 14141414, ...)
    if (!(dp.validate(...)))
    {
      Error = true
    }
  }
   
//  if (Reason == validateMandatoryReferenceCustomData)
//  {
//    boolean eachMandatoryReferenceCdataHasAValue = this.eachMandatoryReferenceCustomDataHasValue()
//    Error = !(eachMandatoryReferenceCdataHasAValue)
//    DevTools.ToBeReviewed("validation must be optimized for UI too...")
//    if (Error)
//    {
//      this.setPropertyError("Alcuni dati personalizzati dei riferimenti non hanno un valore, controlla e riprova", 0)
//    }
//  }
}


// ──────────────────────────────────

// **************************************************************************
// Event called to customize the Smart Lookup procedure at the document level
// **************************************************************************
event Riferimento.OnGetSmartLookup(
  Recordset RecordSet       // The recordset to be filled if the queries run during the Smart Lookup event are customized.
  inout int Level           // The Smart Lookup procedure performs multiple attempts, increasingly widening the search field. This integer parameter specifies the number of attempts made.
  inout boolean NullValue   // A boolean output parameter. If set to True, then NULL (no results found) will be returned to the panel.
  inout boolean Skip        // A boolean output parameter. If set to True, then the standard queries for the current level will not be executed.
  inout boolean Cancel      // A boolean output parameter. If set to True, the Smart Lookup procedure will be stopped.
  IDDocument CallerDocument // The document for which the smart lookup procedure is launched. It can be null.
)
{
   
  if (CallerDocument)
  {
    QappCore.DTTLogMessage(CallerDocument.typeName(), ...)
    if (this.isMyInstance(CallerDocument))
    {
      if (CallerDocument.parent)
      {
         QappCore.DTTLogMessage(CallerDocument.typeName(), ...)
      }
    }
  }
}


// ──────────────────────────────────



// ──────────────────────────────────

// ***********************************************************
// Event raised to the document when the transaction is closed
// ***********************************************************
event Riferimento.OnEndTransaction()
{
  if (RdatainfoMapping.length() > 0)
  {
    for (int i = 1; i <= RdatainfoMapping.length(); i = i + 1)
    {
      string valueFieldName = formatMessage("VALUE|1", i, ...)
      int valuexIndex = getPropertyIndex(valueFieldName, true, true, true, true)
      if (wasModified(valuexIndex))
      {
         this.updateDatoPersonalizzato(valuexIndex, valueFieldName)
      }
    }
  }
}


// ──────────────────────────────────

// ********************************************************************
// Event raised to the document before loading a collection of its type
// ********************************************************************
event Riferimento.BeforeLoadCollection(
  IDDocument Parent                     // The parent document that requested loading of the collection. It can be Null Object if the event is raised by the LoadCollectionByExample procedure.
  IDCollection Collection of IDDocument // The collection to be loaded.
  inout boolean Skip                    // A boolean output parameter. If set to True, standard loading will not be performed.
)
{
  // since references are based on a table that is specific to module and Kordapp so we need ad hoc query to load a collection
   
  if (!(MainModule.isMyInstance(Parent)))
  {
    return 
  }
  if (Collection.loaded)
  {
    return 
  }
  Skip = true
   
  MainModule parentMainModule = (MainModule)Parent
  string:flagYN isModello = if(ModelloEvento.isMyInstance(parentMainModule), Yes, No)
   
  int mainId = parentMainModule.getMainID()
  int:referenceTypeKordModules kordApp = parentMainModule.getKordApp()
  if (isModello == Yes)
  {
    kordApp = Eventi
  }
   
  IDCollection references of Riferimento = new()
  select into collection (references)
  from 
    Riferimento // master table
  where
    IDTipoRiferimento in subquery
      select // 
         IDTipoRiferimento
      from 
         TipiRiferimento // master table
      where
         TipiRiferimento.DestinationKordapp == kordApp
    IDEvento == mainId
    ISMODELLO == isModello
   
   
  // IMPORTANT: we do Move because otherwise the collection does not "sense" it has the document
  Collection.addAll(references, ...)
  Collection.loaded = true
   
  // since the collection is just retrieved we mark as original or it will look modified
  Collection.setOriginal()
}


// ──────────────────────────────────

// *************************************************************
// Event raised to the document at the end of the save procedure
// *************************************************************
event throws exception Riferimento.AfterSave(
  inout boolean Cancel // Boolean output parameter that makes it possible to cancel the entire save. It is equivalent to the Cancel parameter of the BeforeSave event.
)
{
  if (deleted)
    return 
   
  this.SaveCustomDataHistory()
}


// ──────────────────────────────────

// ****************************************
// Describe the purpose of this function...
// ****************************************
public IDCollection Riferimento.GetNonInitializedDatoPersonalizzatoCollection()
{
  IDCollection coll of DatoPersonalizzato = new()
   
  IDCollection refDataInfoCollection of RiferimentoDatoPersonalizzatoInfo = new()
  select into collection (refDataInfoCollection)
  from 
    RiferimentoDatoPersonalizzatoInfo // master table
  where
    IDTipoRiferimento == IDTipoRiferimento
   
  for each RiferimentoDatoPersonalizzatoInfo ri in refDataInfoCollection
  {
    ri.parseCmbValue()
    DatoPersonalizzato rp = DatoPersonalizzato.factory(this, ri)
    coll.add(rp)
  }
  return coll
}


// ──────────────────────────────────

// ****************************************
// Describe the purpose of this function...
// ****************************************
public IDCollection Riferimento.GetDatoPersonalizzatoCollection()
{
  return RdatiPersonalizzati
}


// ──────────────────────────────────

// ****************************************
// Describe the purpose of this function...
// ****************************************
public void Riferimento.SetDatoPersonalizzatoCollection(
  IDCollection datopersonalizzatocollection of IDDocument // What is this parameter for?
)
{
  RdatiPersonalizzati.clear()
  RdatiPersonalizzati.addAll(datopersonalizzatocollection, true)
  RdatiPersonalizzati.loaded = true
}


// ──────────────────────────────────

// ****************************************
// Describe the purpose of this function...
// ****************************************
public void Riferimento.SetCustomData(
  string value              // What is this parameter for?
  IDDocument customDataInfo // What is this parameter for?
)
{
  this.setRDatoPersonalizzato(cast(customDataInfo), value)
}


// ──────────────────────────────────

// ****************************************
// Describe the purpose of this function...
// ****************************************
public void Riferimento.SetRenderingHelper(
  IDDocument renderingHelper // What is this parameter for?
)
{
  RiferimentoRenderingHelper = (RiferimentoRenderingHelper)renderingHelper
}


// ──────────────────────────────────

// ****************************************
// Describe the purpose of this function...
// ****************************************
public IDDocument Riferimento.GetRenderingHelper()
{
  return RiferimentoRenderingHelper
}


// ──────────────────────────────────

// ***********************************************************************************************************************
// interface method (used in MainModule and Riefermento class) to find out whether custom data history to be saved or not.
// It reads from CDATA_REVISIONI_OPTIONS table.
// ***********************************************************************************************************************
public boolean Riferimento.CustomdataHistoryToBeSaved()
{
  ReferenceType rt = ReferenceType.get(IDTipoRiferimento)
   
  int:kordapp currentKordApp = rt.DestinationKordapp
  string vREFCDATAHISTREVISIONIOPTIONS = ""
  select into variables (found variable)
    set vREFCDATAHISTREVISIONIOPTIONS = REFCDATAHIST
  from 
    CDATAREVISIONIOPTIONS // master table
  where
    KORDAPP == currentKordApp
   
  string:flagYN refCdataEnabled = vREFCDATAHISTREVISIONIOPTIONS
   
  return refCdataEnabled == Yes
}


// ──────────────────────────────────

// ***********************************************************
// interface method (used in mainmodule and riferimento class)
// saves the customdata history values
// ***********************************************************
public void Riferimento.SaveCustomDataHistory()
{
  if (this.CustomdataHistoryToBeSaved())
  {
    this.createAndSaveCdataSnapshots(...)
  }
   
}


// ──────────────────────────────────

// ********************************************************************************************************************************
// the owner deletes all the data about owned custom data: extend the method to delete dato presonalizzato collection and snapshots
// ********************************************************************************************************************************
public void Riferimento.DeleteOwnedDatoPersonalizzatoData()
{
  this.loadRDatiPersonalizzati()
   
  this.loadRefCdSnapshotCollection(true)
  for each RefCDSnapshot rcds in RefCDSnapshot
  {
    for each RefCDHistoryValue rcdhv in rcds.RefCDHistoryValue
    {
      rcdhv.deleted = true
    }
    rcds.RefCDHistoryValue.saveToDB(...)
     
    rcds.deleted = true
  }
   
  RefCDSnapshot.saveToDB(...)
   
  for each DatoPersonalizzato dp in RdatiPersonalizzati
  {
    dp.deleted = true
    dp.saveToDB(...)
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static ReferenceType ReferenceType.get(
  int idReferenceType // 
)
{
  ReferenceType rt = new()
  rt.IDTipoRiferimento = idReferenceType
  try 
  {
    rt.loadFromDB(0)
  }
  catch 
  {
    rt = null
    QappCore.DTTLogMessage(formatMessage("Unable to load reference type object for ID: |1", idReferenceType, ...), ...)
  }
  return rt
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static ReferenceType ReferenceType.create(
  int:kordapp destinationKordApp // 
  int:kordapp sourceKordApp      // 
  optional string nome = "Nuovo" // 
)
{
  ReferenceType rt = new()
  rt.init()
  rt.SourceKordapp = sourceKordApp
  rt.DestinationKordapp = destinationKordApp
  rt.Name = nome
  rt.loadCollections()
  return rt
}


// ──────────────────────────────────



// ──────────────────────────────────

// ****************************************************************
// get all mandatory Reference CD Information (EVA_REFERENCE_CDATA)
// ****************************************************************
public IDCollection ReferenceType.getMandatoryCdataInfo()
{
  IDCollection mandatoryRefCD of RiferimentoDatoPersonalizzatoInfo = new()
   
  // load the collection of refCdataInfo (we make an explcit call to avoid to use after load)
  this.loadRefCdataInfo()
   
  // ...check for Obbligatorio oneach refcdata
  for each RiferimentoDatoPersonalizzatoInfo ri in DatipersRiferimenti
  {
    if (ri.Obbligatorio == Yes)
    {
      mandatoryRefCD.add(ri)
    }
  }
  return mandatoryRefCD
}


// ──────────────────────────────────

// *********************************************************************
// get all reference types where sourceKord filtered with passed kordApp
// *********************************************************************
public static IDCollection ReferenceType.getReferenceTypes(
  int:kordapp kordApp // 
)
{
   
  IDCollection moduleWiseReferenceTypes of ReferenceType = new()
  select into collection (moduleWiseReferenceTypes)
  from 
    ReferenceType // master table
  where
    DestinationKordapp == kordApp
   
  // at this moment all the collections are not loaded, loadCollections method should be called to initialize them properly
  // this is for example done at showForm of individual forms for source and destination (to optimize for speed)
  return moduleWiseReferenceTypes
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public ReferenceType ReferenceType.duplicateRefType()
{
  ReferenceType rt = duplicate(0, parentCollection(), true, true, ...)
   
  for each RiferimentoDatoPersonalizzatoInfo ri in DatipersRiferimenti
  {
    RiferimentoDatoPersonalizzatoInfo refDataInfo = (RiferimentoDatoPersonalizzatoInfo)ri.duplicate(0, ...)
    refDataInfo.IDTipoRiferimento = rt.IDTipoRiferimento
    rt.DatipersRiferimenti.add(refDataInfo)
  }
  for each RefTypeToSourceModuleTypeLink rttsmtl in RefTypeToSourceModuleTypeLinks
  {
    RefTypeToSourceModuleTypeLink rttSourceModuleTypeLink = new()
    rttSourceModuleTypeLink.init()
    rttSourceModuleTypeLink.IDTIPO = rttsmtl.IDTIPO
    rttSourceModuleTypeLink.IDTipoRiferimento = rt.IDTipoRiferimento
    rt.RefTypeToSourceModuleTypeLinks.add(rttSourceModuleTypeLink)
  }
  for each RefTypeToDestinationModuleTypeLink rttdmtl in RefTypeToDestinationModuleTypeLinks
  {
    RefTypeToDestinationModuleTypeLink rttDestinationModuleTypeLink = new()
    rttDestinationModuleTypeLink.init()
    rttDestinationModuleTypeLink.IDCLASSE = rttdmtl.IDCLASSE
    rttDestinationModuleTypeLink.IDTipoRiferimento = rt.IDTipoRiferimento
    rt.RefTypeToDestinationModuleTypeLinks.add(rttDestinationModuleTypeLink)
  }
  rt.loadCollections()
  return rt
}


// ──────────────────────────────────

// ***************************************************************************************************
// populate transient collection using source and destination module type collection of reference type
// ***************************************************************************************************
public void ReferenceType.prepareSourceTransientCollection()
{
  if (SourceModuleTypes.isModified())
  {
    return 
  }
   
  SourceModuleTypes.clear()
  for each row (readonly)
  {
    select
      IDTIPOVMODULETIPI = IDTIPO
      DESCRTIPOVMODULETIPI = DESCRTIPO
    from 
      VMODULETIPI // master table
    where
      KORDAPP == SourceKordapp
    order by
      DESCRTIPO
     
    ReferenceModuleType rmt = ReferenceModuleType.create(IDTIPOVMODULETIPI, DESCRTIPOVMODULETIPI)
    for each RefTypeToSourceModuleTypeLink rttomt in RefTypeToSourceModuleTypeLinks
    {
      if (rttomt.IDTIPO == IDTIPOVMODULETIPI)
      {
         rmt.Selected = true
         break 
      }
    }
    SourceModuleTypes.add(rmt)
  }
  SourceModuleTypes.setOriginal()
}


// ──────────────────────────────────

// ***************************************************************************************************
// populate transient collection using source and destination module type collection of reference type
// ***************************************************************************************************
public void ReferenceType.prepareDestinationTransientCollection()
{
  if (DestinationModuleTypes.isModified())
  {
    return 
  }
   
  DestinationModuleTypes.clear()
   
  for each row (readonly)
  {
    select
      IDTIPOVMODULETIPI = IDTIPO
      DESCRTIPOVMODULETIPI = DESCRTIPO
    from 
      VMODULETIPI // master table
    where
      KORDAPP == DestinationKordapp
    order by
      DESCRTIPO
     
    ReferenceModuleType rmt = ReferenceModuleType.create(IDTIPOVMODULETIPI, DESCRTIPOVMODULETIPI)
    for each RefTypeToDestinationModuleTypeLink rttmtl in RefTypeToDestinationModuleTypeLinks
    {
      if (rttmtl.IDCLASSE == IDTIPOVMODULETIPI)
      {
         rmt.Selected = true
         break 
      }
    }
    DestinationModuleTypes.add(rmt)
  }
  DestinationModuleTypes.setOriginal()
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void ReferenceType.loadCollections()
{
  if (!(RefTypeToSourceModuleTypeLinks.loaded))
  {
    this.loadCollectionFromDB(RefTypeToSourceModuleTypeLinks, ...)
  }
  if (!(RefTypeToDestinationModuleTypeLinks.loaded))
  {
    this.loadCollectionFromDB(RefTypeToDestinationModuleTypeLinks, ...)
  }
   
  this.prepareSourceTransientCollection()
  this.prepareDestinationTransientCollection()
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public RefTypeToSourceModuleTypeLink ReferenceType.findInSourceModuleTypeLinks(
  int idReferenceType // 
  int idTipo          // 
)
{
  RefTypeToSourceModuleTypeLink rttsmtl = null
  for each RefTypeToSourceModuleTypeLink rttSourceModuleTypeLink in RefTypeToSourceModuleTypeLinks
  {
    if (idReferenceType == rttSourceModuleTypeLink.IDTipoRiferimento and idTipo == rttSourceModuleTypeLink.IDTIPO)
    {
      rttsmtl = rttSourceModuleTypeLink
    }
  }
  return rttsmtl
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public RefTypeToDestinationModuleTypeLink ReferenceType.findInDestinationModuleTypeLinks(
  int idReferenceType // 
  int idTipo          // 
)
{
  RefTypeToDestinationModuleTypeLink rttdmtl = null
  for each RefTypeToDestinationModuleTypeLink rttdestinationModuleTypeLink in RefTypeToDestinationModuleTypeLinks
  {
    if (idReferenceType == rttdestinationModuleTypeLink.IDTipoRiferimento and idTipo == rttdestinationModuleTypeLink.IDCLASSE)
    {
      rttdmtl = rttdestinationModuleTypeLink
    }
  }
  return rttdmtl
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void ReferenceType.handleSavingOfSourceModuleTypes()
{
  if (SourceModuleTypes.isModified())
  {
    for each ReferenceModuleType rmt in SourceModuleTypes
    {
      if (rmt.isModified(...))
      {
         if (rmt.Selected)
         {
           RefTypeToSourceModuleTypeLink rttsmtl = new()
           rttsmtl.init()
           rttsmtl.IDTIPO = rmt.IdType
           rttsmtl.IDTipoRiferimento = IDTipoRiferimento
           RefTypeToSourceModuleTypeLinks.add(rttsmtl)
         }
         else 
         {
           RefTypeToSourceModuleTypeLink rttsmtl = this.findInSourceModuleTypeLinks(IDTipoRiferimento, rmt.IdType)
           if (rttsmtl)
           {
             rttsmtl.deleted = true
           }
         }
      }
    }
    SourceModuleTypes.setOriginal()
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void ReferenceType.handleSavingOfDestinationModuleTypes()
{
  if (DestinationModuleTypes.isModified())
  {
    for each ReferenceModuleType rmt in DestinationModuleTypes
    {
      if (rmt.isModified(...))
      {
         if (rmt.Selected)
         {
           RefTypeToDestinationModuleTypeLink rttdmtl = new()
           rttdmtl.init()
           rttdmtl.IDCLASSE = rmt.IdType
           rttdmtl.IDTipoRiferimento = IDTipoRiferimento
           RefTypeToDestinationModuleTypeLinks.add(rttdmtl)
         }
         else 
         {
           RefTypeToDestinationModuleTypeLink rttdmtl = this.findInDestinationModuleTypeLinks(IDTipoRiferimento, rmt.IdType)
           if (rttdmtl)
           {
             rttdmtl.deleted = true
           }
         }
      }
    }
    SourceModuleTypes.setOriginal()
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void ReferenceType.manageSourceTypeSelection(
  boolean selectionValue // 
)
{
  for each ReferenceModuleType rmt in SourceModuleTypes
  {
    rmt.Selected = selectionValue
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void ReferenceType.manageDestinationTypeSelection(
  boolean selectionValue // 
)
{
  for each ReferenceModuleType rmt in DestinationModuleTypes
  {
    rmt.Selected = selectionValue
  }
}


// ──────────────────────────────────

// ******************************************************************************************
// returns a recordset with 3 fields:
// kordapp (mainly for grouping like to create popupmenu at runtime)
// id eva refrence types
// ref type description
// 
// every mainmodule have sepcific refTypes available to it
// e.g.: if a reftype is common it will be visible, then it depends on the  module type links
// ******************************************************************************************
public static Recordset ReferenceType.getAvailableReferenceTypesForSpecificMainModule(
  MainModule mainModule // 
)
{
  Recordset availableReferenceTypes = new()
  int:kordapp destinationKordAppForQuery = if(mainModule.getKordApp() == ModelliEventi, Eventi, mainModule.getKordApp())
  int typeId = mainModule.getTypeID()
  select into recordset (availableReferenceTypes)
    IDTipoRiferimento as ID_EVA_REFERENCE_TYPES
    Name as REF_NAME
    SourceKordapp as KORD_APP
    "" as DECODED_KORD_APP
  from 
    TipiRiferimento // master table
  where
    DestinationKordapp == destinationKordAppForQuery
    ISCOMMONCLASS == Yes or exists(subquery)
      select top 1 // 
         IDEVAREFCLASSTYPE
      from 
         RefTypeToDestinationModuleTypeLinks // master table
      where
         RefTypeToDestinationModuleTypeLinks.IDCLASSE == typeId
         TipiRiferimento.IDTipoRiferimento == RefTypeToDestinationModuleTypeLinks.IDTipoRiferimento
   
  // populate the decoded kord app field...
  availableReferenceTypes.moveFirst()
  while (!(availableReferenceTypes.EOF()))
  {
    int:kordapp currentKordApp = toInteger(availableReferenceTypes.getFieldValue("KORD_APP"))
    string decodedKordApp = decode(currentKordApp, Kordapp)
    availableReferenceTypes.setFieldValue("DECODED_KORD_APP", decodedKordApp)
     
    availableReferenceTypes.moveNext()
  }
   
  // ... and finally sort by kord app and ref name
  availableReferenceTypes.addSortCriteria("DECODED_KORD_APP")
  availableReferenceTypes.addSortCriteria("REF_NAME")
  availableReferenceTypes.doSort()
   
  return availableReferenceTypes
}


// ──────────────────────────────────



// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event ReferenceType.OnInit()
{
  IDTipoRiferimento = Sequence.getNextSequence(EVAN_ID_REF_TYP, ...)
  SEQUENZA = 1
  ATTIVO = Yes
  ISCOMMON = No
  ISCOMMONCLASS = No
   
}


// ──────────────────────────────────

// **************************************************************************************
// Event raised to the document during the initial steps of the document saving procedure
// **************************************************************************************
event throws exception ReferenceType.BeforeSave(
  inout boolean Skip         // A boolean output parameter. If set to True, tells the framework not to perform operations on the database, because they will be managed independently.
  inout boolean Cancel       // A boolean output parameter. If set to True, tells the framework to cancel the save procedure due to errors that occurred. Since the entire document is saved in the same transa...
  int:beforeSavePhases Phase // An integer from 0 to 3 that specifies the progress status of the save procedure as detailed at the beginning of the article.
)
{
  if (Phase == PreSave)
  {
    this.handleSavingOfSourceModuleTypes()
    this.handleSavingOfDestinationModuleTypes()
    if (deleted)
    {
      for each RiferimentoDatoPersonalizzatoInfo ri in DatipersRiferimenti
      {
         ri.deleted = true
         ri.saveToDB(0, ...)
      }
      if (!(RefTypeToSourceModuleTypeLinks.loaded))
      {
         this.loadCollectionFromDB(RefTypeToSourceModuleTypeLinks, ...)
      }
      for each RefTypeToSourceModuleTypeLink rttsmtl in RefTypeToSourceModuleTypeLinks
      {
         rttsmtl.deleted = true
         rttsmtl.saveToDB(0, ...)
      }
      if (!(RefTypeToDestinationModuleTypeLinks.loaded))
      {
         this.loadCollectionFromDB(RefTypeToDestinationModuleTypeLinks, ...)
      }
      for each RefTypeToDestinationModuleTypeLink rttdmtl in RefTypeToDestinationModuleTypeLinks
      {
         rttdmtl.deleted = true
         rttdmtl.saveToDB(0, ...)
      }
       
    }
  }
}


// ──────────────────────────────────



// ──────────────────────────────────

// ****************************************************
// Raised to the document during duplication operations
// ****************************************************
event ReferenceType.OnDuplicate(
  IDDocument SourceDocument // This parameter contains the original document when duplicating the document header. Otherwise, its value is Null Object. This way, it is possible to distinguish the two cases, ...
)
{
  IDTipoRiferimento = Sequence.getNextSequence(EVAN_ID_REF_TYP, ...)
   
//  ReferenceType source = (ReferenceType)SourceDocument
//  Name = source.Name + " (copia)"
//  REFDESCRIPTION = source.REFDESCRIPTION
//  SEQUENZA = source.SEQUENZA
//  ATTIVO = source.ATTIVO
//  SourceKordapp = source.SourceKordapp
//  ISCOMMON = source.ISCOMMON
//  ISCOMMONCLASS = source.ISCOMMONCLASS
//  DestinationKordapp = source.DestinationKordapp
//  LinksTabName = source.LinksTabName
//  DESCRWIDTH = source.DESCRWIDTH
//  DETAILWIDTH = source.DETAILWIDTH
//  CDATAZOOM = source.CDATAZOOM
//   
//  for each RiferimentoDatoPersonalizzatoInfo ri in source.DatipersRiferimenti
//  {
//    RiferimentoDatoPersonalizzatoInfo refDataInfo = (RiferimentoDatoPersonalizzatoInfo)ri.duplicate(0, ...)
//    refDataInfo.IDTipoRiferimento = IDTipoRiferimento
//  }
//  for each RefTypeToSourceModuleTypeLink rttsmtl in source.RefTypeToSourceModuleTypeLinks
//  {
//    RefTypeToSourceModuleTypeLink rttSourceModuleTypeLink = new()
//    rttSourceModuleTypeLink.init()
//    rttSourceModuleTypeLink.IDTIPO = rttsmtl.IDTIPO
//    rttSourceModuleTypeLink.IDTipoRiferimento = IDTipoRiferimento
//  }
//  for each RefTypeToDestinationModuleTypeLink rttdmtl in source.RefTypeToDestinationModuleTypeLinks
//  {
//    RefTypeToDestinationModuleTypeLink rttDestinationModuleTypeLink = new()
//    rttDestinationModuleTypeLink.init()
//    rttDestinationModuleTypeLink.IDCLASSE = rttdmtl.IDCLASSE
//    rttDestinationModuleTypeLink.IDTipoRiferimento = IDTipoRiferimento
//  }
//  IDCollection refTypes of ReferenceType = parentCollection()
//  refTypes.add(this)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static ReferenceModuleType ReferenceModuleType.create(
  int idType   // 
  string descr // 
)
{
  ReferenceModuleType mt = new()
  mt.init()
  mt.IdType = idType
  mt.Description = descr
  return mt
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event ReferenceModuleType.OnInit()
{
  Selected = false
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void SearchResult.add(
  BaseDataType baseDataType // 
)
{
  BaseDataTypeClass.add(baseDataType)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void SearchResult.addInteger(
  int intValue // 
)
{
  IntDataType bdtc = BaseDataType.createInteger(intValue)
  BaseDataTypeClass.add(bdtc)
}


// ──────────────────────────────────



// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static IntDataType BaseDataType.createInteger(
  int intValue // 
)
{
  IntDataType idtc = new()
  idtc.IntegerValue = intValue
   
  return idtc
}


// ──────────────────────────────────



// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void QualibusDocument.addImportMetadato(
  string:metadataHeaders caption   // 
  int propertyIndex                // 
  boolean mandatory                // 
  optional MainModuleDatoPersonalizzatoInfo cdataFieldInfo // 
  optional string lookupClass = "" // 
  optional IDMap lookupMap         // 
)
{
  int nextSequence = ImportMetadata.count()
   
   
  ImportMetadato dm = ImportMetadato.create(nextSequence, caption, propertyIndex, cdataFieldInfo, mandatory, lookupClass, lookupMap)
  ImportMetadata.add(dm)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void QualibusDocument.computeStandardImportMetadata()
{
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void QualibusDocument.ComputeCdataImportMetadata()
{
  MainModule mm = this.getAsMainModule()
   
  if (!(mm))
  {
    return 
  }
   
  // null is passed to forcefully get all sections ignoring a specific user (so no visibility is involved)
  IDCollection availableSections of CdataSection = mm.GetAvailableCustomDataSections(null)
  for each CdataSection cs in availableSections
  {
    for each MainModuleDatoPersonalizzatoInfo cfi in cs.CDATAFIELDSINFO
    {
      if (cfi.Active == Yes)
      {
         string caption = lower(cfi.Caption)
         caption = replace(caption, " ", "_")
         string lookupClass = ""
         switch (cfi.Type)
         {
           case ArticoloCDataType:
             lookupClass = Articolo.className(...)
           break
           case CliForCDataType:
             lookupClass = Clifor.className(...)
           break
//           case ContattoCDataType:
//             lookupClass = AltreAnagrafiche.className(...)
//           break
           case EventoCDataType:
             lookupClass = Evento.className(...)
           break
           case AltraAnagraficaCDataType:
             lookupClass = AltreAnagrafiche.className(...)
           break
           case PersonaleCDataType:
             lookupClass = Personale.className(...)
           break
           case ProgettoCDataType:
             lookupClass = Progetto.className(...)
           break
           case FunzioneCDataType:
             lookupClass = Funzione.className(...)
           break
         }
         this.addImportMetadato(caption, 0, cfi.Mandatory == Yes, cfi, lookupClass, ...)
      }
    }
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string QualibusDocument.getImportHeaders(
  int typeID // 
)
{
  this.ComputeImportMetadata()
   
  string headers = ""
  for each ImportMetadato dm in ImportMetadata
  {
    headers = SH.Concat(headers, dm.Caption, ";")
  }
  return headers
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void QualibusDocument.ComputeImportMetadata()
{
  ImportMetadata.clear()
  this.computeStandardImportMetadata()
  this.ComputeCdataImportMetadata()
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public IDMap QualibusDocument.getMap(
  string dateFormat // 
)
{
  IDMap exportMap = new()
  IDDocumentStructure structure = getStructure()
   
  MainModule mm = this.getAsMainModule()
   
  if (!(mm))
    return null
  mm.loadDatiPers()
  for each ImportMetadato im in ImportMetadata
  {
    string propertyValue = ""
    int propertyIndex = im.PropertyIndex
    if (im.MainModuleDatoPersonalizzatoInfo == null)
    {
      IDPropertyDefinition propDefinition = structure.getPropertyDefinition(propertyIndex)
      if (propDefinition.dataType == DateTime)
      {
         date time d = getProperty(propertyIndex)
         propertyValue = format(d, dateFormat, ...)
      }
      else 
      {
         propertyValue = toString(getProperty(propertyIndex))
      }
    }
    else 
    {
      MainModuleDatoPersonalizzatoInfo cfi = im.MainModuleDatoPersonalizzatoInfo
      propertyValue = mm.getDatoPersonalizzatoValueAsString(cfi)
    }
    exportMap.setValue(im.Caption, propertyValue)
  }
  return exportMap
}


// ──────────────────────────────────

// *******************************************************************
// Returns the ID of the object corresponding to the given description
// *******************************************************************
public int QualibusDocument.IDfromDescription(
  string description // 
)
{
   
  return 0
}


// ──────────────────────────────────

// **********************************************************************************************************************************
// Given a IDMap (usually from Qimport) containing ("metadata_caption","value") valorizes object properties converting them if needed
// **********************************************************************************************************************************
public void QualibusDocument.loadFromMap(
  IDMap map         // 
  string dateFormat // 
)
{
  MainModule mm = this.getAsMainModule()
  boolean extendsMainModule = mm != null
  QappCore.DTTLogMessage(ImportMetadata.count(), ...)
  IDDocumentStructure idds = getStructure()
  for each ImportMetadato im in ImportMetadata
  {
    string propertyValue = map.getValue(im.Caption)
    QappCore.DTTLogMessage(formatMessage("Setting - metaCaption:'|1' value:'|2'", im.Caption, propertyValue, ...), ...)
    if (nullValue(propertyValue, "") != "")
    {
      // Main data
      int propertyIndex = im.PropertyIndex
      if (im.MainModuleDatoPersonalizzatoInfo == null)
      {
         IDPropertyDefinition property = idds.getPropertyDefinition(propertyIndex)
         if (property.dataType == DateTime)
         {
           date d = DH.stringToDate(propertyValue, dateFormat)
           date time d1 = toDateTime(d)
           mm.setProperty(propertyIndex, d1)
         }
         else if (property.dataType == Integer)
         {
           mm.setProperty(propertyIndex, toInteger(propertyValue))
         }
         else if (property.dataType == Character)
         {
           int maxLength = property.maxLength
           propertyValue = trim(propertyValue)
           propertyValue = left(propertyValue, maxLength)
           mm.setProperty(propertyIndex, propertyValue)
         }
         else if (property.dataType == FixedCharacter and property.maxLength == 1)
         {
           switch (lower(propertyValue))
           {
             case "false":
             case "falso":
             case "0":
                propertyValue = No
             break
             case "-1":
             case "true":
             case "vero":
                propertyValue = Yes
             break
           }
           mm.setProperty(propertyIndex, propertyValue)
         }
         else 
         {
           mm.setProperty(propertyIndex, propertyValue)
         }
      }
      else 
         ...
    }
  }
}


// ──────────────────────────────────



// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void QualibusDocument.removeMetadata(
  string caption // 
)
{
  ImportMetadata.moveFirst()
   
  while (!(ImportMetadata.isEof()))
  {
    ImportMetadato im = (ImportMetadato)ImportMetadata.getAt()
    if (im.Caption == caption)
    {
      ImportMetadata.removeAt()
      break 
    }
    ImportMetadata.moveNext()
  }
}


// ──────────────────────────────────

// ***************************************************************************************************************************************
// this procedure is needed in case of loadFromMap needs more data or it require module specific procedures to intanciate the final object
// ***************************************************************************************************************************************
public static QualibusDocument QualibusDocument.prepareInstanceForQsyncro(
  QualibusDocument emptyObject                     // 
  IDMap data                                       // 
  optional IDCollection metadata of ImportMetadato // 
  optional string dateFormat = ""                  // 
)
{
  QualibusDocument qd = new()
  if (Evento.isMyInstance(emptyObject))
  {
    qd = cast(Evento.prepareEventoForQsyncro(data, metadata, emptyObject, dateFormat))
  }
  else 
  {
    qd = emptyObject
    qd.loadFromMap(data, dateFormat)
  }
  return qd
}


// ──────────────────────────────────

// ****************************************
// carica la collection Dati personalizzati
// ****************************************
public void MainModule.loadDatiPers()
{
  if (DatiPersonalizzati.loaded)
    return 
   
  DatoPersonalizzato.initializeCollection(this)
   
  if (!(inserted))
  {
    DatiPersonalizzati.setOriginal()
  }
}


// ──────────────────────────────────

// *****************************************************************************************************************************************************************
// sets the inputValue (passed as string as a kind of "variant" datatype) in the passed CdataFieldInfo: the matching custom data in mainmodule collection is updated
// *****************************************************************************************************************************************************************
public void MainModule.setDatoPersonalizzato(
  string inputValue                                // Scrivi un commento per questo parametro o premi backspace per eliminare questo commento
  MainModuleDatoPersonalizzatoInfo inDataFieldInfo // 
)
{
  this.loadDatiPers()
   
  DatoPersonalizzato foundDatoPersonalizzato = null
  for each DatoPersonalizzato dpb in DatiPersonalizzati
  {
    MainModuleDatoPersonalizzatoInfo cdataFieldInfo = cast(dpb.getCustomDataInfo())
    if (cdataFieldInfo.IDCDATAFLD = inDataFieldInfo.IDCDATAFLD)
    {
      foundDatoPersonalizzato = dpb
      break 
    }
  }
   
  string valueToBeInserted = inputValue
  if (foundDatoPersonalizzato)
  {
    string debugInfo = formatMessage("Setting value "|1" in Custom Data "|2" (ID: |3)", inputValue, inDataFieldInfo.Caption, inDataFieldInfo.IDCDATAFLD, ...)
    QappCore.DTTLogMessage(debugInfo, ..., DTTInfo)
    foundDatoPersonalizzato.setValueAsString(valueToBeInserted)
    if (foundDatoPersonalizzato.updated)
    {
      // so the data is modified
      CdataIsModified = true
      this.updated = true
    }
  }
  else 
  {
    QappCore.DTTLogMessage("Custom Data NOT found in DatoPersonalizzato collection in mainModule, setting is not performed", ..., DTTWarning)
  }
   
  if (foundDatoPersonalizzato)
  {
    IDCollection observers of DatoPersonalizzato = foundDatoPersonalizzato.getObservers()
     
    for each DatoPersonalizzato observer in observers
    {
      DatoPersonalizzatoInfo observerCdataFieldInfo = cast(observer.getCustomDataInfo())
      if (observerCdataFieldInfo.getCustomDataType() == CalcFloatCDataType)
      {
         observer.computeCalcFloat()
      }
      else if (observerCdataFieldInfo.getCustomDataType() == ComboCDataType)
      {
         observer.computeDependsOnElements()
      }
    }
  }
}


// ──────────────────────────────────

// ************************************************************************
// the custom data value as string of the passed CDataFieldInfo is returned
// ************************************************************************
public string MainModule.getDatoPersonalizzatoValueAsString(
  MainModuleDatoPersonalizzatoInfo cDataFieldInfo // 
)
{
   
  // find cdataFieldInfo in all Dati personalizzati and ask it the value
   
  DatoPersonalizzato foundDatoPersonalizzato = null
   
  for each DatoPersonalizzato dpb in DatiPersonalizzati
  {
    MainModuleDatoPersonalizzatoInfo cdataFieldInfo = cast(dpb.getCustomDataInfo())
    if (cdataFieldInfo)
    {
      if (cdataFieldInfo.IDCDATAFLD = cDataFieldInfo.IDCDATAFLD)
      {
         foundDatoPersonalizzato = dpb
         break 
      }
    }
  }
   
   
  string returnValue = ""
  if (foundDatoPersonalizzato)
  {
    returnValue = foundDatoPersonalizzato.getValueAsString()
    QappCore.DTTLogMessage(formatMessage("DatoPersonalizzato '|1' found,  retrieved value: '|2'", cDataFieldInfo.Caption, returnValue, ...), ..., DTTInfo)
  }
  else 
  {
    QappCore.DTTLogMessage(formatMessage("DatoPersonalizzato '|1' not found, impossible to retrieve the value", cDataFieldInfo.Caption, ...), ..., DTTInfo)
  }
   
   
   
  return returnValue
}


// ──────────────────────────────────

// *************************************************************************************************************************
// All the available CDSection of the current TypeID and kordApp are returned in a collection 
// For each CDataSection we load its collection of CDataInfo
// 
// Rules: 
//  - common sections 
//  - sections assigned to own module type
//  - look only the active sections 
// 
// 
// the utente parameter is needed to specify for which user to check, if utente is not passed all the sections are returned 
// 
// IMPORTANT: This method will work in all the childrens
// NOTE: Evento overrides this method!
// *************************************************************************************************************************
public IDCollection MainModule.GetAvailableCustomDataSections(
  optional Utente utente // 
)
{
   
  IDCollection availableCDSectionResult of CdataSection = new()
   
  // 1. WE NEED TO FIND OUT THE UTENTE FOR WHICH WE WANT TO GET THE AVAILABLE SECTIONS
  Utente involvedUtente = this.retrieveInvolvedUtente(utente)
   
  // 2. GET ALL AVAILABLE=EXISTING SECTIONS FOR THIS MAIN MODULE
  IDCollection availableCDSections of CdataSection = this.retrieveAvailableCDSections()
   
   
  // 3. CHECK IF ALL SECTIONS SHOULD BE RETURNED
  boolean returnAllSections = this.shouldReturnAllCustomDataSections(involvedUtente)
   
  if (returnAllSections)
  {
    availableCDSectionResult = availableCDSections
  }
  else 
  {
    // 4. CHECK OF PERMESSI ON EACH SECTION
    availableCDSectionResult = this.getSectionsBasedOnPermessi(involvedUtente, availableCDSections)
  }
   
  // 5.LOAD ALL THE SECTIONS IN MEMORY
  for each CdataSection cs in availableCDSectionResult
  {
    // IMPORTANT: in this loadCollectionFromDb the event CdataFieldInfo.OnBeforeLoadCollection is involved and this will take care of loading alo the calc fields in memory even if the user cannot see them
     
    cs.loadCollectionFromDB(cs.CDATAFIELDSINFO, ...)
  }
   
  return availableCDSectionResult
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public CdataSnapshot MainModule.createSnapshot()
{
  CdataSnapshot cdataSnap = new()
  cdataSnap.init()
  int thisKordApp = this.getKordApp()
   
  cdataSnap.IDMODULERECID = this.getMainID()
  cdataSnap.Timestamp = now()
  cdataSnap.KORDAPP = thisKordApp
  if (QappCore.Loggeduser)
    cdataSnap.IDUTENTE = QappCore.Loggeduser.IDUTENTE
   
  boolean parentIsModello = ModelloEvento.isMyInstance(this)
  cdataSnap.ISMODELLO = if(parentIsModello, Yes, No)
   
  return cdataSnap
}


// ──────────────────────────────────

// *****************************************************
// if a utente is not passed the logged user is returned
// *****************************************************
private Utente MainModule.retrieveInvolvedUtente(
  Utente utente // 
)
{
  Utente involvedUtente = null
   
  if (!(utente))
    involvedUtente = QappCore.Loggeduser
  else 
    involvedUtente = utente
   
  return involvedUtente
}


// ──────────────────────────────────

// **************************************************************************************
// This method determines if all custom data sections should be returned for a given user
// **************************************************************************************
private boolean MainModule.shouldReturnAllCustomDataSections(
  Utente involvedUtente // 
)
{
  boolean returnAllSections = false
  if (involvedUtente)
  {
    if (involvedUtente.IDUTENTE > 0)
    {
      int:kordapp kordAppForWhichToCheckDatiPPrivilege = this.getKordApp()
       
      // specific arrangement for Modelli eventi case in which we use Eventi privilege
      if (kordAppForWhichToCheckDatiPPrivilege == ModelliEventi)
      {
         kordAppForWhichToCheckDatiPPrivilege = Eventi
      }
      returnAllSections = involvedUtente.hasSpecificPrivilege(kordAppForWhichToCheckDatiPPrivilege, DatiP)
    }
    else 
    {
      returnAllSections = true
    }
  }
  else 
  {
    // in case a null utente has been passed
    returnAllSections = true
  }
   
  return returnAllSections
}


// ──────────────────────────────────

// *********************************************************************************************************
// This method get the visible sections for given user based on individual persmission on the CData sections
// *********************************************************************************************************
private IDCollection MainModule.getSectionsBasedOnPermessi(
  Utente involvedUtenete                           // 
  IDCollection availableCDSections of CdataSection // 
)
{
  IDCollection sectionsBasedOnPermessi of CdataSection = new()
   
  QappCore.DTTLogMessage("User has no CdataPrivilege, individual permessi in sections will be considered", ..., DTTInfo)
  //  
  // consider individual privileges on section
   
   
  IDCollection sectionsLoggedUserCanSee of CDATASECTIONSPERMESSI = new()
   
  select into collection (sectionsLoggedUserCanSee)
  from 
    CDATASECTIONSPERMESSI // master table
  where
    IDUTENTE == involvedUtenete.IDUTENTE
   
   
  for each CdataSection cs in availableCDSections
  {
    boolean userCanSeeAvailableSectionThanksToApecificSectionPermesso = false
    for each CDATASECTIONSPERMESSI cdatasectionspermessi in sectionsLoggedUserCanSee
    {
      if (cs.IDCDATASEC == cdatasectionspermessi.IDCDATASEC)
      {
         sectionsBasedOnPermessi.add(cs)
         userCanSeeAvailableSectionThanksToApecificSectionPermesso = true
         break 
      }
    }
     
    // if the user cannot see a section because it should be visible but he has no DatiPers privilege and not a specific section permesso we mark the section as Skip Rendering so we do not render in the form but
    // it is in memory so calc(float) is computable
     
    if (!(userCanSeeAvailableSectionThanksToApecificSectionPermesso))
    {
      cs.markAsSkipRendering()
      sectionsBasedOnPermessi.add(cs)
    }
  }
   
  return sectionsBasedOnPermessi
   
}


// ──────────────────────────────────



// ──────────────────────────────────

// ***************************************************************************************************************************************************
// Returns the Description for the DatoPersonalizzato (for normal Cdata is the value, for Module Cdata is the same Description we see in Qualibus C/S)
// ***************************************************************************************************************************************************
public string MainModule.getDatoPersonalizzatoDescription(
  MainModuleDatoPersonalizzatoInfo cDataFieldInfo // 
)
{
   
   
  // find cdataFieldInfo in all Dati personalizzati and ask it the value
   
  MainModuleDatoPersonalizzato foundMainModuleDatoPersonalizzato = null
   
  for each DatoPersonalizzato dpb in DatiPersonalizzati
  {
    MainModuleDatoPersonalizzatoInfo cdataFieldInfo = cast(dpb.getCustomDataInfo())
    if (cdataFieldInfo.IDCDATAFLD = cDataFieldInfo.IDCDATAFLD)
    {
      foundMainModuleDatoPersonalizzato = (MainModuleDatoPersonalizzato)dpb
      break 
       
    }
  }
   
  string returnValue = ""
  if (foundMainModuleDatoPersonalizzato)
  {
    returnValue = foundMainModuleDatoPersonalizzato.getValueAsString()
  }
   
   
  return returnValue
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public IDCollection MainModule.getMandatoryCustomCdataFields()
{
  IDCollection mandatoryCdataFields of MainModuleDatoPersonalizzatoInfo = new()
   
  IDCollection availableCdataSections of CdataSection = this.GetAvailableCustomDataSections(...)
   
  for each CdataSection cs in availableCdataSections
  {
    if (!(cs.CDATAFIELDSINFO.loaded))
    {
      cs.loadCollectionFromDB(cs.CDATAFIELDSINFO, ...)
    }
    for each MainModuleDatoPersonalizzatoInfo cfi in cs.CDATAFIELDSINFO
    {
      if (cfi.Mandatory == Yes)
      {
         mandatoryCdataFields.add(cfi)
      }
    }
  }
   
  return mandatoryCdataFields
}


// ──────────────────────────────────

// *******************************************************************************************************************************
// in this method we check that there is value against Mandatory cdata field
// in errorDetail optional in out param we store the comma separate list of the involved fields
// scanAllDAta must be made true to scan all data (it will take longer but it will allow to retrieve all data in the error Detail)
// *******************************************************************************************************************************
private boolean MainModule.eachMandatoryCdataFieldHasValue(
  optional inout string errorDetail = "" // 
  optional boolean scanAllData = 0       // 
)
{
  IDCollection mandatoryCDFields of MainModuleDatoPersonalizzatoInfo = this.getMandatoryCustomCdataFields()
   
   
  boolean mandatoryCDFieldValueFound = true
   
  // in modelli eventi we do not want to check for mandator custom data: mandatory cdata can be left null so in evento then the mandatoriness check will be perfomed (at the moment of writing in modelli evento
  // only bypassCdataValidation is specialized)
  if (!(this.bypassCdataValidation()))
  {
    boolean errorDetailTooLong = false
     
    for each MainModuleDatoPersonalizzatoInfo cfi in mandatoryCDFields
    {
      string CDValue = this.getDatoPersonalizzatoValueAsString(cfi)
      if ((CDValue == "") or (isNull(CDValue)))
      {
         mandatoryCDFieldValueFound = false
         QappCore.DTTLogMessage(formatMessage("The customdata "|1" is mandatory, it must have a value.", cfi.Caption, ...), ..., DTTWarning)
         errorDetail = SH.Concat(errorDetail, cfi.Caption, ", " + CHR(10))
         errorDetailTooLong = length(errorDetail) > 100
         if (!(scanAllData) or errorDetailTooLong)
         {
           break 
         }
      }
    }
    if (errorDetailTooLong)
    {
      string extraTextAfterLastComma = SH.rightUpToDelimiter(errorDetail, ",", ...)
      string errorDetailPartToKeep = replace(errorDetail, extraTextAfterLastComma, "")
      errorDetail = errorDetailPartToKeep + ",..."
    }
  }
   
   
  return mandatoryCDFieldValueFound
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public DatoPersonalizzato MainModule.getMatchingDatoPersonalizzato(
  MainModuleDatoPersonalizzatoInfo cDataFieldInfo // 
)
{
   
   
  // find cdataFieldInfo in all Dati personalizzati return it
  // 
   
  DatoPersonalizzato foundDatoPersonalizzato = null
   
  for each DatoPersonalizzato dpb in DatiPersonalizzati
  {
    MainModuleDatoPersonalizzatoInfo cdataFieldInfo = cast(dpb.getCustomDataInfo())
    if (cdataFieldInfo.IDCDATAFLD = cDataFieldInfo.IDCDATAFLD)
    {
      foundDatoPersonalizzato = dpb
      break 
       
    }
  }
   
   
  return foundDatoPersonalizzato
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public IDCollection MainModule.getMandatoryCustomdataSections()
{
  IDCollection sectionsWithOnlyMandatoryCFI of CdataSection = this.GetAvailableCustomDataSections(...)
  for each CdataSection cs in sectionsWithOnlyMandatoryCFI
  {
    if (!(cs.CDATAFIELDSINFO.loaded))
    {
      cs.loadCollectionFromDB(cs.CDATAFIELDSINFO, ...)
    }
    for each MainModuleDatoPersonalizzatoInfo cfi in cs.CDATAFIELDSINFO
    {
      cfi.deleted = cfi.Mandatory == No
    }
    cs.CDATAFIELDSINFO.removeDeleted()
  }
   
  for each CdataSection cs in sectionsWithOnlyMandatoryCFI
  {
    cs.deleted = cs.CDATAFIELDSINFO.count() == 0
  }
  sectionsWithOnlyMandatoryCFI.removeDeleted()
  return sectionsWithOnlyMandatoryCFI
}


// ──────────────────────────────────

// *******************************************************************************************************************************************
// "Pseudo-PROTECTED" method that can be specialized in subclasses: return false if custom data validation needs to be avoided in the subclass
// *******************************************************************************************************************************************
public boolean MainModule.bypassCdataValidation()
{
  return false
}


// ──────────────────────────────────

// **********************************************
// INCOPMLETE METHOD SINCE HIST VALUES NOT LOADED
// **********************************************
public void MainModule.loadCdataSnapshot()
{
  int:kordapp kordApp = this.getKordApp()
  int mainId = this.getMainID()
   
  IDCollection allSnapshotsForCurrentObject of CdataSnapshot = new()
  select into collection (allSnapshotsForCurrentObject)
  from 
    CdataSnapshot // master table
  where
    IDMODULERECID == mainId
    KORDAPP == kordApp
   
  CdataSnapshot.addAll(allSnapshotsForCurrentObject, true)
  CdataSnapshot.loaded = true
   
  // HERE WE SHOULD NOW LOAD ALL THE VALUES INTO THE COLLECTION FOR EACH SNAPSHOT
  DevTools.ToBeReviewed("transient collcetion of history values must be populated, now we populate it only to save history but we lack a way to populate it "normally")
  for each CdataSnapshot cs in CdataSnapshot
  {
    cs.loadHistoryCdata()
  }
}


// ──────────────────────────────────



// ──────────────────────────────────

// ******************************************************************
// returns a collcecion of Riferimento objects given a reference type
// ******************************************************************
public IDCollection MainModule.GetRiferimenti(
  ReferenceType referenceType // 
)
{
  IDCollection riferimentiSelected of Riferimento = new()
  int RefTyID = referenceType.IDTipoRiferimento
  Riferimenti.moveFirst()
   
  for each Riferimento r in Riferimenti
  {
    if (r.IDTipoRiferimento = RefTyID)
    {
      riferimentiSelected.addRef(r)
    }
  }
   
  return riferimentiSelected
}


// ──────────────────────────────────

// ********************************************************************************************
// creates a Riferimento and adds it to the mainmodule, the crated riferimento is also returned
// ********************************************************************************************
public Riferimento MainModule.AddRiferimento(
  MainModule Destination // 
  ReferenceType refType  // 
)
{
  Riferimento r = Riferimento.CreateRiferimento(this, Destination, refType)
  Riferimenti.add(r)
  return r
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public boolean MainModule.isReferenceCustomDataHistoryEnabled()
{
  int:kordapp currentKordApp = this.getKordApp()
  string vREFCDATAHISTREVISIONIOPTIONS = ""
  select into variables (found variable)
    set vREFCDATAHISTREVISIONIOPTIONS = REFCDATAHIST
  from 
    CDATAREVISIONIOPTIONS // master table
  where
    KORDAPP == currentKordApp
   
  string:flagYN refCdataEnabled = vREFCDATAHISTREVISIONIOPTIONS
   
  return refCdataEnabled == Yes
}


// ──────────────────────────────────

// **********************************************************************************************************
// Check if Document link already exists in collegati collection
// this method is require to validate/check when we create Document link to prevent Duplicate error on saving
// **********************************************************************************************************
public boolean MainModule.DocumentLinkAlreadyExists(
  Documento document // 
)
{
  return true
}


// ──────────────────────────────────

// **************************************************************************************************************
// create a new Link document for given document and Idrevision and add in to collection of Clifor doc collegati 
// **************************************************************************************************************
public void MainModule.AddDocCollegati(
  Documento Document // 
  int IdRevision     // 
)
{
}


// ──────────────────────────────────

// *********************************************************************************************
// Link a document in Module and block a specific document revision
// Blocking a revision is optional,
// This method support all modules extending MainModule
// Modello Evento and Document module does not support revision blocking (handled with DTTError)
// *********************************************************************************************
public void MainModule.LinkDocumentOBSOLETE(
  Documento document          // 
  optional int idRevision = 0 // 
)
{
  int:kordapp kordApp = this.getKordApp()
  if (kordApp == Documenti or kordApp == ModelliEventi)
  {
    if (idRevision != 0)
    {
      QappCore.DTTLogMessage("In Documenti and modelli Eventi locking a revision is not possible, so you cannot pass an IdRevision", ..., DTTError)
    }
  }
   
  if (!(this.DocumentLinkAlreadyExists(document)))
    this.AddDocCollegati(document, idRevision)
}


// ──────────────────────────────────

// ************************************************************************************************
// given a file with full path a docComunicazione object is created and attached to the main module
// 
// description, creatorUser, IdDocComGroup allow to specify more details on the docComunicazione
// 
// keepOriginalFile is a boolean that if set to False causes the file at filepath to be deleted
// 
// the created moduleDocument is returned (so it can be inspected)
// ************************************************************************************************
public ModuleDocument MainModule.addDocComunicazioniFromFile(
  string filePath                       // 
  string description                    // 
  Utente creatorUser                    // 
  optional int IDDocComGroup = 0        // 
  optional boolean keepOriginalFile = 0 // 
)
{
  ModuleDocument md = ModuleDocument.create(filePath, this.getMainID(), this.getKordApp(), description, creatorUser, IDDocComGroup, keepOriginalFile)
  ModuleDocuments.add(md)
   
  return md
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void MainModule.loadDocumentiComunicazioniOBSOLETE()
{
  DevTools.DEPRECATE("remove", "this method is used by many qapps and must be removed", "loda module documents collection from db instead", "25.5", toDate(2025, 11, 1))
   
  ModuleDocuments.clear()
   
  int:kordapp kordApp = this.getKordApp()
  int mainId = this.getMainID()
   
  IDCollection kordAppSpecificModuleDocument of ModuleDocument = new()
  select into collection (kordAppSpecificModuleDocument)
  from 
    ModuleDocument // master table
  where
    ((kordApp == Eventi) and (mainId == IDEVENTO)) or ((kordApp == Progetti) and mainId == IDPROGETTO) or ((kordApp == Articoli) and (mainId == IDARTICOLO)) or ((kordApp == ClientiForni­
       tori) and (mainId == IDCONTO)) or ((kordApp == Personale) and (mainId == IDDIPENDENTE)) or ((kordApp == AltreAnagrafiche) and (mainId == IDCESPITE)) or ((kordApp == Privati) and (
       mainId == IDCITTADINI)) or ((kordApp == Interventi) and (mainId == IDTESTATAOP)) or ((kordApp == Funzioni) and (mainId == IDFUNZIONE))
   
  ModuleDocuments.addAll(kordAppSpecificModuleDocument, ...)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void MainModule.createModuleDocumentFromModello(
  Documento modelloDoumento // 
)
{
  DocumentEngine de = DocumentEngine.create(modelloDoumento, this)
   
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public IDCollection MainModule.getAllTextReplacements()
{
  IDCollection mapElements of MapElement = new()
   
  mapElements = this.GetModuleSpecificTextReplacements()
   
  IDCollection customdataMapElements of MapElement = new()
   
  customdataMapElements = this.getCustomDataTextReplacements()
   
  mapElements.addAll(customdataMapElements, ...)
   
  IDCollection riferimentiMapElements of MapElement = new()
   
  riferimentiMapElements = this.getRiferimentiTextReplacements()
   
  mapElements.addAll(riferimentiMapElements, ...)
   
  return mapElements
}


// ──────────────────────────────────

// *******************************************************
// this method add in to map all the custom data of module
// *******************************************************
private IDCollection MainModule.getCustomDataTextReplacements()
{
  IDCollection mapElements of MapElement = new()
  this.loadDatiPers()
  for each DatoPersonalizzato dpb in DatiPersonalizzati
  {
    MainModuleDatoPersonalizzatoInfo cdataFieldInfo = cast(dpb.getCustomDataInfo())
    MapElement mapElement = new()
    mapElement.Key = cdataFieldInfo.Caption
    mapElement.Value = dpb.getValueAsString()
    mapElements.add(mapElement)
  }
  return mapElements
}


// ──────────────────────────────────

// *************************************************************************************
// this method add in to map all the custom data of module which is defined as reference
// *************************************************************************************
private IDCollection MainModule.getRiferimentiTextReplacements()
{
  IDCollection mapElements of MapElement = new()
  this.loadCollectionFromDB(Riferimenti, ...)
   
  IDMap refTypeLookupMap = new()
   
  for each Riferimento r in Riferimenti
  {
    // it consider first record of any reference type
    if (!(refTypeLookupMap.containsKey(r.IDTipoRiferimento)))
    {
      refTypeLookupMap.setValue(r.IDTipoRiferimento, r.IDForAll)
      ReferenceType rt = r.getReferenceType()
       
      // retrieve module defined as reference
      MainModule mm = this.retrieve(rt.SourceKordapp, r.IDForAll, ...)
       
      // load custom data
      mm.loadDatiPers()
      string keySuffix = "_DP_" + mm.getModuleInitialLetter()
      for each DatoPersonalizzato MainModuleDatiPersonalizzatiBase in mm.DatiPersonalizzati
      {
         MainModuleDatoPersonalizzatoInfo cdataFieldInfo = cast(MainModuleDatiPersonalizzatiBase.getCustomDataInfo())
         MapElement me = new()
         me.Key = cdataFieldInfo.Caption + keySuffix
         me.Value = MainModuleDatiPersonalizzatiBase.getValueAsString()
//         QappCore.DTTLogMessage(formatMessage("key: |1, value: |2", me.Key, me.Value, ...), 161616161, ...)
         mapElements.add(me)
      }
    }
  }
  return mapElements
}


// ──────────────────────────────────

// **************************************************************************************************************************************************************************************************
// This method can be implemented by extending in all modules but in this way it is more centralized and it is used at single place in "get Riferimenti Text Replacements" method to the replace tags
// **************************************************************************************************************************************************************************************************
private string MainModule.getModuleInitialLetter()
{
  string initialLetter = ""
  int:kordapp kordApp = this.getKordApp()
  switch (kordApp)
  {
    case Funzioni:
      initialLetter = "F"
    break
    case ClientiFornitori:
      initialLetter = "C"
    break
    case Articoli:
      initialLetter = "A"
    break
    case Progetti:
      initialLetter = "P"
    break
    case Personale:
      initialLetter = "D"
    break
    case AltreAnagrafiche:
      initialLetter = "I"
    break
    case Eventi:
      initialLetter = "E"
    break
  }
  return initialLetter
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public IDCollection MainModule.GetCollegamentiITDOENSTCONSIDERPRIVILEGESANDNEWCLASSES(
  ReferenceType refTypeFilter // 
)
{
  int currentId = this.getMainID()
  IDCollection collRiferimenti of Riferimento = new()
  IDCollection collSources of MainModule = new()
  int idRefTypeFilter = refTypeFilter.IDTipoRiferimento
  select into collection (collRiferimenti)
  from 
    Riferimento // master table
  where
    IDForAll == currentId
    IDTipoRiferimento = idRefTypeFilter
  for each Riferimento r in collRiferimenti
  {
    MainModule mm = new()
    mm = r.getSource()
    if (mm != null)
    {
      // before add it, check if there are already = records in the collSources
      boolean checkDistinct = true
      for each MainModule mm1 in collSources
      {
         if (mm1.getMainID() == mm.getMainID())
         {
           if (mm1.getKordApp() == mm.getKordApp())
           {
             checkDistinct = false
           }
         }
      }
      if (checkDistinct)
      {
         collSources.add(mm)
      }
    }
  }
  return collSources
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public int MainModule.loadCollegamenti(
  optional boolean countOnly = 0 // 
)
{
  int collegamentiRiferimentiCount = this.loadCollegamentiRiferimenti(countOnly)
  int collegamentiQappDataCount = this.loadCollegamentiQappData(countOnly)
  int collegamentiDiscussionsCount = this.loadCollegamentiDiscussions(countOnly)
  int collegamentiCustomDataCount = this.loadCollegamentiCustomData(countOnly)
  int totalCount = collegamentiRiferimentiCount + collegamentiQappDataCount + collegamentiDiscussionsCount + collegamentiCustomDataCount
  return totalCount
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public int MainModule.loadCollegamentiRiferimenti(
  optional boolean countOnly = 0 // 
)
{
  int totalCount = 0
   
  int mainID = this.getMainID()
  int:kordapp kordapp = this.getKordApp()
   
  int vCountIDTipoRiferimento = 0
  select into variables (found variable)
    set vCountIDTipoRiferimento = count(Riferimenti.IDRiferimento)
  from 
    Riferimenti     // master table
    TipiRiferimento // joined with Riferimenti using key FK_EVA_REFERENCES_EVA_REFERENCE_TYPES
  where
    Riferimenti.ISMODELLO == No and Riferimenti.IDForAll == mainID and TipiRiferimento.SourceKordapp == kordapp
    TipiRiferimento.ATTIVO == Yes
   
  totalCount = vCountIDTipoRiferimento
  if (!(countOnly))
  {
     
    for each row (readonly)
    {
      select
         IDTipoRiferimento = Riferimenti.IDTipoRiferimento
         destinationKord = TipiRiferimento.DestinationKordapp
         mainId = Riferimenti.IDEvento
      from 
         Riferimenti     // master table
         TipiRiferimento // joined with Riferimenti using key FK_EVA_REFERENCES_EVA_REFERENCE_TYPES
      where
         Riferimenti.ISMODELLO == No and Riferimenti.IDForAll == mainID and TipiRiferimento.SourceKordapp == kordapp
         TipiRiferimento.ATTIVO == Yes
       
       
      this.addCollegamentoMainModule(destinationKord, mainId, IDTipoRiferimento, References, ...)
    }
  }
   
  return totalCount
   
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public int MainModule.loadCollegamentiQappData(
  optional boolean countOnly = 0 // 
)
{
  int countTotal = 0
   
  int:kordapp kord = this.getKordApp()
  int mainID = this.getMainID()
   
  int vCount = 0
  select into variables (found variable)
    set vCount = count(IDQAPPDATA)
  from 
    NGTQAPPSDATA // master table
  where
    ACTIVE == Yes
    MAINID == mainID
    KORDID == kord
   
  countTotal = vCount
  if (!(countOnly))
  {
    IDCollection linkedQappsData of CollegamentoQappdata = new()
     
     
    select into collection (linkedQappsData)
    from 
      CollegamentoQappdata // master table
    where
      MAINID == mainID
      KORDID == kord
     
    for each CollegamentoQappdata cq in linkedQappsData
    {
      cq.computeBackgroundColor()
      cq.TypeID = cq.IDAPPLICAZIONE
      CollegamentiQappData.setValue(cq.IDAPPLICAZIONE, true)
    }
    CollegamentoQappData = linkedQappsData
  }
   
  return countTotal
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public int MainModule.loadCollegamentiDiscussions(
  optional boolean countOnly = 0 // 
)
{
  int countTotal = 0
  int:kordapp kord = this.getKordApp()
  int mainID = this.getMainID()
  int vCount = 0
  select into variables (found variable)
    set vCount = count(IDMESSAGGIO)
  from 
    MSGMESSAGGI // master table
  where
    ISDISCUSSION == Yes
    IDMESSAGGIO in subquery
      select // 
         IDMESSAGIO
      from 
         MSGLINK // master table
      where
         MSGLINK.KORDAPP == kord
         MSGLINK.IDITEM == mainID
   
  countTotal = vCount
   
  if (!(countOnly))
  {
    IDCollection linkedMessaggi of Messaggio = new()
    select into collection (linkedMessaggi)
    from 
      Messaggio // master table
    where
      ISDISCUSSION == Yes
      IDMESSAGGIO in subquery
         select // 
           IDMESSAGIO
         from 
           MSGLINK // master table
         where
           MSGLINK.KORDAPP == kord
           MSGLINK.IDITEM == mainID
     
    CollegamentoDiscussion = linkedMessaggi
  }
   
  return countTotal
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public int MainModule.loadCollegamentiCustomData(
  optional boolean countOnly = 0 // 
)
{
  int countTotal = 0
  int mainID = this.getMainID()
  int:kordapp kordId = this.getKordApp()
  int:cdataType lookupCDType = MainModuleDatoPersonalizzatoInfo.translateKordToCdataType(kordId)
   
  int vCount = 0
  select into variables (found variable)
    set vCount = count(CDATAMODULEVALUES.IDCDATAMODULE)
  from 
    CDATAMODULEVALUES // master table
    CDATAFIELDSINFO   // joined with CDATA MODULE VALUES using key FK_CDATA_MODULE_VALUES_ID_CDATA_FLD
    CDATASECTIONS     // joined with CDATA FIELDS INFO using key FK_CDATA_FIELD_ID_CDATA_SEC
  where
    CDATAMODULEVALUES.VALUE == mainID
    CDATAFIELDSINFO.Active == Yes
    CDATAFIELDSINFO.Type == lookupCDType
    CDATASECTIONS.Active == Yes
   
  countTotal = vCount
   
  if (!(countOnly))
  {
    for each row (readonly)
    {
      select
         moduleID = CDATAMODULEVALUES.IDMODULERECID
         kord = CDATASECTIONS.KordApp
      from 
         CDATAMODULEVALUES // master table
         CDATAFIELDSINFO   // joined with CDATA MODULE VALUES using key FK_CDATA_MODULE_VALUES_ID_CDATA_FLD
         CDATASECTIONS     // joined with CDATA FIELDS INFO using key FK_CDATA_FIELD_ID_CDATA_SEC
      where
         CDATAMODULEVALUES.VALUE == mainID
         CDATAFIELDSINFO.Type = lookupCDType
         CDATAFIELDSINFO.Active == Yes
         CDATASECTIONS.Active == Yes
       
      this.addCollegamentoMainModule(kord, moduleID, kord, CustomData, ...)
    }
    this.handleSubModuleCollegamentiCustomData()
  }
   
  return countTotal
}


// ──────────────────────────────────

// *********************************************
// this procedure loads collegamenti Custom data
// *********************************************
public void MainModule.handleSubModuleCollegamentiCustomData()
{
   
}


// ──────────────────────────────────

// *************************************************************************************************
// This procedure create a CollegamentoMainModule and add it to the proper collection 
// it checks the visibilities of the logged user to decide if the new element should be added or not
// *************************************************************************************************
public void MainModule.addCollegamentoMainModule(
  int:kordapp kord                                   // 
  int mainId                                         // 
  int typeID                                         // 
  string:collegamentiMainmoduleType CollegamentoType // 
  optional boolean isContatto = 0                    // 
)
{
  boolean currentUserCanExecute = QappCore.Loggeduser.hasSpecificPrivilege(kord, Execute)
  boolean currentUserHasPers1 = QappCore.Loggeduser.hasSpecificPrivilege(kord, Pers1)
  boolean currentUserCanViewItem = QappCore.Loggeduser.canViewItem(kord, mainId)
   
  CollegamentoMainModule cr = null
  switch (kord)
  {
    case Eventi:
      if (currentUserCanExecute and (currentUserHasPers1 or currentUserCanViewItem))
      {
         cr = cast(CollegamentoEvento.create(mainId, typeID))
      }
    break
    case Personale:
      if (currentUserCanExecute)
      {
         cr = cast(CollegamentoPersonale.create(mainId, typeID))
      }
    break
    case AltreAnagrafiche:
      if (currentUserCanExecute and (currentUserHasPers1 or currentUserCanViewItem))
      {
         cr = cast(CollegamentoAltreAnagrafiche.create(mainId, typeID))
      }
    break
    case Progetti:
      boolean currentUserHasPers2 = QappCore.Loggeduser.hasSpecificPrivilege(kord, Pers2)
      if (currentUserCanExecute and (currentUserHasPers1 or currentUserHasPers2 or currentUserCanViewItem))
      {
         cr = cast(CollegamentoProgetto.create(mainId, typeID))
      }
    break
    case Interventi:
      if (currentUserCanExecute)
      {
         cr = cast(CollegamentoIntervento.create(mainId, typeID))
      }
    break
    case ClientiFornitori:
      if (currentUserCanExecute)
      {
         cr = cast(CollegamentoClifor.create(mainId, typeID))
      }
    break
    case Articoli:
      if (currentUserCanExecute)
      {
         cr = cast(CollegamentoArticolo.create(mainId, typeID))
      }
    break
    case Funzioni:
      if (currentUserCanExecute)
      {
         cr = cast(CollegamentoFunzione.create(mainId, typeID))
      }
    break
    case Privati:
      if (currentUserCanExecute)
      {
         cr = cast(CollegamentoPrivati.create(mainId, typeID))
      }
    break
  }
   
  if (cr != null)
  {
    cr.computeBackgroundColor()
    if (CollegamentoType == References)
    {
      cr.Type = References
      CollegamentiRiferimentoTypes.setValue(typeID, true)
      CollegamentoRiferimento.add(cr)
    }
    if (CollegamentoType == CustomData)
    {
      cr.ISCONTATTO = isContatto
      cr.Type = CustomData
      CollegamentiCustomDataKords.setValue(typeID, true)
      CollegamentoCustomData.add(cr)
    }
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public IDCollection MainModule.retrieveMainModuleCollegamentiCollection(
  int typeID                                         // IDReferenceType or KORD
  string:collegamentiMainmoduleType collegamentiType // Reference or Customdata
)
{
  IDCollection collegamenti of Collegamento = null
  IDCollection collegamentiOfSpecificType of Collegamento = new()
  switch (collegamentiType)
  {
    case References:
      collegamenti = CollegamentoRiferimento
    break
    case CustomData:
      collegamenti = CollegamentoCustomData
    break
    case Qappdata:
      collegamenti = CollegamentoQappData
    break
  }
   
  if (collegamenti and collegamenti.count() > 0)
  {
    for each Collegamento c in collegamenti
    {
      if (c.TypeID == typeID)
      {
         collegamentiOfSpecificType.add(c)
      }
    }
  }
  return collegamentiOfSpecificType
}


// ──────────────────────────────────

// ******************************************************************************
// creates a docSpreadsheet for a mainmodule and attaches the crated object to it
// 
// if a docSpreadsheet already exists for the mainmodule it is updated from file
// 
// an Utente and a xlsx file (as full path like c:\folders\file.xlsx)
// ******************************************************************************
public void MainModule.updateDocSpreadsheetFromFile(
  string fullPath      // 
  Utente manipulatedBy // 
)
{
  string fileExtension = SH.rightUpToDelimiter(fullPath, ".", ...)
  if (fileExtension != "xlsx")
  {
    QappCore.DTTLogMessage("only xlsx is supported!", ..., DTTError)
    return 
  }
   
  DocSpreadsheet ds = new()
  ds.IDITEM = this.getMainID()
  ds.KORDAPP = this.getKordApp()
  try 
  {
    ds.loadFromDB(...)
  }
  catch 
  {
    ds.init()
    ds.DATACREAZIONE = today()
    ds.IDUTENTECREATORE = manipulatedBy.IDUTENTE
  }
  ds.IDUTENTEULTMOD = manipulatedBy.IDUTENTE
   
   
  DocSpreadsheet = ds
   
  DocFile df = DocFile.createFromFile(fullPath, ...)
   
  DocSpreadsheet.DocFile = df
  DocSpreadsheet.IDDOCFILE = df.IDDOCFILE
}


// ──────────────────────────────────

// *****************************************************************************
// method to be called to load in memory the DocSpreadsheet object of MainModule
// *****************************************************************************
public void MainModule.loadDocSpreadsheet()
{
  DocSpreadsheet ds = new()
  ds.KORDAPP = this.getKordApp()
  ds.IDITEM = this.getMainID()
  try 
  {
    ds.loadFromDB(...)
  }
  catch 
  {
    QappCore.DTTLogMessage(formatMessage("Impossible to load DocSpreadsheet for MainId |1 and KordApp |2, either there is no DocSpreadsheet in the DB or more DocSpreadsheet are defined for these MainID and 
          KordApp", ds.IDITEM, decode(ds.KORDAPP, Kordapp), ...), ..., DTTInfo)
    ds = null
  }
  DocSpreadsheet = ds
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean MainModule.quickLoad(
  optional int numberOfLevels = 9999 // Write a comment for this parameter or press backspace to delete this comment
)
{
  this.setTag("quickLoad", true)
  boolean succesfullyLoaded = false
  try 
  {
    this.loadFromDB(numberOfLevels)
    succesfullyLoaded = true
  }
  catch 
  {
    int mainId = this.getMainID()
    int:kordapp kordApp = this.getKordApp()
    string errorMessage = formatMessage("Error while loading from db |1 with Id |2", decode(kordApp, Kordapp), mainId, ...)
    QappCore.DTTLogMessage(errorMessage, ..., DTTError)
     
    this.clear()
     
    succesfullyLoaded = false
  }
  return succesfullyLoaded
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean MainModule.quickLoadRequested()
{
  boolean isQuickLoadRequested = getTag("quickLoad") == true
   
  return isQuickLoadRequested
}


// ──────────────────────────────────

// **********************************************************************************************
// returns the object of the proper type already initialized with mainid (ready for load from db)
// 
// **********************************************************************************************
public static MainModule MainModule.createProperlyTypedObject(
  int:kordapp kordApp // 
  int mainId          // 
  optional string:executionModes executionMode = "normalMode" // 
)
{
  MainModule retrievedMainModule = null
   
  if (kordApp > 0)
  {
    switch (kordApp)
    {
      case ClientiFornitori:
         Clifor c = new()
         c.IDCONTO = mainId
         retrievedMainModule = c
      break
      case Articoli:
         Articolo a = new()
         a.IDARTICOLO = mainId
         retrievedMainModule = a
      break
      case Eventi:
         Evento e = new()
         e.IDEVENTO = mainId
         retrievedMainModule = e
      break
      case AltreAnagrafiche:
         AltreAnagrafiche aa = new()
         aa.ID = mainId
         retrievedMainModule = aa
      break
      case Privati:
         Privati p = new()
         p.IDCITTADINI = mainId
         retrievedMainModule = p
      break
      case Progetti:
         Progetto p = new()
         p.IDPROGETTO = mainId
         retrievedMainModule = p
      break
      case Documenti:
         Documento dd = new()
         dd.IDDOCUMENTO = mainId
         retrievedMainModule = dd
      break
      case Interventi:
         Intervento i = new()
         i.IDTESTATAOP = mainId
         retrievedMainModule = i
      break
      case Funzioni:
         Funzione f = new()
         f.IDFUNZIONE = mainId
         retrievedMainModule = f
      break
      case Personale:
         Personale p = new()
         p.IDDIPENDENTE = mainId
         retrievedMainModule = p
      break
      case Organigrammi:
         Orgchart o = new()
         o.IDORGANIGRAMMA = mainId
         retrievedMainModule = o
      break
      case ModelliEventi:
         ModelloEvento me = new()
         me.IDTEMPLATEEVENTO = mainId
         retrievedMainModule = me
      break
      default:
         QappCore.DTTLogMessage(formatMessage("Not handled for kordapp |1", decode(kordApp, Kordapp), ...), ..., DTTError)
      break
    }
     
  }
  else 
  {
    QappCore.DTTLogMessage("NULL kordapp passed", ..., if(executionMode == normalMode, DTTError, DTTInfo))
  }
   
  return retrievedMainModule
}


// ──────────────────────────────────

// *******************************************************************
// completes the getFromDB, not to be called directly, used internally
// *******************************************************************
private boolean MainModule.handleGetFromDB(
  string:getFromDBLoadingModes loadingMode // 
)
{
  boolean succesfullyRetrieved = false
   
  if (loadingMode == quickLoad)
  {
    succesfullyRetrieved = this.quickLoad(0)
  }
  else 
  {
    try 
    {
      int numberOfLevels = if(loadingMode == normalZeroLevels, 0, 999)
      this.loadFromDB(numberOfLevels)
      succesfullyRetrieved = true
    }
    catch 
    {
      QappCore.DTTLogMessage(formatMessage("handleGetFromDB: cannot load |1 of MainId |2", decode(this.getKordApp(), Kordapp), this.getMainID(), ...), ..., Tools.getAlternativeForDttErrorInUnitTests(...))
    }
  }
   
  return succesfullyRetrieved
}


// ──────────────────────────────────

// ********************************************************************************************************************************************************************************
// method used to perform the getFromDB in a centralized way, it returns the properly typed instance of main module in case of success (e.g. a CliFor) or null in case of failure
// 
// loadingMode allows to specify how to load: quick, 0 levels, 999 levels
// QUICK is a way to load with 0 levls but it offers us an extra feature:
// in the after load we are able to know if quickload was requested so we can avoid to run some part of the after load event for example (refer to Eventi.AfterLoad for an example)
// in the after load we can call mainmodule.quickLoadRequeseted to know if we shold perform a quickload or not
// 
// IMPORTANT: the default value of laodingMode is not the inde one: loadFormDB loads 999 collection levels by default, mainmodule is more conservative and does 0
// ********************************************************************************************************************************************************************************
public static MainModule MainModule.retrieve(
  int:kordapp kordApp // 
  int mainId          // 
  optional string:getFromDBLoadingModes loadingMode = "normalZeroLevels" // 
)
{
  boolean validIntParametersPassed = kordApp > 0 and mainId > 0
   
  MainModule mm = null
  if (validIntParametersPassed)
  {
    mm = this.createProperlyTypedObject(kordApp, mainId, ...)
     
    boolean success = false
    if (mm)
    {
      success = mm.handleGetFromDB(loadingMode)
    }
     
    if (!(success))
      mm = null
  }
  else 
  {
    QappCore.DTTLogMessage(formatMessage("Invalid parameters passed: kordapp "|1", mainId "|2"", kordApp, mainId, ...), ..., DTTError)
  }
   
  return mm
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public ModuleType MainModule.getType()
{
  ModuleType mt = null
   
  int:kordapp kordApp = this.getKordApp()
  int typeID = this.getTypeID()
   
  switch (kordApp)
  {
    case ClientiFornitori:
      TipoClifor tc = TipoClifor.get(typeID)
      mt = tc
    break
    case Personale:
      TipoPersonale tp = new()
      tp.IDTIPOANAGR = typeID
      try 
      {
         tp.loadFromDB(...)
      }
      catch 
      {
         tp = null
      }
      mt = tp
    break
    case Eventi:
      ClasseEvento e = new()
      e.IDCLASSE = typeID
      try 
      {
         e.loadFromDB(...)
      }
      catch 
      {
         e = null
      }
      mt = e
    break
    case ModelliEventi:
      ClasseEvento me = new()
      me.IDCLASSE = typeID
      try 
      {
         me.loadFromDB(...)
      }
      catch 
      {
         me = null
      }
      mt = me
    break
    case Articoli:
      TipoArticolo tipoart = new()
      tipoart.IDTIPOARTICOLO = typeID
      try 
      {
         tipoart.loadFromDB(...)
      }
      catch 
      {
         tipoart = null
      }
      mt = tipoart
    break
    case Privati:
      PrivatiTipoAnagrafica priv = new()
      priv.IDTIPOANAGR = typeID
      try 
      {
         priv.loadFromDB(...)
      }
      catch 
      {
         priv = null
      }
      mt = priv
    break
    case Progetti:
      TipoProgetto prog = new()
      prog.IDTIPOPROGETTO = typeID
      try 
      {
         prog.loadFromDB(...)
      }
      catch 
      {
         prog = null
      }
      mt = prog
    break
    case AltreAnagrafiche:
      TipoCespite tipoCespite = new()
      tipoCespite.IDTIPIINFRSTR = typeID
      try 
      {
         tipoCespite.loadFromDB(...)
      }
      catch 
      {
         tipoCespite = null
      }
      mt = tipoCespite
    break
    case Funzioni:
      FunzioneModuleType f = new()
      f.IDFUNZIONE = typeID
       
      try 
      {
         f.loadFromDB(...)
      }
      catch 
      {
         f = null
      }
      mt = f
    break
    default:
      QappCore.DTTLogMessage(formatMessage("kordapp |1 not supported", decode(kordApp, Kordapp), ...), ..., DTTError)
      mt = null
    break
  }
   
  return mt
}


// ──────────────────────────────────

// **********************************************************************************************
// returns the object of the proper type already initialized with mainid (ready for load from db)
// 
// **********************************************************************************************
public static MainModule MainModule.getFromDBByKordApp(
  int:kordapp kordApp // 
  int mainId          // 
)
{
  MainModule retrievedMainModule = null
   
  switch (kordApp)
  {
    case ModelliEventi:
      retrievedMainModule = ModelloEvento.getFromDB(mainId, ...)
    break
    case ClientiFornitori:
      retrievedMainModule = Clifor.getFromDB(mainId, ...)
    break
    case Personale:
      retrievedMainModule = Personale.getFromDB(mainId, ...)
    break
    case Eventi:
      retrievedMainModule = Evento.getFromDB(mainId, ...)
    break
    case Funzioni:
      retrievedMainModule = Funzione.getFromDB(mainId, ...)
    break
    default:
      QappCore.DTTLogMessage(formatMessage("MainModule.getFromDBByKordApp: kordApp '|1' not supported", decode(kordApp, Kordapp), ...), ..., DTTError)
    break
  }
   
  return retrievedMainModule
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static MainModule MainModule.createTestObject(
  string description          // 
  optional string detail = "" // 
  optional IDMap parameters   // 
)
{
  QappCore.DTTLogMessage("ABSTRACT ERROR", ..., DTTError)
  return null
}


// ──────────────────────────────────

// ********************************************************************
// This procedure converts the property of the class into a json string
// ********************************************************************
public string MainModule.toJson()
{
  string mmToJson = ""
  IDMap jsonStructure = new()
  IDDocumentStructure idds = getStructure()
  for (int i = 0; i < idds.getPropertyCount(); i = i + 1)
  {
    IDPropertyDefinition idpd = idds.getPropertyDefinition(i)
    if (idpd)
    {
      if (this.skipPropertyOnJsonExport(i))
         continue 
      string propertyName = lower(idpd.UIName)
      propertyName = replace(propertyName, " ", "_")
      string value = toString(cast(getProperty(i)))
      jsonStructure.setValue(propertyName, value)
    }
  }
  mmToJson = JSON.stringify(jsonStructure)
  return mmToJson
}


// ──────────────────────────────────

// *************************************************************************************************************
// returns true whether the passed poperty should be ignored on to JSon export (tipical case exclude RTF fields)
// *************************************************************************************************************
private boolean MainModule.skipPropertyOnJsonExport(
  int propertyIndex // 
)
{
  boolean result = false
   
  IDArray excludedPropertyIndexesForCurrentModule = this.getPropertyIndexesOfPropertiesExcludedFromJsonExport()
   
  for (int i = 0; i < excludedPropertyIndexesForCurrentModule.length(); i = i + 1)
  {
    int currentPropertyName = excludedPropertyIndexesForCurrentModule.getValue(i)
    if (propertyIndex == currentPropertyName)
    {
      result = true
      break 
    }
  }
   
  return result
}


// ──────────────────────────────────

// *************************************************************************************************************************************************
// to be extended in each subcass: it must return an array of property indexes names for the properties we do not want to include in the json export
// 
// NOTE: it is done with property index so we are forced to use the property itself, making it searchable better
// *************************************************************************************************************************************************
public IDArray MainModule.getPropertyIndexesOfPropertiesExcludedFromJsonExport()
{
  // sample code to be used in subclasses
  IDArray exludedPropertyIndexes = new()
//  exludedPropertyIndexes.addValue(toPropertyIndex(this.DESCREVENTO))
//  exludedPropertyIndexes.addValue(toPropertyIndex(this.INSTRUCTIONS))
   
  return exludedPropertyIndexes
}


// ──────────────────────────────────

// ***********************************
// return a collectiono of Map Element
// ***********************************
public IDCollection MainModule.GetModuleSpecificTextReplacements()
{
  QappCore.DTTLogMessage("ABSTRACT error", ..., DTTError)
  return null
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public IDCollection MainModule.retrieveAvailableCDSections()
{
  int IDTipoAnagrafica = this.getTypeID()
  int:kordapp kordApp = this.getKordApp()
   
  DevTools.ToBeReviewed("Eventi overrides this method, so the eventi part could be removed from here, anyway at the moment of writing this we are note 100% sure and moreover this method is not unit tested so 
        we prefer not to modify it. WE think anyay that the eeventi part of this if should be removed. Francesco and Girish, 26/04/2023")
  IDCollection retrievedCDSections of CdataSection = new()
  select into collection distinct (retrievedCDSections)
    set IDCDATASEC = CDATASECTIONS.IDCDATASEC
    set Name = CDATASECTIONS.Name
    set Description = CDATASECTIONS.Description
    set Sequence = CDATASECTIONS.Sequence
    set Active = CDATASECTIONS.Active
    set KordApp = CDATASECTIONS.KordApp
    set ISCOMMON = CDATASECTIONS.ISCOMMON
    set DEPRECATEDIDTEMPLATE = CDATASECTIONS.DEPRECATEDIDTEMPLATE
    set DEPRECATEDTEMPLATE = CDATASECTIONS.DEPRECATEDTEMPLATE
    set DEPRECATEDIDPARENTTEMPLATEREMOTE = CDATASECTIONS.DEPRECATEDIDPARENTTEMPLATEREMOTE
    set HIDDEN = CDATASECTIONS.HIDDEN
  from 
    CDATASECTIONS   // master table
    CDATAMODULETYPE // joined with CDATA SECTIONS using key FK_CDATA_MODULE_TYPE_ID_CDATA_SEC
  where
    CDATASECTIONS.Active == Yes
    CDATASECTIONS.KordApp = kordApp
    (CDATASECTIONS.ISCOMMON == Yes) or ((CDATAMODULETYPE.IDTIPO == IDTipoAnagrafica) and (CDATASECTIONS.ISCOMMON == No))
  order by
    CDATASECTIONS.Sequence
    CDATASECTIONS.Description
   
   
  return retrievedCDSections
}


// ──────────────────────────────────

// *******************************************************************************************************************
// it returns the property index of the property linked to the main image field of the class (e.g. ART_ANAGRAFICA.FOTO
// *******************************************************************************************************************
public int MainModule.PROTECTEDgetMainImagePropertyIndex()
{
  QappCore.DTTLogMessage("ABSTRACT", ..., DTTError)
  return null
}


// ──────────────────────────────────

// ****************************************************************************************************************************
// it returns the property index of the property linked to the second image field of the class (e.g. ART_ANAGRAFICA.FOTO_SECOND
// ****************************************************************************************************************************
public int MainModule.PROTECTEDgetSecondImagePropertyIndex()
{
  QappCore.DTTLogMessage("ABSTRACT", ..., DTTError)
  return null
}


// ──────────────────────────────────

// ***********************
//  given a property Index
// ***********************
private string MainModule.extractImageExtensionFromPropertyIndex(
  int imagePropertyIndex // 
)
{
  string tableName = ""
  string primaryKeyFieldName = ""
  string primaryKeyAsString = toString(this.getMainID())
  string imageFieldName = ""
   
   
  IDDocumentStructure idds = getStructure()
  tableName = idds.DBCode
   
  for (int i = 1; i <= idds.getPropertyCount(); i = i + 1)
  {
    IDPropertyDefinition idpd = idds.getPropertyDefinition(i)
    if (idpd.PK)
    {
      primaryKeyFieldName = idpd.DBCode
    }
    if (i == imagePropertyIndex)
    {
      if (idpd.dataType != BLOB)
         QappCore.DTTLogMessage(formatMessage("property index |1 is not about a blob field", i, ...), ..., DTTError)
      imageFieldName = idpd.DBCode
    }
  }
   
  string extension = Tools.extractFileExtension(cast(QualibusDB.me()), tableName, imageFieldName, primaryKeyFieldName, primaryKeyAsString)
   
  return extension
}


// ──────────────────────────────────

// *******************************************************************************************
// given a imageFieldType it returns the file extension (jpg, png, bmp, gif) of the image file
// *******************************************************************************************
public string MainModule.ExtractImageFieldFileExtension(
  optional string:imageFieldTypes imageFieldType = "mainImage" // 
)
{
  int requestedImagePropertyIndex = 0
  switch (imageFieldType)
  {
    case mainImage:
      requestedImagePropertyIndex = this.PROTECTEDgetMainImagePropertyIndex()
    break
    case secondImage:
      requestedImagePropertyIndex = this.PROTECTEDgetSecondImagePropertyIndex()
    break
  }
   
  if (requestedImagePropertyIndex == null)
  {
    QappCore.DTTLogMessage("propertyIndex not found for " + imageFieldType, ..., DTTWarning)
    return null
  }
   
  string retrievedExtension = this.extractImageExtensionFromPropertyIndex(requestedImagePropertyIndex)
   
  retrievedExtension = retrievedExtension
   
  return retrievedExtension
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void MainModule.loadQappCommands()
{
  QappCommands.clear()
   
   
  int:kordapp currentKordApp = this.getKordApp()
   
  IDCollection loadedQappCommands of QappCommand = new()
  select into collection (loadedQappCommands)
  from 
    QappCommand // master table
  where
    KORDID == currentKordApp
   
  // FILTER  management done for Eventi and AltreAnagrafiche only
  this.applyComandFilters(loadedQappCommands)
   
  QappCommands.addAll(loadedQappCommands, true)
}


// ──────────────────────────────────

// ******************************************
// getter for private QappCommands collection
// ******************************************
public IDCollection MainModule.getQappCommands()
{
  return QappCommands
}


// ──────────────────────────────────

// ***********************************************************************************************************
// the passed collection is processed and only commands that match with FILTER are left, the other are removed
// ***********************************************************************************************************
private void MainModule.applyComandFilters(
  IDCollection loadedQappCommands of QappCommand // 
)
{
  int:kordapp currentKordApp = this.getKordApp()
   
  // FILTER  management done for Eventi and AltreAnagrafiche only
  int typeId = null
  if (currentKordApp == Eventi)
  {
    typeId = this.getTypeID()
  }
  else if (currentKordApp == AltreAnagrafiche)
  {
    AltreAnagrafiche aa = cast(this)
    typeId = aa.getMainSCMType()
  }
   
  if (currentKordApp == Eventi or currentKordApp == AltreAnagrafiche)
  {
    for each QappCommand qc in loadedQappCommands
    {
       
      boolean typeAllowedInFilter = qc.isValueAllowedInFilter(typeId)
      if (!(typeAllowedInFilter))
      {
         qc.deleted = true
      }
    }
  }
  loadedQappCommands.removeDeleted()
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public IDCollection MainModule.getSearchIds(
  Utente utente          // user for which visiblity is checked
  optional int subId = 0 // 
  optional string:utenteVisibilityGetResultsTypes getResultsType = "consultazione" // 
)
{
  UtenteVisibility uv = UtenteVisibility.create(utente)
  IDCollection visibleIds of IntDataType = uv.getIdsUserCanSee(this.getKordApp(), subId, getResultsType)
  return visibleIds
}


// ──────────────────────────────────

// *********************************************************************************
// giveen the query it returns a IDCollection of results for which the query matches
// it must be extended in every mainModule subclass
// *********************************************************************************
public IDCollection MainModule.search(
  string query // 
)
{
  QappCore.DTTLogMessage("ABSTRACT", ..., DTTError)
   
  IDCollection resultsThatShouldBeSubclasses of MainModule = new()
  return resultsThatShouldBeSubclasses
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void MainModule.loadDocCollegati()
{
  DocCollegati.clear()
  IDCollection coll of DocCollegato = DocCollegato.loadCollectionForMainModule(this)
  DocCollegati.addAll(coll, true)
   
  // mark the collection as loaded, because it is transient
  DocCollegati.loaded = true
   
  if (!(inserted))
  {
    DocCollegati.setOriginal()
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void MainModule.handleDocCollegatiSaving()
{
  for each DocCollegato dc in DocCollegati
  {
    dc.saveToDB(...)
  }
}


// ──────────────────────────────────

// *******************************************************************************************************************
// this method copies the main collections common to every main module from a source main module to the current objcet
// aldo docSpreadsheet is copied (the only non collection involved in the method for now)!
// it is currently tested by duplicate test of modello evento (normale and workflow to test all the collections)
// *******************************************************************************************************************
public void MainModule.copyMainModuleDataFromSource(
  MainModule sourceMainModule        // 
  boolean shouldDuplicateDatiPers    // 
  boolean shouldDuplicateRiferimenti // 
)
{
  DatiPersonalizzati.clear()
  Riferimenti.clear()
  ModuleDocuments.clear()
  DocCollegati.clear()
  Promemoria.clear()
   
  if (shouldDuplicateDatiPers)
  {
    this.copyDatiPersCollectionFromSource(sourceMainModule)
  }
   
  if (shouldDuplicateRiferimenti)
  {
    this.copyRiferimentiCollectionFromSource(sourceMainModule)
  }
   
  this.copyModuleDocumentsCollectionFromSource(sourceMainModule)
   
  this.copyDocCollegatiCollectionFromSource(sourceMainModule)
   
  if (sourceMainModule.getKordApp() == ModelliEventi)
    QappCore.DTTLogMessage("Modelli evento has no promemoria, collection is empty so it is not copied anyway", ..., DTTInfo)
  this.copyPromemoriaCollectionFromSource(sourceMainModule)
   
  DocSpreadsheet = sourceMainModule.DocSpreadsheet
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void MainModule.copyDatiPersCollectionFromSource(
  MainModule sourceMainModule // 
)
{
  sourceMainModule.DatiPersonalizzati.clear()
  sourceMainModule.loadDatiPers()
   
  this.loadDatiPers()
   
  this.SetRenderingHelper(sourceMainModule.GetRenderingHelper())
   
  for each DatoPersonalizzato sourceDp in sourceMainModule.DatiPersonalizzati
  {
    for each DatoPersonalizzato destDp in DatiPersonalizzati
    {
      DatoPersonalizzatoInfo sourceInfo = sourceDp.getCustomDataInfo()
      DatoPersonalizzatoInfo destInfo = destDp.getCustomDataInfo()
      if (sourceInfo.getFieldID() == destInfo.getFieldID())
      {
         // get from source and set value at destination dp
         destDp.setValueAsString(sourceDp.getValueAsString())
         break 
      }
    }
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void MainModule.copyRiferimentiCollectionFromSource(
  MainModule sourceMainModule // 
)
{
  if (!(sourceMainModule.Riferimenti.loaded))
  {
    sourceMainModule.loadCollectionFromDB(sourceMainModule.Riferimenti, ...)
  }
   
  for each Riferimento sourceRiferimento in sourceMainModule.Riferimenti
  {
    // identify if it is modello Evento
    string isModelloEvento = if(ModelloEvento.isMyInstance(this), Yes, No)
     
    // create riferimento
    Riferimento newlyCreatedRiferimento = Riferimento.create(sourceRiferimento.IDTipoRiferimento, this.getMainID(), sourceRiferimento.IDForAll, isModelloEvento)
    newlyCreatedRiferimento.IDCONTATTO = sourceRiferimento.IDCONTATTO
     
    // load reference customdata
    if (!(sourceRiferimento.RdatiPersonalizzati.loaded))
    {
      sourceRiferimento.loadRDatiPersonalizzati()
    }
     
    for each DatoPersonalizzato rdatoPersSource in sourceRiferimento.RdatiPersonalizzati
    {
      // get source reference customdata value
      string rdatoPersSourceValue = rdatoPersSource.getValueAsString()
       
      // get source customdata info
      DatoPersonalizzatoInfo datoPersInfoSource = rdatoPersSource.getCustomDataInfo()
       
      // create referenence customdata
      DatoPersonalizzato newRdatoDatoPers = DatoPersonalizzato.factory(newlyCreatedRiferimento, datoPersInfoSource)
       
      // set value of reference customdata
      newRdatoDatoPers.setValueAsString(rdatoPersSourceValue)
       
      newlyCreatedRiferimento.RdatiPersonalizzati.add(newRdatoDatoPers)
    }
     
    Riferimenti.add(newlyCreatedRiferimento)
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void MainModule.copyModuleDocumentsCollectionFromSource(
  MainModule sourceMainModule // 
)
{
  if (!(sourceMainModule.ModuleDocuments.loaded))
  {
    sourceMainModule.loadCollectionFromDB(sourceMainModule.ModuleDocuments, ...)
  }
   
  for each ModuleDocument sourceDocument in sourceMainModule.ModuleDocuments
  {
    string sourceModuleDocumentFilePath = sourceDocument.getFilePath(...)
    ModuleDocument newlyCreatedFile = ModuleDocument.create(sourceModuleDocumentFilePath, this.getMainID(), this.getKordApp(), sourceDocument.DESCRIZIONE, QappCore.Loggeduser, sourceDocument.IDDOCCOMMGROUP, ...
             )
     
    ModuleDocuments.add(newlyCreatedFile)
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void MainModule.copyPromemoriaCollectionFromSource(
  MainModule sourceMainModule // 
)
{
  if (!(sourceMainModule.Promemoria.loaded))
  {
    sourceMainModule.loadCollectionFromDB(sourceMainModule.Promemoria, ...)
  }
   
  for each Promemoria sourcePromemoria in sourceMainModule.Promemoria
  {
    Promemoria newlyCreatedPromemoria = Promemoria.create(this.getKordApp(), this.getMainID(), sourcePromemoria.IDTIPOPROMEMORIA)
    newlyCreatedPromemoria.StartTime = sourcePromemoria.StartTime
    newlyCreatedPromemoria.EndTime = sourcePromemoria.EndTime
    newlyCreatedPromemoria.PromemoriaDate = sourcePromemoria.PromemoriaDate
     
    Promemoria.add(newlyCreatedPromemoria)
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void MainModule.copyDocCollegatiCollectionFromSource(
  MainModule sourceMainModule // 
)
{
  sourceMainModule.loadDocCollegati()
   
  for each DocCollegato sourceDocCollegato in sourceMainModule.DocCollegati
  {
    DocCollegato newDocCollegato = DocCollegato.create(this.getKordApp(), this.getMainID(), sourceDocCollegato.IdDocumento, sourceDocCollegato.IdRevisione, sourceDocCollegato.Note)
    DocCollegati.add(newDocCollegato)
  }
}


// ──────────────────────────────────

// **********************************************************************************************
// this method will send an app message to open the appropriate Detail form of the current object
// **********************************************************************************************
public void MainModule.openDetailForm()
{
  QappCore.sendAppMessage("OPEN_DETAIL_FORM", this)
}


// ──────────────────────────────────



// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string MainModule.getCaption(
  string additionalDescription // 
)
{
  QappCore.DTTLogMessage("ABSTRACT", ..., DTTError)
  return ""
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean MainModule.isClosed()
{
  QappCore.DTTLogMessage("ABSTRACT", ..., DTTError)
  return false
}


// ──────────────────────────────────

// *********************
// Method to be extended
// *********************
public int MainModule.setMainHtmlFieldPropertyIndex()
{
  return -999
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void MainModule.convertMainHtmlFieldToTxt()
{
  int mainHtmlFieldPropertyIndex = this.setMainHtmlFieldPropertyIndex()
  int mainTxtFieldPropertyIndex = this.setMainTxtFieldPropertyIndex()
   
  if (mainHtmlFieldPropertyIndex > -1 and mainTxtFieldPropertyIndex > -1)
  {
    string noteHtmlValue = getProperty(mainHtmlFieldPropertyIndex)
    this.setProperty(mainTxtFieldPropertyIndex, Richcontentmanager.HtmlToText(noteHtmlValue))
  }
}


// ──────────────────────────────────

// ***************************************
// Descrivi lo scopo di questa funzione...
// ***************************************
public int MainModule.getMainID()
{
  return null
}


// ──────────────────────────────────

// ***************************************
// Descrivi lo scopo di questa funzione...
// ***************************************
public int MainModule.getKordApp()
{
  return null
}


// ──────────────────────────────────

// ***************************************
// Descrivi lo scopo di questa funzione...
// ***************************************
public int MainModule.getTypeID()
{
  return null
}


// ──────────────────────────────────

// ****************************************
// Describe the purpose of this function...
// ****************************************
public string MainModule.getDescription()
{
  return null
}


// ──────────────────────────────────

// *************************************************************
// Event raised to the document at the end of the save procedure
// *************************************************************
event throws exception MainModule.AfterSave(
  inout boolean Cancel // Boolean output parameter that makes it possible to cancel the entire save. It is equivalent to the Cancel parameter of the BeforeSave event.
)
{
  DevTools.ToBeReviewed("In future we should remove Utente class inheritence from main module and adapt the code.")
  if (this.getKordApp() == Utenti)
    return 
   
   
  // we do not execute after save content for a deleted object
  if (deleted)
    return 
   
  this.SaveCustomDataHistory()
   
  if (DocSpreadsheet)
  {
    if (DocSpreadsheet.updated)
    {
      DocSpreadsheet.saveToDB(...)
    }
  }
   
  // save modified docCollegati objects
  this.handleDocCollegatiSaving()
   
  if (!(QappCommands.loaded))
  {
    this.loadQappCommands()
  }
   
   
   
  DevTools.ToBeReviewed("the code below is commented to avoid that it fires always, we made PBI for it (1267 on Jira)")
   
//  // only Qualibus web should fire hooks: the code below does not consider user has esegui and the executeAlways feeature
//  boolean qualibusWebInitiatedQappCore = isSessionAQualibusWebSession()
//  if (qualibusWebInitiatedQappCore)
//  {
//    for each QappCommand qc in QappCommands
//    {
//      if (qc.isAfterSaveHook())
//      {
//         AfterSaveHook ash = AfterSaveHook.create(this, qc)
//         ash.Perform()
//      }
//    }
//  }
}


// ──────────────────────────────────

// **************************************************************************************
// Event raised to the document during the initial steps of the document saving procedure
// **************************************************************************************
event throws exception MainModule.BeforeSave(
  inout boolean Skip         // A boolean output parameter. If set to True, tells the framework not to perform operations on the database, because they will be managed independently.
  inout boolean Cancel       // A boolean output parameter. If set to True, tells the framework to cancel the save procedure due to errors that occurred. Since the entire document is saved in the same transa...
  int:beforeSavePhases Phase // An integer from 0 to 3 that specifies the progress status of the save procedure as detailed at the beginning of the article.
)
{
  DevTools.ToBeReviewed("In future we should remove Utente class inheritence from main module and adapt the code.")
  if (this.getKordApp() != Utenti)
  {
    if (this.deleted and Phase == PreSave)
    {
      IdCollectionTools.deleteCollectionFromDb(this, Riferimenti)
       
      // delete all linked dato personalizzato and CDataHistory
      this.DeleteOwnedDatoPersonalizzatoData()
       
      IdCollectionTools.deleteCollectionFromDb(this, ModuleDocuments)
       
      // docCollegati must be loaded by method because loadFromDb won't work on a transient collection
      if (!(DocCollegati.loaded))
      {
         this.loadDocCollegati()
      }
      IdCollectionTools.deleteCollectionFromDb(this, DocCollegati)
    }
  }
   
  if (Phase == PreSave)
  {
    this.convertMainHtmlFieldToTxt()
  }
}


// ──────────────────────────────────

// ************************************************************
// Event raised to the document during the validation procedure
// ************************************************************
event MainModule.OnValidate(
  int:validateReasons Reason // An integer specifying the reason the document is being validated. The most common values are 0 to indicate complete validation before saving, or 1 to indicate a quick validati...
  inout boolean Error        // A boolean output parameter. If set to True, specifies an error condition and stops the validation immediately.
  inout boolean Skip         // A boolean output parameter. If set to True, does not check the rules for automatic validation of the document (see notes).
)
{
   
  // centralized trick to skip the validation of a mainModule object
  if (getTag("skipValidation") == true)
  {
    Skip = true
    return 
  }
   
  if (deleted)
  {
    Skip = true
    return 
  }
   
  for each Riferimento r in Riferimenti
  {
    if (!(r.validate(...)))
    {
      Error = true
    }
  }
   
  // validate promemoria
  for each Promemoria p in Promemoria
  {
    boolean isValidPromemoria = p.validate(...)
    Error = !(isValidPromemoria)
  }
   
  this.loadDatiPers()
  for each DatoPersonalizzato dpb in DatiPersonalizzati
  {
    int errorMsg = 0
    if (!(dpb.validate(errorMsg, ...)))
    {
      Error = true
    }
  }
}


// ──────────────────────────────────

// ************************************************************
// Event raised by the document after loading from the database
// ************************************************************
event MainModule.AfterLoad(
  boolean AlreadyLoaded     // A boolean parameter that specifies whether the document was previously loaded.
  IDArray LoadedCollections // IDArray type parameter that contains the collections loaded by the framework. If the value of the Already Loaded parameter is False, the Loaded Collections parameter is NULL.
)
{
   
}


// ──────────────────────────────────

// ****************************************
// Describe the purpose of this function...
// ****************************************
public IDCollection MainModule.GetNonInitializedDatoPersonalizzatoCollection()
{
   
  IDCollection coll of DatoPersonalizzato = new()
   
  IDCollection availableCDSections of CdataSection = null
  availableCDSections = this.GetAvailableCustomDataSections(...)
   
  for each CdataSection cs in availableCDSections
  {
    for each MainModuleDatoPersonalizzatoInfo cdi in cs.CDATAFIELDSINFO
    {
      if (cdi.Active != Yes)
         continue 
       
      cdi.parseCmbValue()
      DatoPersonalizzato dp = DatoPersonalizzato.factory(this, cdi)
       
      QappCore.DTTLogMessage(formatMessage("sezione '|1' dato '|2' ID '|3' retrieving for Module:|4", cs.Name, cdi.Caption, cdi.IDCDATAFLD, this.typeName(), ...), ..., DTTInfo)
      coll.add(dp)
      dp.setOriginal()
    }
  }
   
  return coll
}


// ──────────────────────────────────

// ****************************************
// Describe the purpose of this function...
// ****************************************
public IDCollection MainModule.GetDatoPersonalizzatoCollection()
{
  return DatiPersonalizzati
}


// ──────────────────────────────────

// ****************************************
// Describe the purpose of this function...
// ****************************************
public void MainModule.SetDatoPersonalizzatoCollection(
  IDCollection datopersonalizzatocollection of IDDocument // What is this parameter for?
)
{
  DatiPersonalizzati.clear()
  DatiPersonalizzati.addAll(datopersonalizzatocollection, true)
  DatiPersonalizzati.loaded = true
   
   
}


// ──────────────────────────────────

// ****************************************
// Describe the purpose of this function...
// ****************************************
public void MainModule.SetCustomData(
  string value              // What is this parameter for?
  IDDocument customDataInfo // What is this parameter for?
)
{
  this.setDatoPersonalizzato(value, cast(customDataInfo))
}


// ──────────────────────────────────

// ****************************************
// Describe the purpose of this function...
// ****************************************
public void MainModule.SetRenderingHelper(
  IDDocument renderingHelper // What is this parameter for?
)
{
  MainModuleCdataRenderingHelper = (MainModuleCdataRenderingHelper)renderingHelper
}


// ──────────────────────────────────

// ****************************************
// Describe the purpose of this function...
// ****************************************
public IDDocument MainModule.GetRenderingHelper()
{
  return MainModuleCdataRenderingHelper
}


// ──────────────────────────────────



// ──────────────────────────────────

public void MainModule.SaveCustomDataHistory()
{
  boolean isCDHistoryEnabled = this.isCustomDataHistoryEnabled()
   
  if (CdataIsModified and isCDHistoryEnabled)
  {
    CdataIsModified = false
     
    // crea uno snapshot
    CdataSnapshot snapshottedDato = cast(DatoPersonalizzatoSnapshot.factory(this))
     
    for each DatoPersonalizzato dpb in DatiPersonalizzati
    {
      string value = dpb.getValueAsString()
      if (nullValue(value, "") != "")
      {
         // add mod to the CD History Value 
         snapshottedDato.SetValue(cast(dpb))
      }
    }
     
    CdataSnapshot.add(snapshottedDato)
    CdataSnapshot.saveToDB(1, ...)
  }
}


// ──────────────────────────────────

// ********************************************************************************************************************************
// the owner deletes all the data about owned custom data: extend the method to delete dato presonalizzato collection and snapshots
// ********************************************************************************************************************************
public void MainModule.DeleteOwnedDatoPersonalizzatoData()
{
  if (!(CdataSnapshot.loaded))
  {
    this.loadCdataSnapshot()
  }
  IdCollectionTools.deleteCollectionFromDb(this, CdataSnapshot)
   
  if (!(DatiPersonalizzati.loaded))
  {
    this.loadDatiPers()
  }
  IdCollectionTools.deleteCollectionFromDb(this, DatiPersonalizzati)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean ModuleType.shouldTabBeVisible(
  string:moduleSubformTypes subformType // 
)
{
//  QappCore.DTTLogMessage("ABSTRACT", ...)
  return false
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public int ModuleType.loadFromDescription(
  string description // 
)
{
  int ID = 0
  return ID
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string ModuleType.IDfromDescription(
  string description // 
)
{
  IDDocumentStructure structure = getStructure()
  int propertyCount = structure.getPropertyCount()
  int descriptivePropertyIndex = 0
  int PKPropertyIndex = 0
  for (int i = 1; i <= propertyCount; i = i + 1)
  {
    IDPropertyDefinition property = structure.getPropertyDefinition(i)
    if (property.describeRow)
    {
      descriptivePropertyIndex = i
      break 
    }
  }
  for (int j = 1; j <= propertyCount; j = j + 1)
  {
    IDPropertyDefinition property = structure.getPropertyDefinition(j)
    if (property.PK)
    {
      PKPropertyIndex = j
      break 
    }
  }
  if (descriptivePropertyIndex > 0)
  {
    this.setProperty(descriptivePropertyIndex, description)
    try 
    {
      this.loadFromDB(0)
      int mainID = getProperty(PKPropertyIndex)
      return toString(mainID)
    }
    catch 
    {
      this.init()
      this.setProperty(descriptivePropertyIndex, description)
      int newId = 0
      try 
      {
         boolean ok = saveToDB(...)
         newId = if(ok, getProperty(PKPropertyIndex), 0)
      }
      return toString(newId)
    }
  }
  else 
  {
    return "0"
  }
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public IDMap ModuleType.getValueMap(
  optional boolean export = 0 // 
)
{
  IDMap map = new()
  IDDocumentStructure structure = getStructure()
  int propertyCount = structure.getPropertyCount()
  int descriptivePropertyIndex = 0
  int PKPropertyIndex = 0
  for (int i = 1; i <= propertyCount; i = i + 1)
  {
    IDPropertyDefinition property = structure.getPropertyDefinition(i)
    if (property.describeRow)
    {
      descriptivePropertyIndex = i
      break 
    }
  }
  for (int j = 1; j <= propertyCount; j = j + 1)
  {
    IDPropertyDefinition property = structure.getPropertyDefinition(j)
    if (property.PK)
    {
      PKPropertyIndex = j
      break 
    }
  }
  if (descriptivePropertyIndex > 0)
  {
    IDCollection documents of ModuleType = new()
    this.loadCollectionByExample(documents, false, 0, ...)
     
    for each ModuleType mt in documents
    {
      string description = mt.getProperty(descriptivePropertyIndex)
      int mainID = mt.getProperty(PKPropertyIndex)
      if (export)
      {
         map.setValue(toString(mainID), description)
      }
      else 
      {
         description = replace(upper(description), " ", "")
         map.setValue(description, mainID)
      }
    }
  }
  return map
}


// ──────────────────────────────────

// ************************************************************
// Static function to be used to remove from a string the chars
// that give problems when used in a filename
// To be called to process a filename before creating the file
// ************************************************************
public static string Tools.RemoveBadFileNameChars(
  string InputStr // 
)
{
  string outputStr = ""
   
  outputStr = replace(InputStr, "/", "")
  outputStr = replace(outputStr, "<", "")
  outputStr = replace(outputStr, ">", "")
  outputStr = replace(outputStr, "à", "a")
  outputStr = replace(outputStr, "è", "e")
  outputStr = replace(outputStr, "é", "e")
  outputStr = replace(outputStr, "ì", "i")
  outputStr = replace(outputStr, "ò", "o")
  outputStr = replace(outputStr, "ù", "u")
  outputStr = replace(outputStr, "À", "A")
  outputStr = replace(outputStr, "Á", "A")
  outputStr = replace(outputStr, "È", "E")
  outputStr = replace(outputStr, "É", "E")
  outputStr = replace(outputStr, "Ì", "I")
  outputStr = replace(outputStr, "Í", "I")
  outputStr = replace(outputStr, "Ò", "O")
  outputStr = replace(outputStr, "Ó", "O")
  outputStr = replace(outputStr, "Ù", "U")
  outputStr = replace(outputStr, "Ú", "U")
  //  
  // replace of DoubleQuote (ASCII 34):
  outputStr = replace(outputStr, CHR(34), "")
  //  
  // replace of DoubleQuote (ASCII 34):
  outputStr = replace(outputStr, CHR(34), "")
   
  // replace of other ASCII chars that could be foudn in a description
  outputStr = replace(outputStr, ":", "")
  outputStr = replace(outputStr, "?", "")
  outputStr = replace(outputStr, "*", "")
  outputStr = replace(outputStr, "\\", "")
  outputStr = replace(outputStr, "|", "")
  outputStr = replace(outputStr, "#", "")
  outputStr = replace(outputStr, "™", "")
  //  
  // replace of  LINEFEEED (ASCII 10) and CARRIAGERETURN (ASCII 13)
  outputStr = replace(outputStr, CHR(10), "")
  outputStr = replace(outputStr, CHR(13), "")
   
  // removal of odd ascii chars
  for (int i = 123; i <= 255; i = i + 1)
  {
    outputStr = replace(outputStr, CHR(i), "")
  }
   
  return outputStr
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public static int Tools.DxcolorToInde(
  int DXColor // 
)
{
  int IndeColor = Misc.dxColorToInde(DXColor)
  return IndeColor
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public static int Tools.FloatTimeToMinutes(
  float floatValue // float value between 0 to 1 to express time of day
)
{
  int minutes = 0
  minutes = round(floatValue * 60 * 24, 0)
  return minutes
}


// ──────────────────────────────────



// ──────────────────────────────────

// *****************************************************
// Concatenates string with " - " only if both not empty
// if only one not empty that is the result
// *****************************************************
public static string Tools.Concatenate(
  string FirstString                // 
  string SecondString               // 
  optional string Separator = " - " // 
)
{
  string ConcatenatedStrings = ""
  // 
  if (FirstString = "")
  {
    if (SecondString = "")
    {
      ConcatenatedStrings = ""
    }
    else 
    {
      ConcatenatedStrings = SecondString
    }
     
  }
  else 
  {
    if (SecondString == "")
    {
      ConcatenatedStrings = FirstString
    }
    else 
    {
      ConcatenatedStrings = FirstString + Separator + SecondString
    }
  }
  return ConcatenatedStrings
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public static int Tools.TimeToMinutes(
  time TimeValue // 
)
{
  int hours = 0
  int minutes = 0
  int result = 0
  hours = hour(TimeValue)
  minutes = minute(TimeValue)
  result = hours * 60 + minutes
  return result
}


// ──────────────────────────────────

// *************************************************************************************
// This method will change Time to Float value for e.g. 09:00 will be converted to 0.375
// *************************************************************************************
public static float Tools.TimeToFloat(
  time Time // Time of day
)
{
  float Result = ((hour(Time) + (minute(Time) / 60,0)) / 24,0)
  return Result
}


// ──────────────────────────────────

// *****************************************************************************************************************************
// checks whether the first parameter is a well written char separator list of positive (>=0) integers, default separator is ";"
// 
// invalid lists are:
// 1;2;3; - because it ends with separator
// 1;two;3 - because "two" is not a number
// 1.0;2;3 - because 1.0 is not an integer
// -1;0;2 - because -1 is negative
// *****************************************************************************************************************************
public static boolean Tools.ValidCharSeparatedListOfPositiveIntegers(
  string charSeparatedList        // 
  optional string separator = ";" // 
)
{
   
  // we do not allow that Filter endsby ";" even if StringTokenizer allows it, to avoid managing this case in Delphi
  string lastCharInFilter = right(charSeparatedList, 1)
  if (lastCharInFilter = separator)
  {
    return false
  }
   
   
  StringTokenizer st = new()
  st.setString(charSeparatedList, separator, ...)
   
  boolean onlyPositiveIntegersFound = true
   
  while (st.hasNextToken())
  {
    string currentElement = st.nextToken()
     
    boolean elementIsANumber = isNumber(currentElement)
     
    boolean elementIsAnInteger = false
     
    boolean elementIsAPositiveinteger = false
     
    if (elementIsANumber)
    {
      int elementAsInteger = toInteger(currentElement)
      float elementAsFloat = toFloat(currentElement)
      elementIsAnInteger = (elementAsInteger == elementAsFloat)
      elementIsAPositiveinteger = (elementAsInteger >= 0) and elementIsAnInteger
       
    }
    if (!(elementIsAPositiveinteger) or !(elementIsANumber))
    {
      onlyPositiveIntegersFound = false
      break 
    }
  }
   
  return onlyPositiveIntegersFound
}


// ──────────────────────────────────

// *************************************************************************************
// *************************************************************************************
// *************************************************************************************
// This method will change Float to Time value for e.g. 0.375 will be converted to 09:00
// *************************************************************************************
// *************************************************************************************
// *************************************************************************************
public static time Tools.FloatToTime(
  float value // Time expressed in float as in Qualibus
)
{
  int hours = floor(value * 24)
   
  int minutes = round(((value * 24) - hours) * 60, 2)
   
  time t = toTime(hours, minutes, 0)
   
  return t
}


// ──────────────────────────────────

// *************************************************************************************************
// returns the string to be used to create a properly defined new line char in a qualibus memo field
// *************************************************************************************************
public static string Tools.getNewLineChar()
{
  return CHR(13) + CHR(10)
}


// ──────────────────────────────────

// *************************************************************************************************************************************
// This method allows to rename a STYLE_ID of a QappDataStyle, by taking care of the FK to QappStlye, QappStyle is updated too
// 
// It makes sense to transform data on a db where data have been saved with a name we want to modify and it is a convenient way to do it
// 
// In principle this should be run once only on a production db
// 
// Note: to be used at own risk, if the passed parameters have wrong values the consequences can be bad
// *************************************************************************************************************************************
public static void Tools.RenameDataStyle(
  string oldStyleId // 
  string newStyleId // 
)
{
  QualibusDB.maxRows = 1
  int firstIdApplicazioneFound = 0
  select into variables (found variable)
    set firstIdApplicazioneFound = IDAPPLICAZIONE
  from 
    NGTAPPLICAZIONI // master table
  order by
    IDAPPLICAZIONE
   
  // inserisco uno style farlocco TEMP
  insert values into NGTQAPPSSTYLES (last value variable)
    set IDQAPPSTYLE = 999999
    set ICONQUALIBUS = null
    set ICONQMOBILE = null
    set STYLEID = "TEMP"
    set COLOR = 0
    set IDAPPLICAZIONE = firstIdApplicazioneFound
    set SEQ = 1
   
  // metto a TEMP lo stile di tutti i dati che usano lo stile deprecato
  update NGTQAPPSDATA
    set STYLEID = "TEMP"
  where
    STYLEID == oldStyleId
   
  // ora che nessun dato è più con stile deprecato posso metttere lo style id giusto nello stile
  update NGTQAPPSSTYLES
    set STYLEID = newStyleId
  where
    STYLEID == oldStyleId
   
  // ora metto lo stile definitivo sui dati con stile TEMP
  update NGTQAPPSDATA
    set STYLEID = newStyleId
  where
    STYLEID == "TEMP"
   
  delete from NGTQAPPSSTYLES
  where
    STYLEID == "TEMP"
}


// ──────────────────────────────────

// *******************************************************
// Given a class, returns the corresponding kordapp number
// *******************************************************
public static int Tools.getKordappForClassname(
  string classname // 
)
{
  int kordApp = 0
   
  switch (classname)
  {
    case Personale.className(false):
      kordApp = Personale
    break
    case Clifor.className(false):
      kordApp = ClientiFornitori
    break
    case Progetto.className(false):
      kordApp = Progetti
    break
    case Evento.className(false):
      kordApp = Eventi
    break
    case Privati.className(false):
      kordApp = Privati
    break
    case Funzione.className(false):
      kordApp = Funzioni
    break
    case Articolo.className(false):
      kordApp = Articoli
    break
    case AltreAnagrafiche.className(false):
      kordApp = AltreAnagrafiche
    break
  }
   
  return kordApp
}


// ──────────────────────────────────

// ***********************************************************************************************************************************
// given a document, a string with the concatenation of the errors is returned
// 
// if the addHtmlFormatting parameter is true, some HTML elements are added to have a string ready to be shown in a IdForm.ShowMessage
// ***********************************************************************************************************************************
public static string Tools.getErrorString(
  IDDocument document                    // 
  optional boolean addHtmlFormatting = 0 // 
)
{
  IDDocumentStructure idds = document.getStructure()
  int propertyCount = idds.getPropertyCount()
   
  string retrievedError = null
  for (int j = 1; j <= propertyCount; j = j + 1)
  {
    IDPropertyDefinition idpd = idds.getPropertyDefinition(j)
    string name = idpd.UIName
    string error = document.getPropertyErrorByIndex(j)
    if (error != "")
    {
      string currentError = null
       
      if (addHtmlFormatting)
      {
         currentError = formatMessage("<br>&emsp;&emsp;<b>|1</b> - |2", name, error, ...)
      }
      else 
      {
         currentError = formatMessage("|1 - |2", name, error, ...)
      }
       
      retrievedError = SH.Concat(retrievedError, currentError, ";")
    }
  }
  return retrievedError
}


// ──────────────────────────────────

// *******************************************************************************************************************************************
// given database, table, image fields this method creates a query, runs it and returns the image extension of the blob contained in the field
// 
// the return value is the extension, these are the possible results: png, gif, jpg, bmp (note: "." is not in the result!)
// *******************************************************************************************************************************************
public static string Tools.extractFileExtension(
  IDDatabase IDDatabase                    // 
  string tableName                         // 
  string imageFieldName                    // 
  optional string primaryKeyFieldName = "" // 
  optional string primaryKeyAsString = ""  // 
)
{
  IDDatabase idd = cast(QualibusDB.me())
   
  string runTimeSQl = ""
   
   
  int:DBTypes databaseType = idd.DBType
  if (databaseType == SqlServer2005/2008)
  {
     
    // SQL SERVER
    runTimeSQl = "select CASE "
    runTimeSQl = runTimeSQl + formatMessage("WHEN SUBSTRING(|1, 1, 2) = 0x8950 THEN 'png'", imageFieldName, ...)
    runTimeSQl = runTimeSQl + formatMessage("WHEN SUBSTRING(|1, 1, 2) = 0x4749 THEN 'gif'", imageFieldName, ...)
    runTimeSQl = runTimeSQl + formatMessage("WHEN SUBSTRING(|1, 1, 2) = 0xFFD8 THEN 'jpg'", imageFieldName, ...)
    runTimeSQl = runTimeSQl + formatMessage("WHEN SUBSTRING(|1, 1, 2) = 0x424D THEN 'bmp'", imageFieldName, ...)
    runTimeSQl = runTimeSQl + "ELSE '???' END "
    runTimeSQl = runTimeSQl + formatMessage("FROM |1 ", tableName, ...)
  }
  else if (databaseType == Postgres)
  {
    // POSTGRES
    runTimeSQl = "select CASE "
    runTimeSQl = runTimeSQl + formatMessage("WHEN SUBSTRING(|1, 1, 2) = 0x8950 THEN 'png'", imageFieldName, ...)
    runTimeSQl = runTimeSQl + formatMessage("WHEN SUBSTRING(|1, 1, 2) = 0x4749 THEN 'gif'", imageFieldName, ...)
    runTimeSQl = runTimeSQl + formatMessage("WHEN SUBSTRING(|1, 1, 2) = 0xFFD8 THEN 'jpg'", imageFieldName, ...)
    runTimeSQl = runTimeSQl + formatMessage("WHEN SUBSTRING(|1, 1, 2) = 0x424D THEN 'bmp'", imageFieldName, ...)
    runTimeSQl = runTimeSQl + "ELSE '???' END "
    runTimeSQl = runTimeSQl + formatMessage("FROM |1 ", tableName, ...)
    QappCore.DTTLogMessage("NEVER DONE IN POSTGRES THIS IS A PURE PLACEHOLDER FOR THE FUTURE!!!", ..., DTTError)
     
  }
   
  // COMMON WHERE (we append it only it the last 2 params are not empty
   
  if (primaryKeyFieldName != "" and primaryKeyAsString != "")
  {
    runTimeSQl = runTimeSQl + formatMessage("WHERE |1 = '|2'", primaryKeyFieldName, primaryKeyAsString, ...)
  }
   
  Recordset r = IDDatabase.SQLQuery(runTimeSQl)
   
  r.moveFirst()
   
  string extension = r.getFieldValueIdx(1)
   
  return extension
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static string Tools.getDatabaseName(
  IDDatabase IDDatabase // 
)
{
  string connectionString = IDDatabase.defaultConnectionString
   
  string DBnamePrefix = "Initial Catalog"
//  java code
//  {
//    DBnamePrefix = "databaseName"
//  }
   
  string DBname = ""
   
  QappCore.DTTLogMessage(connectionString, 98875 - 1, ...)
   
  // parese the CS to extract DB name, in .net at least
   
  IDArray ida = SH.tokenizeToArray(connectionString, ";", ...)
//  QappCore.DTTLogMessage(ida.getValue(0), 1111, ...)
//  QappCore.DTTLogMessage(ida.getValue(1), 1112, ...)
//  QappCore.DTTLogMessage(ida.getValue(2), 1113, ...)
//  QappCore.DTTLogMessage(ida.getValue(3), 1114, ...)
//  QappCore.DTTLogMessage(ida.length(), 1115, ...)
  for (int i = 0; i < ida.length(); i = i + 1)
  {
    string idaValue = ida.getValue(i)
    if (idaValue != "")
    {
      string leftValue = SH.leftUpToDelimiter(idaValue, "=", ...)
      if (leftValue == "Initial Catalog" || leftValue == "databaseName")
      {
         DBname = SH.rightUpToDelimiter(idaValue, "=", ...)
      }
//      QappCore.DTTLogMessage(leftValue, 1114, ...)
    }
//    QappCore.DTTLogMessage(ida.getValue(i), 1161, ...)
  }
//  DBname = "QualibusDemoDev"
  return DBname
}


// ──────────────────────────────────

// ********************************************************************
// helper tool that scans the passed collection to find any error in it
// ********************************************************************
public static boolean Tools.isInError(
  IDCollection collection of IDDocument // 
)
{
  boolean anyErrorFound = false
  for each IDDocument doc in collection
  {
    if (doc.isInError(...))
    {
      anyErrorFound = true
      break 
    }
  }
  return anyErrorFound
}


// ──────────────────────────────────

// *****************************************************
// helper tool to serach for a specific char in a string
// *****************************************************
public static boolean Tools.stringContainsChar(
  string inputString // 
  string character   // 
)
{
  if (length(character) != 1)
  {
    QappCore.DTTLogMessage("char must be 1 in length", ..., DTTError)
  }
   
  boolean charContainedInString = find(inputString, character, ...) > 0
  return charContainedInString
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static string Tools.HtmlToRtf(
  string origin // 
)
{
  if (length(origin) == 0)
  {
    return ""
  }
   
  string result = ""
   
  int position = find(origin, "<", 0)
  string noStartSpacesString = mid(origin, position, ...)
   
  if (length(noStartSpacesString) > 5)
  {
    string base64Rtf = OnlyOfficeConverter.ConvertToDocumentType(noStartSpacesString, "html", "rtf", ...)
     
    string decodedRtf = trim(base64Decode(base64Rtf))
    if (lower(left(decodedRtf, 7)) == "blobrtf")
    {
      result = SH.removeLeftChars(decodedRtf, 7)
    }
    else 
    {
      result = decodedRtf
    }
  }
   
  return result
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static string Tools.AppendMessageToNewLine(
  string message   // 
  string toBeAdded // 
)
{
  if (message != "")
  {
    message = message + "<br/>"
  }
  message = message + toBeAdded
   
  return message
}


// ──────────────────────────────────

// ***********************************************************************************************************************************
// returns the value to be bassed in the last parameter of DTTLogMessage, useful to avoid DTTError in unit tests (to avoid "RED" mark)
// ***********************************************************************************************************************************
public static int Tools.getAlternativeForDttErrorInUnitTests(
  optional int:DTTMessageTypes DTTMesssageTypeForUnitTests = 2 // defaults to DTTWarning
)
{
  int DttMessage = null
   
  boolean inUnitTestApp = find(lower(QappCore.mainCaption), "unit", ...) > 0
  if (inUnitTestApp)
  {
    DttMessage = DTTMesssageTypeForUnitTests
  }
  else 
  {
    DttMessage = DTTError
  }
  return DttMessage
}


// ──────────────────────────────────

// *************************************************
// Add the elements of the second array to the first
// *************************************************
public static IDArray Tools.mergeArrays(
  IDArray first  // 
  IDArray second // 
)
{
  IDArray result = new()
   
  if (first == null and second == null)
  {
    return result
  }
  if (first == null)
  {
    return second
  }
  if (second == null)
  {
    return first
  }
   
  for (int i = 0; i < first.length(); i = i + 1)
  {
    int dt = first.getDataType(i)
    switch (dt)
    {
      case Object:
         result.addObject(first.getObject(i))
      break
      default:
         result.addValue(first.getValue(i))
      break
    }
  }
   
  for (int i1 = 0; i1 < second.length(); i1 = i1 + 1)
  {
    int dt = second.getDataType(i1)
    switch (dt)
    {
      case Object:
         result.addObject(second.getObject(i1))
      break
      default:
         result.addValue(second.getValue(i1))
      break
    }
  }
  return result
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static string Tools.ArrayToString(
  IDArray inputArray              // 
  optional string separator = " " // 
)
{
  string result = ""
   
  for (int i = 0; i < inputArray.length(); i = i + 1)
  {
    result = result + inputArray.getValue(i)
     
    if (i < (inputArray.length() - 1))
    {
      result = result + separator
    }
  }
   
  return result
}


// ──────────────────────────────────

// **********************************************************************************
// Take a string and insert a <br/> html tag to visually split the string to new line
// **********************************************************************************
public static string Tools.AddNewLineToLongStrings(
  string stringToModify                   // string to split
  int lineMinimalLenght                   // expected minimal string lenght
  optional string newLineString = "<br/>" // default is <br/>
)
{
  // if lenght of string minor of the line expected lenght
  if (length(stringToModify) <= lineMinimalLenght)
    return stringToModify
   
  // prevent errors sanitizing parameter
  if (lineMinimalLenght < 1)
    lineMinimalLenght = 1
   
  string result = ""
   
  // trim the space at the end if exists
  stringToModify = trimRight(stringToModify)
   
  int partStartPos = 0
  string toAdd = ""
  for (int cursor = 0; cursor < length(stringToModify); cursor = cursor + 1)
  {
     
    int partLenght = 0
    string char = mid(stringToModify, cursor, 1)
    if (char == " ")
    {
      partLenght = cursor - partStartPos
      if (partLenght >= lineMinimalLenght)
      {
         toAdd = trim(mid(stringToModify, partStartPos, partLenght))
         result = result + ifEqual(length(result), 0, "", newLineString) + toAdd
         partStartPos = cursor + 1
      }
    }
  }
   
  // add the final part
  toAdd = mid(stringToModify, partStartPos, length(stringToModify))
  result = result + ifEqual(length(result), 0, "", newLineString) + toAdd
   
  return result
}


// ──────────────────────────────────

// *************************************************
// method to compute html text for Icon with Fa icon
// one must pass string extension with "." eg ".pdf"
// *************************************************
public static string Tools.computeHtmlIcon(
  string fileExtension // 
)
{
  string estensione = trim(lower(fileExtension))
  if (mid(fileExtension, 1, 1) != ".")
  {
    estensione = "." + estensione
  }
  string icona = ""
   
  // Scrivi un commento per questo blocco o premi backspace per eliminare questo commento
  switch (estensione)
  {
    case .pdf:
      icona = "{{icon-fa-file-pdf}}"
    break
    case .doc:
      icona = "{{icon-fa-file-word}}"
    break
    case .docx:
      icona = "{{icon-fa-file-word}}"
    break
    case .xls:
      icona = "{{icon-fa-file-excel}}"
    break
    case .xlsx:
      icona = "{{icon-fa-file-excel}}"
    break
    case .rtf:
      icona = "{{icon-fa-file-word}}"
    break
    default:
      icona = "{{icon-fa-file}}"
    break
  }
   
  // we return icon + " " + estensione because in this way iconHtml contains both image and text and it can be used directly in panels
  string iconHtml = formatMessage("|1 |2", icona, estensione, ...)
  return iconHtml
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static IDArray Tools.SingleStringToArray(
  string stringValue // 
)
{
  IDArray ida = new()
  ida.addValue(stringValue)
   
  return ida
}


// ──────────────────────────────────

// *************************************************************************************************
// centralized way to call Exit
// this is useful for debugging the exit, by modifying this method we can study what happens on exit
// call always performExit and never exit directly
// 
// logoutUrl is optional and defaults to "???"
// *************************************************************************************************
public static void Tools.performExit(
  optional string logoutURl = "???" // 
)
{
  QappCore.exit(logoutURl)
}


// ──────────────────────────────────

// *******************************************************************************
// created to use instead of "addValueSourceRow" to avoid a 26.0 inde bug in java.
// *******************************************************************************
public static void Tools.addRowToRecordset(
  Recordset rs        // 
  string firstColumn  // 
  string secondColumn // 
)
{
  Collection c = new()
  c.addString(firstColumn)
  c.addString(secondColumn)
  rs.addRow(c)
}


// ──────────────────────────────────



// ──────────────────────────────────

// **************************************************************************************************************
// the stored connection properties (useranme, passord and connection string) are applied again to the connection
// **************************************************************************************************************
public void QualibusDatabaseHelper.applyStoredSettings()
{
  IDDatabase.defaultUserID = StoredUsername
  IDDatabase.defaultPassword = StoredPassword
  IDDatabase.defaultConnectionString = StoredConnectionString
   
  // connection must be closed so at the next query the settings will be applied and the connection reopened
  IDDatabase.closeConnection(true)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static QualibusDatabaseHelper QualibusDatabaseHelper.create()
{
  QualibusDatabaseHelper qdh = new()
  qdh.setDatabase(QualibusDB.me())
  return qdh
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void QualibusDatabaseHelper.setDatabase(
  IDDatabase IDDatabase // 
)
{
  IDDatabase = IDDatabase
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static MapElement MapElement.create(
  string key   // 
  string value // 
)
{
  MapElement me = new()
  me.Key = key
  me.Value = value
  return me
}


// ──────────────────────────────────

********************* Method to be extended *********************

// ──────────────────────────────────

// *****************************************************************************************
// the connection to the database is done in this method:
// 1) connection is prepared for the Java enviornement
// 2) connection is done to the CUstom Qualibus database
// 3) if connection to Qualibus is not ok, an error is shown to the user in the login screen
// *****************************************************************************************
public static void DatabaseConnector.connectToDatabases()
{
  DatabaseConnector.initializeNetCoreConnectionFromFile()
  QappCore.initializeJavaDatabaseConnection()
  QappCore.setCustomQualibusDatabaseConnection()
  QappCore.checkThatAdminDatabaseConnectionIsOk()
}


// ──────────────────────────────────

// *************************************************************************************
// Retrieves from config file the connectioninfo and creaetes the java connection string
// *************************************************************************************
private static void DatabaseConnector.initializeJavaDatabaseConnection()
{
//  // 
//  java
//  {
//    string DBServer = QappCore.getSetting(Application, "QBServer")
//    string DBDatabase = QappCore.getSetting(Application, "QBDatabase")
//    string DBUser = QappCore.getSetting(Application, "QBUser")
//    string DBPassword = QappCore.getSetting(Application, "QBPassword")
//     
//    QappCore.DTTLogMessage(formatMessage("initializeJavaDatabaseConnection, retrieved parameters from web.xml: Server '|1', database '|2', username '|3', password '|4'", DBServer, DBDatabase, DBUser, 
//          DBPassword, ...), ..., DTTInfo)
//     
//    QappCore.connectToQualibusDatabase(DBServer, DBDatabase, DBUser, DBPassword)
//  }
   
   
   
   
   
}


// ──────────────────────────────────

// **********************************************************************************
// the connection of CustomQualibus database is set copying it from Qualibus database
// **********************************************************************************
public static void DatabaseConnector.setCustomQualibusDatabaseConnection()
{
  CustomQualibus.defaultConnectionString = QualibusDB.defaultConnectionString
  CustomQualibus.defaultUserID = QualibusDB.defaultUserID
  CustomQualibus.defaultPassword = QualibusDB.defaultPassword
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private static void DatabaseConnector.checkThatAdminDatabaseConnectionIsOk()
{
  // 
  in net to help developers
  {
    try 
    {
       
      // perform a query just to verify no errors on connection occur...
      QualibusDB.SQLExecute("SELECT @@VERSION")
    }
    catch 
    {
       
      // ... so in case we can inform the user to speed up the identification of a connection problem
      IDMap idm = new()
      idm.setValue("LOGIN_MESSAGE", "ERROR: impossible to estabilish a connection as administrator to the database, please check the connection properties to the Qualibus database")
      QappCore.sendAppMessage("SET_LOGIN_MESSAGE", idm)
       
      QappCore.DTTLogMessage("The connection to the Qualibus database is not active, check the connection parameters", ..., DTTError)
    }
  }
}


// ──────────────────────────────────

// *******************************************
// db connection in case of netcore is read by
// qualibus_settings_custom
// or
// qualibus_settings
// *******************************************
public static void DatabaseConnector.initializeNetCoreConnectionFromFile()
{
   
  boolean skipNetCoreArrangement = false
   
//  // search for a customized file 
//  java
//  {
//    skipNetCoreArrangement = true
//  }
   
  if (!(skipNetCoreArrangement))
  {
    string actualConnectionFile = ""
     
     
    string customFilePath = this.getCustomConnectionFile()
    string defaultFilePath = this.getDefaultConnectionFile()
     
    if (fileExists(customFilePath))
    {
      actualConnectionFile = customFilePath
    }
    else if (fileExists(defaultFilePath))
    {
      actualConnectionFile = defaultFilePath
    }
    else 
    {
      QappCore.DTTLogMessage("NetCore connection file not found, using design time connection settings", ..., DTTInfo)
    }
     
    if (actualConnectionFile != "")
    {
      string user = ""
      string password = ""
      string connectionString = this.readConnectionStringFromQualibusSettings(actualConnectionFile, user, password)
       
      QualibusDB.defaultUserID = user
      QualibusDB.defaultPassword = password
      QualibusDB.defaultConnectionString = connectionString
       
      QappCore.DTTLogMessage("netcore connection string has been set, this won't work in net 4.0", ..., DTTWarning)
    }
  }
   
}


// ──────────────────────────────────

// ***********************************************************************************
// given a path to the QualibusSettings file it returns the net core connection string
// ***********************************************************************************
private static string DatabaseConnector.readConnectionStringFromQualibusSettings(
  string qualibusSettingsFilePath // 
  inout string defaultUser        // 
  inout string defaultPassword    // 
)
{
  if (!(fileExists(qualibusSettingsFilePath)))
  {
    QappCore.DTTLogMessage("File not found", ..., DTTError)
    return ""
  }
   
  int fileNumber = QappCore.freeFile()
  QappCore.openFileForInput(qualibusSettingsFilePath, fileNumber, ...)
   
  string server = ""
  string port = ""
  string database = ""
  string user = ""
  string password = ""
   
  QappCore.readLine(fileNumber, server)
  server = replace(server, "SERVER=", "")
  QappCore.readLine(fileNumber, port)
  port = replace(port, "PORT=", "")
  QappCore.readLine(fileNumber, database)
  database = replace(database, "DBNAME=", "")
  QappCore.readLine(fileNumber, user)
  user = replace(user, "USER=", "")
  QappCore.readLine(fileNumber, password)
  password = replace(password, "PASSWORD=", "")
   
  defaultUser = user
  defaultPassword = password
   
  // setting the port
  server = formatMessage("|1,|2", server, port, ...)
   
  string netCoreConnectionString = formatMessage("Data Source=|1;Initial Catalog=|2;Persist Security Info=False", server, database, ...)
   
   
  return netCoreConnectionString
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private static string DatabaseConnector.getDefaultConnectionFile()
{
  return QappCore.path() + "/qualibus_settings"
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private static string DatabaseConnector.getCustomConnectionFile()
{
  string defaultFilePath = this.getDefaultConnectionFile()
  return defaultFilePath + "_custom"
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static ImportMetadato ImportMetadato.create(
  int sequence                     // 
  string caption                   // 
  optional int propertyIndex = 0   // 
  optional MainModuleDatoPersonalizzatoInfo cdataFieldInfo // 
  optional boolean mandatory = 0   // 
  optional string lookupClass = "" // 
  optional IDMap lookupMap         // 
)
{
  ImportMetadato dm = new()
  dm.Sequence = sequence
  dm.Caption = caption
  dm.PropertyIndex = propertyIndex
  dm.MainModuleDatoPersonalizzatoInfo = cdataFieldInfo
  dm.Mandatory = mandatory
  dm.LookupClass = lookupClass
  dm.LookupMap = lookupMap
   
  return dm
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string SimpleLoginInfoEncrypter.encrypt(
  string username // 
  string password // 
)
{
   
  // user and password are encoded in base64 with a unique separator
  string encryptedLoginInfo = base64Encode(username + this.getUniqueSeparator() + password)
   
  return encryptedLoginInfo
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void SimpleLoginInfoEncrypter.decrypt(
  string encryptedString // 
  inout string username  // 
  inout string password  // 
)
{
   
  string retrievedUsername = ""
  string retrievedPassword = ""
  string retrievedBlock = encryptedString
  try 
  {
    string decodedInformation = base64Decode(retrievedBlock)
    int positionOfSeparator = find(decodedInformation, this.getUniqueSeparator(), ...)
    retrievedUsername = left(decodedInformation, positionOfSeparator - 1)
    retrievedPassword = replace(decodedInformation, retrievedUsername + this.getUniqueSeparator(), "")
  }
  catch 
  {
    // show error only when not in unit tests app
    if (find(lower(QappCore.mainCaption), "test", ...) == 0)
    {
      QappCore.DTTLogMessage(formatMessage("impossible to decode |1", encryptedString, ...), ..., DTTError)
    }
    retrievedUsername = ""
    retrievedPassword = ""
  }
  username = retrievedUsername
  password = retrievedPassword
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private string SimpleLoginInfoEncrypter.getUniqueSeparator()
{
  return "FC3E3387-485E-4685-86EC-622E62EE442C"
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static IDDocument SmartLookupHelper.getNewObjectByClassName(
  string className // 
)
{
  IDDocument doc = QappCore.createFormFromLibrary(this.getLibraryFileName(), className)
  return doc
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private static string SmartLookupHelper.getLibraryFileName()
{
  string qappcoreFileName = "QappCore."
   
//  // 
//  java
//  {
//    qappcoreFileName = qappcoreFileName + "jar"
//  }
  // 
  net
  {
    qappcoreFileName = qappcoreFileName + "dll"
  }
   
  return qappcoreFileName
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static string SmartLookupHelper.getDescriptiveProperty(
  string className // 
  int primaryKey   // 
)
{
  IDDocument doc = this.getNewObjectByClassName(className)
   
   
   
  IDDocumentStructure idds = doc.getStructure()
  int pkIdx = 0
  int descriptiveIdx = 0
  for (int i = 1; i <= idds.getPropertyCount(); i = i + 1)
  {
    IDPropertyDefinition idpd = idds.getPropertyDefinition(i)
    if (idpd.PK)
    {
      pkIdx = i
    }
    if (idpd.describeRow)
    {
      descriptiveIdx = i
    }
  }
   
  doc.setProperty(pkIdx, primaryKey)
  doc.loadFromDB(...)
  string descriptivePropertyValue = doc.getProperty(descriptiveIdx)
  return descriptivePropertyValue
}


// ──────────────────────────────────

// *******************************************************************************************************************************************************************************************
// given a className and an "array of integer IDs passed as a recordset" this method returns a recordset well formed, ready to be used in a on get smart lookup event
// 
// the rules are:
// - the returned recordset will contain IDs (the same as the idsOnlyRecordset) and the description (computed thanks to descriptive properties of the class)
// - data is filtered based on searchString
// 
// notes: the advantage is that the complexity of creating a recordset with the correct field names and filtering by searchString are handled by the method
// 
// IMPORTANT: this should not be used on classes with many records (such As clifor or employee) or it would  be too slow, for this a check on idsOnlyRecordset length is done at the beginning
// 
// *******************************************************************************************************************************************************************************************
public static Recordset SmartLookupHelper.getWellFormedLookupRecordset(
  string searchString        // 
  string className           // 
  Recordset idsOnlyRecordset // a recordset containing only int IDs
)
{
  if (idsOnlyRecordset.recordCount() > 100)
  {
    QappCore.DTTLogMessage("it is not advisable to use this method for recordsets so big, use the query approach instead", ..., DTTError)
  }
  if (!(this.isAnIdsOlnyRecordset(idsOnlyRecordset)))
  {
    QappCore.DTTLogMessage("you must pass a recordset containing int ids only", ..., DTTError)
    return null
  }
   
  Recordset computedRs = new()
  RecordsetMetaData rmd = new()
  rmd.setColumnCount(2)
   
  Collection specificLookupFieldNames = this.getSpecificClassIdAndDescriptionLookupFieldNamesTuple(className)
  rmd.setFieldType(1, Integer)
  rmd.setFieldType(2, Character)
   
  // note that Collection items are zero-based!
  rmd.setFieldName(1, specificLookupFieldNames.getString(0))
  rmd.setFieldName(2, specificLookupFieldNames.getString(1))
  computedRs.setMetaData(rmd)
   
  idsOnlyRecordset.moveFirst()
  while (!(idsOnlyRecordset.EOF()))
  {
    int currentID = toInteger(idsOnlyRecordset.getFieldValueIdx(1))
     
    string currentDescription = SmartLookupHelper.getDescriptiveProperty(className, currentID)
    boolean matchFound = find(lower(currentDescription), lower(searchString), ...) > 0
    if (matchFound or searchString == "*")
    {
      Collection c = new()
      c.addInteger(currentID)
      c.addString(currentDescription)
      computedRs.addRow(c)
    }
     
    idsOnlyRecordset.moveNext()
  }
   
  computedRs.addSortCriteria(2)
  computedRs.doSort()
   
  return computedRs
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private static boolean SmartLookupHelper.isAnIdsOlnyRecordset(
  Recordset recordset // 
)
{
  boolean recordSetContainsOnlyOneIntegerField = false
   
  RecordsetMetaData rmd = (RecordsetMetaData)recordset.getMetaData()
   
  recordSetContainsOnlyOneIntegerField = (rmd.getColumnCount() == 1) and (rmd.getFieldType(1) == Integer)
  return recordSetContainsOnlyOneIntegerField
}


// ──────────────────────────────────

// *******************************************************************************************************************************************************
// in this antipattern method (class name is used as parameter...) we encapsulate the logic of deciding how the smartlookup recordset fields are called 
// 
// for example a smartlookup on PRG INTERFACCE will use IdInterfaccia and FullDesecription, for those fields the framework wants IDINTERF and FULLDESCRIPT
// 
// note: Collection is used to return two strings
// *******************************************************************************************************************************************************
private static Collection SmartLookupHelper.getSpecificClassIdAndDescriptionLookupFieldNamesTuple(
  string className // 
)
{
  Collection c = new()
   
  string idFieldName = ""
  string descriptionFieldName = ""
   
  switch (className)
  {
    case PRGINTERFACCE.className(...):
      idFieldName = "IDINTERF"
      descriptionFieldName = "FULLDESCRIPT"
    break
    case PRGFASI.className(...):
      idFieldName = "IDFASE"
      descriptionFieldName = "FULLCODE"
    break
    default:
      QappCore.DTTLogMessage(formatMessage("class |1 still not supported", className, ...), ..., DTTError)
    break
  }
  c.addString(idFieldName)
  c.addString(descriptionFieldName)
   
  return c
   
}


// ──────────────────────────────────

// ***********************************************************************************************************************************
// call this in the on get smart lookup events
// 
// it is made to centralize the handling of the "no results found in the recordset"
// the 3rd and 4th parameter is only for unit testing purposes (3rd avoids diplaying the error, 4 th is to hold the message to test it
// 
// ***********************************************************************************************************************************
public static void DOEventsImplementationHelper.onGetSmartLookupHandler(
  Recordset lookupRecordset                   // 
  IDDocument currentDocument                  // 
  optional boolean showMessage = 0            // this is meant for testing only
  optional inout string DisplayedMessage = "" // this is meant for testing only
)
{
  if (lookupRecordset.recordCount() == 0)
  {
    IDDocumentStructure idds = currentDocument.getStructure()
    string elementName = idds.UIName
    DisplayedMessage = formatMessage("Nessun |1 da mostrare", elementName, ...)
    if (showMessage)
      QappCore.messageBox(DisplayedMessage)
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static string IconsHelper.getIconFilePath(
  string extension // 
)
{
  string cleanedExtension = lower(trim(replace(extension, ".", "")))
  int iconID = 0
  switch (cleanedExtension)
  {
    case "pdf":
      iconID = 2
    break
    case "xls":
      iconID = 3
    break
    case "xlsx":
      iconID = 3
    break
    case "doc":
      iconID = 4
    break
    case "docx":
      iconID = 4
    break
    case "ppt":
      iconID = 5
    break
    case "pptx":
      iconID = 5
    break
    case "htm":
      iconID = 6
    break
    case "html":
      iconID = 6
    break
    case "bmp":
      iconID = 7
    break
    case "mdb":
      iconID = 8
    break
    case "zip":
      iconID = 9
    break
    case "7z":
      iconID = 9
    break
    case "targz":
      iconID = 9
    break
    case "jpg":
      iconID = 10
    break
    case "png":
      iconID = 10
    break
    case "jpeg":
      iconID = 10
    break
    case "wmf":
      iconID = 10
    break
    case "tif":
      iconID = 10
    break
    case "eml":
      iconID = 11
    break
    case "dwg":
      iconID = 12
    break
    case "dxf":
      iconID = 12
    break
    case "vdm":
      iconID = 12
    break
    case "3dm":
      iconID = 12
    break
    case "gif":
      iconID = 13
    break
    case "txt":
      iconID = 14
    break
    case "wma":
      iconID = 15
    break
    case "mp3":
      iconID = 15
    break
    case "avi":
      iconID = 16
    break
    case "mp4":
      iconID = 16
    break
    case "swf":
      iconID = 16
    break
    case "wmv":
      iconID = 16
    break
    case "odg":
      iconID = 17
    break
    case "odp":
      iconID = 18
    break
    case "ods":
      iconID = 19
    break
    case "odt":
      iconID = 20
    break
    case "rar":
      iconID = 21
    break
    default:
      iconID = 17
    break
  }
   
  string iconFilePath = formatMessage("images/file_icons/D|1.png", iconID, ...)
  return iconFilePath
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static IndeGethttpCall IndeGethttpCall.create(
  IDMap headers                           // 
  string url                              // 
  optional string methodOverrideName = "" // 
)
{
  IndeGethttpCall apic = new()
  apic.init()
  apic.prepareCallContent(methodOverrideName, headers)
  apic.Url = url
  return apic
}


// ──────────────────────────────────

// ******************************
// the api is called with getHTTP
// ******************************
public string IndeGethttpCall.perform()
{
  QappCore.DTTLogMessage(CallContent.getValue("ID_HEADERS"), 565656, ...)
  QappCore.DTTLogMessage(Url, 565656, ...)
   
  string response = QappCore.getHTTP(Url, CallContent, ...)
   
  return response
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void IndeGethttpCall.prepareCallContent(
  string methodOverrideName        // 
  optional IDMap additionalHeaders // 
)
{
  string headersString = ""
  if (methodOverrideName != "")
  {
    headersString = formatMessage("|1: |2", "X-HTTP-Method-Override", methodOverrideName, ...)
  }
  if (additionalHeaders)
  {
    IDArray ida = additionalHeaders.getKeys()
    headersString = headersString + "\n"
    for (int i = 0; i < ida.length(); i = i + 1)
    {
      string key = ida.getValue(i)
      string value = additionalHeaders.getValue(key)
      headersString = headersString + formatMessage("|1: |2", key, value, ...)
      if (i != ida.length() - 1)
      {
         headersString = headersString + "\n"
      }
    }
  }
  CallContent.setValue("ID_HEADERS", headersString)
   
}


// ──────────────────────────────────

// *******************************************
// given a sql script passed as array of lines
// e.g
// select 1
// select 2
// is passed as
// ida[0]= 'select 1', ida[1] = 'select 2'
// *******************************************
public static IDArray SqlCodeHelper.extractExecutableBatches(
  IDArray wholeScriptAsArray // 
)
{
  IDArray sqlBatches = new()
   
   
  boolean insideAMultiLineComment = false
  string script = ""
  for (int i = 0; i < wholeScriptAsArray.length(); i = i + 1)
  {
    string currentLine = trim(wholeScriptAsArray.getValue(i))
     
    removal of inline comment if found
    {
      int positionOfInlineComment = find(currentLine, "--", ...)
      if (positionOfInlineComment > 0)
      {
         currentLine = left(currentLine, positionOfInlineComment - 1)
      }
    }
     
    // 
    management Of Multi Line Comments
    {
      boolean currentLinceContainsAOpenOrCloseMultiLineComment = false
      int positionOfOpenMultiLineComment = find(currentLine, "/*", ...)
       
      string codeBeforeMultiLineComment = ""
      string codeAfterMultiLineComment = ""
      if (positionOfOpenMultiLineComment > 0)
      {
         currentLinceContainsAOpenOrCloseMultiLineComment = true
         codeBeforeMultiLineComment = left(currentLine, positionOfOpenMultiLineComment - 1)
          
      }
      boolean currentLineStartsContainsACloseMultiLineComment = false
       
      int positionOfCloseMultiLineComment = find(currentLine, "*/", ...)
      if (positionOfCloseMultiLineComment > 0)
      {
         currentLinceContainsAOpenOrCloseMultiLineComment = true
         currentLineStartsContainsACloseMultiLineComment = true
         codeAfterMultiLineComment = right(currentLine, length(currentLine) - (positionOfCloseMultiLineComment + 1))
      }
       
      if ((codeBeforeMultiLineComment != "") or (codeAfterMultiLineComment != ""))
         currentLine = SH.Concat(codeBeforeMultiLineComment, codeAfterMultiLineComment, " ")
       
       
      if (currentLinceContainsAOpenOrCloseMultiLineComment)
         insideAMultiLineComment = true
      if (currentLineStartsContainsACloseMultiLineComment)
         insideAMultiLineComment = false
       
      if (insideAMultiLineComment and !(currentLinceContainsAOpenOrCloseMultiLineComment))
         continue 
    }
     
     
    if (currentLine == "")
    {
      continue 
    }
     
    // concatenating in a single line until GO is found
    if (currentLine != "GO")
    {
      script = SH.Concat(script, currentLine, " ")
    }
    else 
    {
      sqlBatches.addValue(script)
      script = ""
    }
  }
  if (script != "")
  {
    sqlBatches.addValue(script)
  }
   
  if (sqlBatches.length() == 0)
  {
    sqlBatches = null
  }
   
  return sqlBatches
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static string Richcontentmanager.HtmlToText(
  string htmlText // 
)
{
  // we search for "html>" so we cover both starts: <!doctype html> and <html>
  boolean inputStringContainsHtmlTag = find(htmlText, "html>", ...) > 0
   
  // extra wrapping for top safety in case the passed string is not a valid html (it might be a part of html only)
  if (!(inputStringContainsHtmlTag))
  {
    htmlText = formatMessage("<html><body>|1</body></html>", htmlText, ...)
  }
  string result = OnlyOfficeConverter.ConvertToDocumentType(htmlText, "html", "txt", ...)
  result = trim(result)
  result = replace(result, CHR(0), "")
  return result
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static string Richcontentmanager.HtmlToRtf(
  string htmlText // 
)
{
  string formattedHtml = formatMessage("<html><body>|1</body></html>", htmlText, ...)
  string convertedText = OnlyOfficeConverter.ConvertToDocumentType(formattedHtml, "html", "rtf", ...)
  string decodedRtf = base64Decode(convertedText)
  string startingString = "{\\rtf1\\"
  decodedRtf = replace(decodedRtf, startingString, "£")
  decodedRtf = SH.rightUpToDelimiter(decodedRtf, "£", ...)
  decodedRtf = startingString + replace(decodedRtf, "£", startingString)
  return decodedRtf
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static string Richcontentmanager.RtfToHtml(
  string rtf // 
)
{
  string result = OnlyOfficeConverter.ConvertToDocumentType(rtf, "rtf", "html", ...)
  return result
}


// ──────────────────────────────────

// ******************************************************************************************************************************************************************************************
// this method allows, given a idDocument, to rename the UI Name of one property
// the property is identified by "UiNameToBeUpdated" and removeSpaces is used to consider or not " " in property serarch ("value 1" with removeSpaces will consider "value1" as a good match)
// newUiNAme is the new name to be set
// ******************************************************************************************************************************************************************************************
public static void IdDocumentTools.updatePropertyUiNameOnSpecificDocument(
  IDDocument idDocument // 
  int propertyIndex     // 
  string newUiName      // 
)
{
  IDDocumentStructure idds = idDocument.getStructure()
  IDPropertyDefinition idpd = idds.getPropertyDefinition(propertyIndex)
  idpd.UIName = newUiName
   
}


// ──────────────────────────────────

// *******************************************************************************************
// given an ID Document and a DB Code this method sets the value of the corresponding property
// 
// propertyValue: string value to be set in the proprety of doc that has a specific DB Code
// *******************************************************************************************
public static void IdDocumentTools.setPropertyValueByDbCode(
  IDDocument doc        // id document to be generically modified
  string propertyDBCode // DB Code of the property to be updated
  string propertyValue  // new value of the property
)
{
  IDDocumentStructure idds = doc.getStructure()
   
  for (int i = 1; i <= idds.getPropertyCount(); i = i + 1)
  {
    IDPropertyDefinition idpd = idds.getPropertyDefinition(i)
    if (idpd.DBCode == propertyDBCode)
    {
      doc.setProperty(i, propertyValue)
      break 
    }
  }
}


// ──────────────────────────────────

// **********************************************************************************************
// given an ID Document and a DB Code this method returns the value of the corresponding property
// 
// propertyValue: string value to be set in the proprety of doc that has a specific DB Code
// **********************************************************************************************
public static string IdDocumentTools.getPropertyValueByDbCode(
  IDDocument doc        // id document to be generically modified
  string propertyDBCode // DB Code of the property to be updated
)
{
  string propertyValue = ""
   
  IDDocumentStructure idds = doc.getStructure()
   
  for (int i = 1; i <= idds.getPropertyCount(); i = i + 1)
  {
    IDPropertyDefinition idpd = idds.getPropertyDefinition(i)
    if (idpd.DBCode == propertyDBCode)
    {
      propertyValue = doc.getProperty(i)
      break 
    }
  }
  return propertyValue
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static void BackgroundWorker.executeCommand(
  string:backgroundWorkerCommands command // 
)
{
  // makes sure the session is started
  QappCore.startSession(this.getServerSessionName(), ...)
   
  // since the session now exists we send it a message
  QappCore.sendSessionMessage(this.getServerSessionName(), command, -1, ...)
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private static string BackgroundWorker.getServerSessionName()
{
  return "BACKGROUND_WORKER"
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static void BackgroundWorker.onSessionMessageHandler(
  inout string message // 
)
{
  if (QappCore.sessionName() == BackgroundWorker.getServerSessionName())
  {
    string:backgroundWorkerCommands command = message
    switch (command)
    {
      case createMissingPrivilegi:
         BackgroundWorker.createMissingPrivilegi()
      break
      default:
         QappCore.DTTLogMessage(formatMessage("command |1 not supported", command, ...), ..., DTTError)
      break
    }
  }
}


// ──────────────────────────────────



// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event QappCoreRuntimeBehavior.OnInit()
{
  Storage = new()
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static void QappCoreRuntimeBehavior.instantiateSingleton()
{
  QappCore.QappCoreRuntimeBehavior = new()
  QappCore.QappCoreRuntimeBehavior.init()
   
  QappCore.QappCoreRuntimeBehavior.SetDefaultValues()
   
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void QappCoreRuntimeBehavior.SetDefaultValues()
{
  this.setBehaviorParameter(showOpenButtonInLookupCdata, toString(true))
   
}


// ──────────────────────────────────

// ***********************************************
// set a property chooisng one from the value list
// ***********************************************
public void QappCoreRuntimeBehavior.setBehaviorParameter(
  string:qappCoreRuntimeBehaviorParameters parameter // 
  string value                                       // 
)
{
  if (Storage == null)
  {
    QappCore.DTTLogMessage("NULL", ...)
  }
   
  Storage.setValue(parameter, value)
}


// ──────────────────────────────────

// ****************************************************
// retrives a property chooisng one from the value list
// ****************************************************
public string QappCoreRuntimeBehavior.getBehaviorParameter(
  string:qappCoreRuntimeBehaviorParameters parameter // 
)
{
  string storedValue = Storage.getValue(parameter)
  return storedValue
}


// ──────────────────────────────────

// ********************************************************
// centralization of deleting and saving to db a collection
// ********************************************************
public static void IdCollectionTools.deleteCollectionFromDb(
  IDDocument owner                      // 
  IDCollection collection of IDDocument // 
)
{
   
   
  if (!(collection.loaded))
    owner.loadCollectionFromDB(collection, ...)
   
  for each IDDocument doc in collection
  {
    doc.deleted = true
  }
  collection.saveToDB(...)
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public IDCollection Utente.GetCalendar(
  date time startDate // data inizio importazione calendario
  date time endDate   // data fine importazione calendario
)
{
  IDCollection res of IDDocument = new()
  // 
  IDCollection memos of Memo = Memo.GetUserMemo(this, startDate, endDate)
  for each Memo m in memos
  {
    res.addRef(m)
  }
   
  IDCollection disposizioni of Disposizione = Disposizione.getUserDisposizioni(this, startDate, endDate)
   
  for each Disposizione d in disposizioni
  {
    res.addRef(d)
  }
   
  IDCollection attività of Attività = Attività.getUserAttività(this, startDate, endDate)
  for each Attività a in attività
  {
    res.addRef(a)
  }
  IDCollection interventi of Intervento = Intervento.getUserInterventi(this, startDate, endDate)
  for each Intervento i in interventi
  {
    res.addRef(i)
  }
  // 
   
  IDCollection programmi of ProgrammaIntervento = ProgrammaIntervento.getUserProgrammaIntervento(this, startDate, endDate)
   
   
  for each ProgrammaIntervento p in programmi
  {
    res.addRef(p)
  }
   
  IDCollection promemoria of Promemoria = new()
  Personale linkedEmployee = this.getLinkedPersonale()
  if (linkedEmployee)
  {
    promemoria = Promemoria.GetEmployeePromemoria(linkedEmployee, startDate, endDate)
    for each Promemoria p in promemoria
    {
      res.addRef(p)
    }
  }
  //  
  // cerco i calendari di tutti i plugin
  IDArray Plugins = QappCore.PluginInterface.GetPluginsNames()
  IDArray calendarParameters = new()
  calendarParameters.addObject(this)
  calendarParameters.addValue(startDate)
  calendarParameters.addValue(endDate)
  for (int p = 0; p < Plugins.length(); p = p + 1)
  {
    PluginInterface PluginInterface = QappCore.PluginInterface.GetPluginInterface(Plugins.getValue(p)) // 
    if (PluginInterface)
    {
      IDArray result = null // nul
       
       
      result = PluginInterface.SendCommand("getCalendar", QappCore.PluginInterface, calendarParameters, Plugins.getValue(p))
      // 
      if (result != null && result.length() > 0)
      {
         IDCollection plgcoll of IDDocument = null
         plgcoll = (IDCollection)result.getObject(0)
         for each IDDocument doc in plgcoll
         {
           res.addRef(doc)
         }
      }
    }
  }
  // 
  res.loaded = true
   
  return res
}


// ──────────────────────────────────

// ****************************************************************************************************************************************************************************************************************
// Procedura statica per generare file ics in cartella disponibile su un server per la sincronizzazione con altri client. Il file andrebbe generato/aggiornato ad ogni modifica del calendario oppure ad intervalli
// regolari.
// ****************************************************************************************************************************************************************************************************************
public static string Utente.generateIcsContent(
  date startDate // Da quando inizia ad esportare
  date endDate   // Fino a che data
  Utente utente  // Utente di cui generare il contenuto Ics
)
{
   
  // ottengo lo username dell'utente per stabilire il nome del file ics
  string user = utente.Username
   
  // header con parametri calendario
  string header = "BEGIN:VCALENDAR\nVERSION:2.0\nPRODID:-//Nord Est Systems/Qualibus\nX-WR-CALNAME:" + user + "\nX-PUBLISHED-TTL:PT1M\nREFRESH-
           INTERVAL;VALUE=DURATION:P1M\nCALSCALE:GREGORIAN\nBEGIN:VTIMEZONE\nTZID:Europe/Rome\nBEGIN:STANDARD\nDTSTART:16011005T030000\nRRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10\nTZOFFSETFROM:+0200\nTZOFFSETTO:+0­
           100\nEND:STANDARD\nBEGIN:DAYLIGHT\nDTSTART:16010305T020000\nRRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3\nTZOFFSETFROM:+0100\nTZOFFSETTO:+0200\nEND:DAYLIGHT\nEND:VTIMEZONE\n"
   
  // footer di chiusura del file
  string footer = "END:VCALENDAR\n"
   
  // caricamento dei dati della collection dalla funzione di estrazione del calendario
  IDCollection res of IDDocument = utente.GetCalendar(startDate, endDate)
   
  // stringa comprensiva di header, footer e tutti gli eventi del calendario
  string eventiCalendario = ""
   
  // per ogni documento presente nel calendario
  for each IDDocument doc in res
  {
    // Formattazione parametro DTSTAMP
    string dtstamp = toString(now())
    dtstamp = format(dtstamp, "yyyymmdd" + "T" + "hhnnss" + "Z", ...)
     
    // Generazione parametro UID
    string uid = doc.getDNA()
    uid = replace(uid, """, "_")
    uid = replace(uid, ",", "@")
     
    // Formattazione parametro DTSTART
    string strStartDate = toString(doc.getNamedPropertyValue("STARTDATE"))
    strStartDate = format(strStartDate, "yyyymmdd", ...)
    string strStartTime = toString(doc.getNamedPropertyValue("STARTTIME"))
    strStartTime = format(strStartTime, "hhnnss", ...)
    string dtstart = strStartDate + "T" + strStartTime
     
    // Formattazione parametro DTEND
    // Al momento non sono supportati eventi su più giornate, la data di fine deve 
    // giocoforza coincidere con quella di inizio
    string strEndDate = strStartDate
     
    // Derivo il tempo di fine da somma tra tempo di inizio e durata
    time endTime = dateAdd(Minute, doc.getNamedPropertyValue("DURATION"), doc.getNamedPropertyValue("STARTTIME"))
     
    // Trasformo nel formato giusto
    string strEndTime = toString(endTime)
    strEndTime = format(strEndTime, "hhnnss", ...)
    string dtend = strEndDate + "T" + strEndTime
     
    // Estrazione parametro SUMMARY
    string summary = doc.getNamedPropertyValue("DESCRIPTION")
     
    // Generazione parametro DESCRIPTION
    // Attualmente su QualibusWeb esiste soltanto la descrizione, che è stata salvata 
    // in SUMMARY, quindi nel titolo dell'evento. DESCRIPTION viene lasciato vuoto
    string description = ""
     
    // Generazione evento e inserimento dei suoi parametri
    string evento = "BEGIN:VEVENT\nDTSTAMP:" + dtstamp + "\nUID:" + uid + "\nDTSTART;TZID=Europe/Rome:" + dtstart + "\nDTEND;TZID=Europe/Rome:" + dtend + "\nSUMMARY:" + summary + "\nEND:VEVENT\n"
     
    // aggiungo in coda all'elenco eventi l'ultimo evento caricato
    eventiCalendario = eventiCalendario + evento
  }
   
  // metto assieme i pezzi del puzzle
  string calendarioCompleto = header + eventiCalendario + footer
   
  return calendarioCompleto
}


// ──────────────────────────────────



// ──────────────────────────────────

// *************************************
// Get Utente object for given ID Utente
// *************************************
public static Utente Utente.get(
  int IdUtente // 
)
{
  Utente utente = null
  utente = new()
  utente.IDUTENTE = IdUtente
  try 
  {
    utente.loadFromDB(...)
  }
  catch 
  {
    utente = null
  }
  return utente
}


// ──────────────────────────────────

// *****************************************************
// returns the Personale linked to the Utente (if found)
// *****************************************************
public Personale Utente.getLinkedPersonale()
{
  Personale retrievedPersonale = null
  if (Personale and Personale.IDDIPENDENTE > 0)
  {
    retrievedPersonale = Personale
  }
  else 
  {
    retrievedPersonale = Personale.getPersonaleLinkedToUtente(this)
  }
  return retrievedPersonale
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public int Utente.IDfromDescription(
  string description // 
)
{
   
  string cleanDescription = replace(upper(description), " ", "")
   
  QualibusDB.maxRows = 1
  int matchingID = 0
  select into variables (found variable)
    set matchingID = IDUTENTE
  from 
    Utenti // master table
  where
    (replace(upper(Username), " ", "") = cleanDescription) or (replace(upper(DESCRUTENTE), " ", "") = cleanDescription)
   
  return matchingID
   
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean Utente.isAnonymous()
{
  TabParametri tp = tp.getInstance()
   
  boolean userIsTheAnonymousOne = tp.IDUTENTEANONYMOUS == IDUTENTE
   
  return userIsTheAnonymousOne
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Utente.skipValidation()
{
  this.setTag("SkipValidation", true)
}


// ──────────────────────────────────



// ──────────────────────────────────

// ***********************************************
// returns the Utente matching with anonymous user
// ***********************************************
public static Utente Utente.retrieveAnonymousUser()
{
  TabParametri tp = TabParametri.getInstance()
   
  Utente u = Utente.get(tp.IDUTENTEANONYMOUS)
  return u
}


// ──────────────────────────────────

// *****************************************************
// returns true if the logged utente is the anonyous one
// *****************************************************
public static boolean Utente.isAnonymousUtenteLoggedIn()
{
  boolean anonymousUserIsLoggedIn = false
   
  Utente u = this.retrieveAnonymousUser()
  if (u)
    anonymousUserIsLoggedIn = (QappCore.Loggeduser.IDUTENTE == u.IDUTENTE)
   
  return anonymousUserIsLoggedIn
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string Utente.getDescrReparto()
{
  string repartoDescr = ""
  Reparto reparto = this.getReparto()
  repartoDescr = reparto.DESCRREPARTO
  return repartoDescr
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public Reparto Utente.getReparto()
{
  Reparto reparto = new()
  reparto.IDREPARTO = IDREPARTO
  try 
  {
    reparto.loadFromDB(...)
  }
  catch 
  {
    reparto = null
    QappCore.DTTLogMessage(formatMessage("Unable to load reporto for ID: |1", IDREPARTO, ...), ...)
  }
  return reparto
   
}


// ──────────────────────────────────

// ********************************************************************************************************************************************
// A user can see SCM Evento only if
// 1. User has Exceute privilege in Eventi module
// 2. User has Per1 in eventi or user has any one Classe role out of Immisione,Analisi,Disposizioni,Documenti,Messagio,Costi,Chiusura,Visibilta
// 3. User has NOT Restrict_open
// ********************************************************************************************************************************************
public boolean Utente.canSeeSCMEvento(
  int IdClasse // 
)
{
  ClasseEvento ce = ClasseEvento.get(IdClasse)
  boolean userHasAnyRoleOnClass = this.hasAnyOneRoleOnEventoClasse(ce)
  boolean userIsPers1InEvento = this.hasSpecificPrivilege(Eventi, Pers1)
  boolean userHasVisibilty = userHasAnyRoleOnClass or userIsPers1InEvento
   
  boolean userHasExecutePrivilege = this.hasSpecificPrivilege(Eventi, Execute)
  boolean userIsNotResctrictedToOpen = (this.hasSpecificRoleOnEventoClasse(ce, Restrict_open) == false)
   
  boolean userCanSeeSCM = userHasExecutePrivilege and userIsNotResctrictedToOpen and userHasVisibilty
  return userCanSeeSCM
}


// ──────────────────────────────────

// *********************************************************************************************************************
// static method to check the Eventi classe roles and findout if any specific role is assigned to user for a given class
// *********************************************************************************************************************
public boolean Utente.hasSpecificRoleOnEventoClasse(
  ClasseEvento classeEvento     // 
  string:classeRoles classeRole // 
)
{
  boolean userHasSpecificRole = false
  EventoClasseRole eventoRoles = new()
  eventoRoles.IDUTENTE = IDUTENTE
  eventoRoles.IDCLASSE = classeEvento.IDCLASSE
  try 
  {
    eventoRoles.loadFromDB(...)
    switch (classeRole)
    {
      case Immissione:
         userHasSpecificRole = eventoRoles.IMMISSIONE == Yes
      break
      case Analisi:
         userHasSpecificRole = eventoRoles.ANALISI == Yes
      break
      case Disposizioni:
         userHasSpecificRole = eventoRoles.DISPOSIZIONI == Yes
      break
      case Costi:
         userHasSpecificRole = eventoRoles.COSTI == Yes
      break
      case Chiusura:
         userHasSpecificRole = eventoRoles.CHIUSURA == Yes
      break
      case Messagio:
         userHasSpecificRole = eventoRoles.MESSAGGIO == Yes
      break
      case Visibilita:
         userHasSpecificRole = eventoRoles.VISIBILITA == Yes
      break
      case Documenti:
         userHasSpecificRole = eventoRoles.DOCUMENTI == Yes
      break
      case Restrict_open:
         userHasSpecificRole = eventoRoles.RESTRICTOPEN == Yes
      break
    }
     
  }
  catch 
  {
    eventoRoles = null
    QappCore.DTTLogMessage(formatMessage("The evento roles for user: |1, classe: |2", DESCRUTENTE, classeEvento.DESCRCLASSE, ...), ...)
  }
   
  return userHasSpecificRole
}


// ──────────────────────────────────

// ********************************************************************************************
// static method to check the any one Eventi classe roles is assigned to user for a given class
// ********************************************************************************************
public boolean Utente.hasAnyOneRoleOnEventoClasse(
  ClasseEvento classeEvento // 
)
{
  boolean userHasAnyRole = false
  EventoClasseRole eventoRoles = new()
  eventoRoles.IDUTENTE = IDUTENTE
  eventoRoles.IDCLASSE = classeEvento.IDCLASSE
  try 
  {
    eventoRoles.loadFromDB(...)
     
    userHasAnyRole = (eventoRoles.IMMISSIONE == Yes) or (eventoRoles.ANALISI == Yes) or (eventoRoles.DISPOSIZIONI == Yes) or (eventoRoles.COSTI == Yes) or (eventoRoles.CHIUSURA == Yes) or (eventoRoles.
             MESSAGGIO == Yes) or (eventoRoles.VISIBILITA == Yes) or (eventoRoles.DOCUMENTI == Yes)
  }
  catch 
  {
    eventoRoles = null
    QappCore.DTTLogMessage(formatMessage("The evento roles for user: |1, classe: |2", DESCRUTENTE, classeEvento.DESCRCLASSE, ...), ...)
  }
   
  return userHasAnyRole
}


// ──────────────────────────────────

// ***************************************************************************************************************************************************************************************************************
// returns true if the user has enough rights to see the passed module
// 
// this is the equivalent of the client server's sf_canOpenItem, anyway it has been renamed to canVIEWItem because this method in facts tells if the user can see (tipically in collegamenti) that a record exists
// 
// Opening requires more rights (only example known at the moment of writing is RESTRIC OPEN role in eventi)
// ***************************************************************************************************************************************************************************************************************
public boolean Utente.canViewItem(
  int:kordapp kordapp // 
  int mainID          // 
)
{
  boolean canOpenModuleItem = false
  switch (kordapp)
  {
    case Eventi:
      Evento e = Evento.getFromDB(mainID, ...)
      if (e and e.loaded)
      {
         boolean thereAreDisposizioniLinkedToUser = Disposizione.thereAreDisposizioniLinkedToUser(e, this)
         boolean userHasClassVisibilitiesRoles = EventoClasseRole.userHasClassVisibilityRoles(e, this)
         boolean userHasAmbitoRoles = EventoAmbitoRole.userhasAmbitoVisibilityRole(e, this)
         boolean userIsResponsibleOfEvento = e.IDUTENTERESP == IDUTENTE
         boolean eventoIsInsertedByUser = e.IDUTENTEINS == IDUTENTE
         canOpenModuleItem = userIsResponsibleOfEvento or eventoIsInsertedByUser or thereAreDisposizioniLinkedToUser or (userHasClassVisibilitiesRoles and userHasAmbitoRoles)
      }
    break
    case AltreAnagrafiche:
      AltreAnagrafiche aa = AltreAnagrafiche.getFromDB(mainID, ...)
      if (aa and aa.loaded)
      {
         boolean userHasOpenPriviledge = CESPERMESSI.userHasSpecificPriviledge(aa, this, Open)
         boolean userIsReferente = aa.IDUTENTEREFERENTE == IDUTENTE
         Personale personale = this.getLinkedPersonale()
         if (personale and personale.loaded)
         {
           boolean linkedEmployeeIsResponsabile = personale.IDDIPENDENTE == aa.IDRESPONSABILE
           canOpenModuleItem = userHasOpenPriviledge or userIsReferente or linkedEmployeeIsResponsabile
         }
      }
    break
    case Progetti:
      Progetto p = Progetto.getFromDB(mainID, ...)
      if (p and p.loaded)
      {
         boolean userIsInterfaccia = PRGINTERFACCE.userIsInterfaceInProject(p, this)
         boolean userIsResposabile = p.IDUTENTERESPONSABILE == IDUTENTE
         canOpenModuleItem = userIsInterfaccia or userIsResposabile
      }
    break
    default:
      canOpenModuleItem = true
    break
  }
   
  return canOpenModuleItem
}


// ──────────────────────────────────

// ************************************************************************************************************************************************************************
// returns true if the user has enough rights to open (=access the data) of the passed module
// 
// note: at the moment of writing the only case for which one can view but nor open relates with restrict_open eventi classe role. (in fact this method checks only for it)
// ************************************************************************************************************************************************************************
public boolean Utente.canOpenItem(
  int:kordapp kordapp // 
  int mainID          // 
)
{
  boolean canOpenModuleItem = true
  boolean canviewModuleItem = this.canViewItem(kordapp, mainID)
   
  if (canviewModuleItem)
  {
    // handle specific cases
    switch (kordapp)
    {
      case Eventi:
         Evento e = Evento.getFromDB(mainID, ...)
         ClasseEvento ce = ClasseEvento.get(e.IDCLASSE)
         boolean restrictOpenOnClass = this.hasSpecificRoleOnEventoClasse(ce, Restrict_open)
          
         canOpenModuleItem = !(restrictOpenOnClass)
      break
    }
  }
  else 
    canOpenModuleItem = canviewModuleItem
   
  return canOpenModuleItem
}


// ──────────────────────────────────



// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static boolean Utente.userExists(
  string userName // 
)
{
  boolean userExists = false
  Utente user = new()
  user.Username = userName
  try 
  {
    user.loadFromDB(...)
    userExists = true
  }
  catch 
  {
    userExists = false
  }
  return userExists
}


// ──────────────────────────────────

// *********************************************************************************************************
// returns true if for the current object the status is such that sql server creation scripts should be used
// 
// Note: true occurs when the object is in a state for which username is either new or modified
// *********************************************************************************************************
public boolean Utente.sqlServerUserCreationIsNeeded()
{
  int usernamePropertyIndex = toPropertyIndex(Username)
  string originalUsername = getOriginalValue(usernamePropertyIndex)
  string currenUsername = getProperty(usernamePropertyIndex)
  boolean userUsernameHasBeenModificated = originalUsername != currenUsername
  return userUsernameHasBeenModificated
}


// ──────────────────────────────────

// ****************************************************************************************************************
// DROP USER SCRIPT, already wrapped with TRY CATCH because in case of non existing user we do not deal with errors
// ****************************************************************************************************************
public string Utente.getSqlServerDropUserScript()
{
  return formatMessage("BEGIN TRY DROP USER [|1] END TRY BEGIN CATCH END CATCH", Username, ...)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string Utente.getSqlServerCreateLoginScript()
{
  if (this.isLdapUser())
  {
    return formatMessage("CREATE LOGIN [|1] FROM WINDOWS", Username, ...)
  }
  else 
  {
    string dbName = Tools.getDatabaseName(QualibusDB.me())
    return formatMessage("CREATE LOGIN [|1] WITH PASSWORD = '|2', DEFAULT_DATABASE = |3, CHECK_POLICY = OFF", Username, Username, dbName, ...)
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void Utente.performSqlServerUserCreation()
{
  boolean inUnitTestApp = find(lower(QappCore.mainCaption), "test", ...) > 0
   
  string dropUserScript = this.getSqlServerDropUserScript()
  string dropLoginScript = this.getSqlServerDropLoginScript()
  string createLoginScript = this.getSqlServerCreateLoginScript()
  string createUserScript = this.getSqlServerCreateUserScript()
  string grantSelectScriptTabParametri = this.GetSqlServerGrantAccessToTabParametriScript()
  string grantSelectScriptSW9DB = this.GetSqlServerGrantAccessToSW9DBScript()
  if (inUnitTestApp)
  {
    QappCore.DTTLogMessage("in Unit Test app, executing DROP USER SCRIPT: " + dropUserScript, ..., DTTInfo)
    QappCore.DTTLogMessage("in Unit Test app, executing DROP LOGIN SCRIPT: " + dropLoginScript, ..., DTTInfo)
    QappCore.DTTLogMessage("in Unit Test app, executing CREATE LOGIN SCRIPT: " + createLoginScript, ..., DTTInfo)
    QappCore.DTTLogMessage("in Unit Test app, executing CREATE USER SCRIPT: " + createUserScript, ..., DTTInfo)
    QappCore.DTTLogMessage("in Unit Test app, executing GRANT SELECT TP1 SCRIPT: " + grantSelectScriptTabParametri, ..., DTTInfo)
    QappCore.DTTLogMessage("in Unit Test app, executing GRANT SELECT SW9 SCRIPT: " + grantSelectScriptSW9DB, ..., DTTInfo)
     
    SqlServerCreationScriptsHaveBeenExecuted = true
  }
  else 
  {
    try 
    {
      QappCore.DTTLogMessage(formatMessage("Executing: |1", dropUserScript, ...), ..., DTTInfo)
      QualibusDB.SQLExecute(dropUserScript)
      QappCore.DTTLogMessage(formatMessage("Executing: |1", dropLoginScript, ...), ..., DTTInfo)
      QualibusDB.SQLExecute(dropLoginScript)
      QappCore.DTTLogMessage(formatMessage("Executing: |1", createLoginScript, ...), ..., DTTInfo)
      QualibusDB.SQLExecute(createLoginScript)
      QappCore.DTTLogMessage(formatMessage("Executing: |1", createUserScript, ...), ..., DTTInfo)
      QualibusDB.SQLExecute(createUserScript)
      QappCore.DTTLogMessage(formatMessage("Executing: |1", grantSelectScriptTabParametri, ...), ..., DTTInfo)
      QualibusDB.SQLExecute(grantSelectScriptTabParametri)
      QappCore.DTTLogMessage(formatMessage("Executing: |1", grantSelectScriptSW9DB, ...), ..., DTTInfo)
      QualibusDB.SQLExecute(grantSelectScriptSW9DB)
       
      SqlServerCreationScriptsHaveBeenExecuted = true
    }
    catch 
    {
      SqlServerCreationScriptsHaveBeenExecuted = false
      QappCore.DTTLogMessage("Error occured in sql server creation scripts", ..., DTTError)
      QappCore.DTTLogMessage(errorMessage(), ..., DTTError)
    }
  }
}


// ──────────────────────────────────

// ******************************************************************************
// returns true if sql server creation scipts have been run, useful in unit tests
// 
// ******************************************************************************
public boolean Utente.haveSqlServerCreationScriptsBeenExecuted()
{
  boolean returnedValue = SqlServerCreationScriptsHaveBeenExecuted
   
  // reset to false so in subsequent calls (without reloading the document) it is possible to check if scripts have been run again or not (this method is used in tests so it makes sense)
  SqlServerCreationScriptsHaveBeenExecuted = false
   
  return returnedValue
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string Utente.getSqlServerCreateUserScript()
{
  return formatMessage("CREATE USER [|1] FOR LOGIN [|2]", Username, Username, ...)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string Utente.GetSqlServerGrantAccessToSW9DBScript()
{
  return formatMessage("GRANT SELECT ON SW9_DB TO [|1]", Username, ...)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string Utente.getSqlServerResetPasswordScript()
{
   
  if (this.isLdapUser())
  {
    QappCore.DTTLogMessage("User is LDAP/Windows user, password reset cannot be executed", ..., DTTWarning)
    return ""
  }
  else 
  {
    string dbName = Tools.getDatabaseName(QualibusDB.me())
    return formatMessage("ALTER LOGIN [|1] WITH PASSWORD = '|2', DEFAULT_DATABASE = |3, CHECK_POLICY = OFF", Username, lower(Username), dbName, ...)
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean Utente.performSqlServerResetUserPassword(
  optional boolean avoidModifyingDatabase = 0 // parameter to bypass password reset in unit tests
)
{
  boolean passwordChangedSuccuessfully = false
   
  string alterUserPasswordScript = this.getSqlServerResetPasswordScript()
  try 
  {
     
    // when testing we avoid running the alter password script...
    if (!(avoidModifyingDatabase))
    {
      QualibusDB.SQLExecute(alterUserPasswordScript)
    }
     
    // when resetting password we want to force user to change it at the next login
    FORCECHANGEPASSWORD = Yes
     
    // ... and to forcefully save the user to db to store the new value of FORCECHANGEPASSWORD
    if (!(avoidModifyingDatabase))
    {
      this.saveToDB(999, false)
    }
    passwordChangedSuccuessfully = true
  }
  catch 
  {
    QappCore.DTTLogMessage("Error occured in sql server alter login password script", ..., DTTError)
  }
  return passwordChangedSuccuessfully
}


// ──────────────────────────────────

// *****************************************************************************************************************
// DROP LOGIN SCRIPT, already wrapped with TRY CATCH because in case of non existing user we do not deal with errors
// *****************************************************************************************************************
public string Utente.getSqlServerDropLoginScript()
{
  return formatMessage("BEGIN TRY DROP LOGIN [|1] END TRY BEGIN CATCH END CATCH", Username, ...)
}


// ──────────────────────────────────

// *************************************
// true if username contains a backslash
// *************************************
public boolean Utente.isLdapUser()
{
  boolean usernameContainsSlash = find(Username, "\\", ...) > 0
  return usernameContainsSlash
}


// ──────────────────────────────────



// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Utente.loadOpzioniGenerali()
{
  OpzioniGeneraliUtente = new()
  OpzioniGeneraliUtente.IDUTENTE = IDUTENTE
  try 
  {
    OpzioniGeneraliUtente.loadFromDB(...)
  }
  catch 
  {
    QappCore.DTTLogMessage("imposssible to load OpzioniGeneraliUtente from DB", ..., DTTWarning)
    OpzioniGeneraliUtente = null
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string Utente.GetSqlServerGrantAccessToTabParametriScript()
{
  return formatMessage("GRANT SELECT ON TAB_PARAMETRI_1 TO [|1]", Username, ...)
}


// ──────────────────────────────────



// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void Utente.deleteAllCalendarFilters()
{
  if (!(CalendarFilters.loaded))
  {
    this.loadCollectionFromDB(CalendarFilters, ...)
  }
  for each CalendarFilters cf in CalendarFilters
  {
    cf.deleted = true
    cf.saveToDB(...)
  }
}


// ──────────────────────────────────



// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private boolean Utente.UsernameIsValid()
{
  boolean result = false
  boolean windowsUser = this.isLdapUser()
   
  if (!(windowsUser))
  {
    string regex = "^[a-zA-Z][a-zA-Z0-9_-]*(?:\\.[a-zA-Z][a-zA-Z0-9_-]*)?$"
    boolean regExMatches = SH.RegExMatches(Username, regex)
    result = regExMatches
  }
  else 
  {
    result = true
  }
  return result
   
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean Utente.isDeletable()
{
  boolean userIsDeletable = false
   
  DevTools.ToBeReviewed("this huge query approach is effective but quite difficult to read and maintain, hopefully in the future we can have a class based approach, but for now his oes the job")
  string queryString = "select sum(tmp.cts) from( select count(*) cts from NGT_USER_SMTP where ID_UTENTE = |1 union all select count(*) cts from CDATA_HIST_LIST WHERE ID_UTENTE =|1  union all select count(*) 
           cts from CDATA_SECTIONS_PERMESSI WHERE ID_UTENTE =|1  union all select count(*) cts from CES_ANAGRAFICA WHERE ID_REFERENTE =|1  union all select count(*) cts from CES_PERMESSI WHERE ID_UTENTE =|1  
           union all select count(*) cts from CRU_PERMESSI WHERE ID_UTENTE =|1  union all select count(*) cts from CRU_RECIPIENTS WHERE ID_USER_TO_SHOW_DATA =|1  union all select count(*) cts from DIS_PROMEMORI­
           A WHERE ID_UTENTE_CHIUSURA =|1 union all select count(*) cts from DIS_PROMEMORIA WHERE ID_UTENTE_INS =|1 union all select count(*) cts from DIS_PROMEMORIA WHERE ID_UTENTE_ULT_MOD =|1  union all selec­
           t count(*) cts from DOC_ALTRI_FILE WHERE ID_CREATORE =|1  union all select count(*) cts from DOC_ALTRI_FILE WHERE ID_UTENTE_ESTR =|1  union all select count(*) cts from DOC_ALTRI_FILE WHERE 
           ID_UTENTE_ULT_MOD =|1  union all select count(*) cts from DOC_DISTRIBUZIONE WHERE ID_DISTRIBUTORE =|1  union all select count(*) cts from DOC_DOCUMENTI WHERE ID_UTENTE_INS =|1  union all select 
           count(*) cts from DOC_DOCUMENTI WHERE ID_UTENTE_OBS =|1  union all select count(*) cts from DOC_EXTERNAL WHERE ID_CREATORE =|1  union all select count(*) cts from DOC_EXTERNAL WHERE ID_UTENTE_ULT_MOD 
           =|1  union all select count(*) cts from DOC_PERMESSI WHERE ID_UTENTE =|1  union all select count(*) cts from DOC_REVISIONI WHERE ID_APPROVATORE =|1  union all select count(*) cts from DOC_REVISIONI 
           WHERE ID_REDATTORE =|1  union all select count(*) cts from DOC_REVISIONI WHERE ID_UTENTE_ESTR =|1  union all select count(*) cts from DOC_REVISIONI WHERE ID_UTENTE_FILE_ALT =|1  union all select 
           count(*) cts from DOC_REVISIONI WHERE ID_UTENTE_ULT_MOD =|1  union all select count(*) cts from DOC_REVISIONI WHERE ID_VERIFICATORE =|1  union all select count(*) cts from DOC_SPREADSHEETS WHERE 
           ID_CREATORE =|1  union all select count(*) cts from DOC_SPREADSHEETS WHERE ID_UTENTE_ULT_MOD =|1  union all select count(*) cts from DOC_VERIFICHE WHERE ID_VERIFICATORE =|1  union all select count(*) 
           cts from EVA_ATTIVITA WHERE ID_RESP_EVENTO =|1  union all select count(*) cts from EVA_ATTIVITA WHERE ID_UTENTE_CHIUSURA =|1  union all select count(*) cts from EVA_ATTIVITA WHERE ID_UTENTE_INS =|1  
           union all select count(*) cts from EVA_ATTIVITA WHERE ID_UTENTE_ULT_MOD =|1  union all select count(*) cts from EVA_COSTI WHERE ID_UTENTE_INS =|1  union all select count(*) cts from EVA_COSTI WHERE 
           ID_UTENTE_ULT_MOD =|1  union all select count(*) cts from EVA_RIGHE_EVENTO WHERE ID_UTENTE_INS =|1  union all select count(*) cts from EVA_RIGHE_EVENTO WHERE ID_UTENTE_ULT_MOD =|1  union all select 
           count(*) cts from EVA_RUOLI_AMBITO WHERE ID_UTENTE =|1  union all select count(*) cts from EVA_RUOLI_EVENTO WHERE ID_UTENTE =|1  union all select count(*) cts from EVA_TESTATA_EVENTO WHERE 
           ID_UTENTE_CHIUS =|1  union all select count(*) cts from EVA_TESTATA_EVENTO WHERE ID_UTENTE_INS =|1  union all select count(*) cts from EVA_TESTATA_EVENTO WHERE ID_UTENTE_RESP =|1  union all select 
           count(*) cts from EVA_TIPOLOGIE_EVENTO WHERE ID_RESPONSABILE =|1  union all select count(*) cts from GCF_TIPI_RUOLI WHERE ID_UTENTE =|1  union all select count(*) cts from MAN_COSTI WHERE ID_UTENTE_I­
           NS =|1  union all select count(*) cts from MAN_COSTI WHERE ID_UTENTE_ULT_MOD =|1  union all select count(*) cts from MAN_PRG_OPERAZIONI WHERE ID_RESPONSABILE =|1  union all select count(*) cts from 
           MAN_PRG_OPERAZIONI WHERE ID_UTENTE_INS =|1  union all select count(*) cts from MAN_PRG_OPERAZIONI WHERE ID_UTENTE_ULT_MOD =|1  union all select count(*) cts from MAN_TESTATA_OPERAZIONI WHERE 
           ID_UTENTE_CHIUSURA =|1  union all select count(*) cts from MAN_TESTATA_OPERAZIONI WHERE ID_UTENTE_INS =|1  union all select count(*) cts from MAN_TESTATA_OPERAZIONI WHERE ID_UTENTE_RESPONSABILE =|1  
           union all select count(*) cts from MAN_UM_REGISTRAZIONI WHERE ID_UTENTE_INS =|1  union all select count(*) cts from MAN_UM_REGISTRAZIONI WHERE ID_UTENTE_ULT_MOD =|1  union all select count(*) cts fro­
           m MEM_RESOURCES WHERE ID_UTENTE =|1  union all select count(*) cts from MSG_DESTINATARI WHERE ID_UTENTE =|1  union all select count(*) cts from MSG_DISC_NOTIFICATION WHERE ID_UTENTE =|1  union all 
           select count(*) cts from MSG_DISC_NOTIFICATION WHERE ID_UTENTE_SENT =|1  union all select count(*) cts from MSG_DISCUSSION WHERE ID_UTENTE =|1  union all select count(*) cts from MSG_MESSAGGI WHERE 
           ID_UTENTE =|1  union all select count(*) cts from MSG_POLL_DATA WHERE ID_UTENTE =|1  union all select count(*) cts from NGT_FAVOURITE_FILTER WHERE ID_UTENTE =|1  union all select count(*) cts from 
           NGT_FILTER WHERE ID_UTENTE =|1  union all select count(*) cts from NGT_GRIDSETTING WHERE ID_UTENTE =|1  union all select count(*) cts from NGT_LINKS WHERE ID_UTENTE =|1  union all select count(*) cts 
           from NGT_LOCKS WHERE ID_UTENTE =|1  union all select count(*) cts from NGT_TOKENS WHERE ID_UTENTE =|1  union all select count(*) cts from PER_ANAGRAFICA WHERE ID_UTENTE =|1  union all select count(*) 
           cts from PER_TIPI_RUOLI WHERE ID_UTENTE =|1  union all select count(*) cts from PRG_ATTIVITA WHERE ID_UTENTE_INS =|1  union all select count(*) cts from PRG_ATTIVITA WHERE ID_UTENTE_ULT_MOD =|1  unio­
           n all select count(*) cts from PRG_COSTI WHERE ID_UTENTE_INS =|1  union all select count(*) cts from PRG_COSTI WHERE ID_UTENTE_ULT_MOD =|1  union all select count(*) cts from PRG_PROGETTI WHERE 
           ID_RESPONSABILE =|1  union all select count(*) cts from PRG_RICAVI WHERE ID_UTENTE_INS =|1  union all select count(*) cts from PRG_RICAVI WHERE ID_UTENTE_ULT_MOD =|1  union all select count(*) cts 
           from PRO_SHARED_EXTERNAL WHERE ID_UTENTE =|1  union all select count(*) cts from QRY_FILTRI WHERE ID_UTENTE =|1  union all select count(*) cts from QRY_PERMESSI WHERE ID_UTENTE =|1  union all select 
           count(*) cts from REFCDATA_HIST_LIST WHERE ID_UTENTE =|1  union all select count(*) cts from SSI_INDICATORI WHERE ID_RESP_RILIEVO =|1  union all select count(*) cts from SSI_REGISTRAZIONI WHERE 
           ID_UTENTE_INS =|1  union all select count(*) cts from SSI_TRAGUARDI WHERE ID_UTENTE_INS =|1  union all select count(*) cts from SSI_TRAGUARDI WHERE ID_UTENTE_ULT_MOD =|1  union all select count(*) ct­
           s from SSI_TRAGUARDI WHERE ID_VALUTATORE =|1  union all select count(*) cts from SW9_DATA_LOG WHERE ID_UTENTE =|1  union all select count(*) cts from TAB_PARAMETRI_1 WHERE ID_UTENTE_ANONYMOUS =|1  
           union all select count(*) cts from TAB_PARAMETRI_1 WHERE ID_UTENTE_BACHECA =|1  union all select count(*) cts from VFO_TESTATA_VAL WHERE ID_UTENTE_CHIUSURA =|1  union all select count(*) cts from 
           VFO_TESTATA_VAL WHERE ID_UTENTE_INS =|1  union all select count(*) cts from VFO_TIPOLOGIE WHERE ID_RESPONSABILE =|1)tmp"
   
   
   
   
  string formattedQueryText = formatMessage(queryString, IDUTENTE, ...)
   
  Recordset r = null
  try 
  {
     
    // in case any error occurs in the query (schema changed in time: with an hardcoded query errors can occur)
    r = QualibusDB.SQLQuery(formattedQueryText)
    r.moveFirst()
    int countOfIdUtenteFK = toInteger(r.getFieldValueIdx(1))
    userIsDeletable = countOfIdUtenteFK == 0
  }
  catch 
  {
    userIsDeletable = false
  }
   
   
   
  return userIsDeletable
   
   
}


// ──────────────────────────────────

// **************************************************************************************
// password should be changed: only for non ldap user and if force change password is Yes
// **************************************************************************************
public boolean Utente.shouldPasswordBeChanged()
{
  boolean isLdapUser = this.isLdapUser()
  boolean forceChangePassword = FORCECHANGEPASSWORD == Yes
  boolean shouldPassowrdBeChanged = !(isLdapUser) and forceChangePassword
  return shouldPassowrdBeChanged
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static IDCollection Utente.getCollectionOfAllUtentiWithIDUtenteOnly()
{
  IDCollection allUtenti of Utente = new()
   
  for each row (readonly)
  {
    select
      IDUTENTE = IDUTENTE
    from 
      Utenti // master table
     
    Utente u = new()
    u.IDUTENTE = IDUTENTE
     
    allUtenti.add(u)
  }
   
  return allUtenti
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public IDCollection Utente.getPersonaleUtenteCanSee(
  optional string queryString = "%"                                // 
  optional string:personaleLoadingModes loadingMode = "lookupMode" // 
)
{
  string searchPattern = "%" + queryString + "%"
   
  boolean isPers1InPersonale = this.hasSpecificPrivilege(Personale, Pers1)
   
   
  IDCollection visiblePersonale of Personale = new()
  for each row (readonly)
  {
    select
      recIdDipendente = IDDIPENDENTE
      recNOME = NOME
      recCOGNOME = COGNOME
    from 
      Personale // master table
    where
      (NOME like searchPattern) or (COGNOME like searchPattern)
      STATO != Licenziato/Chiuso
      isPers1InPersonale == true or exists(subquery)
         select top 1 // 
           IDTIPOANAGR
         from 
           TipoPersonaleRoles // master table
         where
           TipoPersonaleRoles.IDUTENTE == IDUTENTE
           TipoPersonaleRoles.VISUALIZZA == Yes
           TipoPersonaleRoles.IDTIPOANAGR == Personale.IDTIPOANAGR
     
    Personale p = null
     
    if (loadingMode == lookupMode)
    {
      p = new()
      p.IDDIPENDENTE = recIdDipendente
      p.NOME = recNOME
      p.COGNOME = recCOGNOME
      p.computeFullName()
    }
    else 
    {
      p = Personale.getFromDB(recIdDipendente, ...)
    }
     
    visiblePersonale.add(p)
  }
   
   
   
  return visiblePersonale
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public IDCollection Utente.getFunzioniUtenteCanSee(
  optional string queryString = "%" // 
)
{
  string searchPattern = "%" + queryString + "%"
   
  IDCollection visibleFunzioni of Funzione = new()
  select into collection (visibleFunzioni)
  from 
    Funzione // master table
  where
    (DESCRFUNZIONE like searchPattern) or (CODFUNZIONE like searchPattern)
    ATTIVA == Yes
   
  // in this moment this query returns all and does not consider privileges of Utente
   
   
  return visibleFunzioni
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public IDCollection Utente.getUsersUtenteCanSee(
  optional string queryString = "%" // 
)
{
  string searchPattern = "%" + queryString + "%"
   
  IDCollection visibleUesrs of Utente = new()
  select into collection (visibleUesrs)
  from 
    Utente // master table
  where
    (Username like searchPattern) or (DESCRUTENTE like searchPattern)
    ATTIVO == Yes
   
  // in this moment this query returns all and does not consider privileges of Utente
   
   
  return visibleUesrs
}


// ──────────────────────────────────

// ****************************************************
// search string allows to search in
// titolo
// descr modello
// tipologia
// ambito
// 
// (this is a method made to simplify UI implementation
// 
// ****************************************************
public IDCollection Utente.getModelliEventiBySearchString(
  string searchString // 
)
{
   
  // NOTE: we pass Immissione and not Visibility (that sounds more logic) because in C/S immissione is in fact used to filter the classes list
  IDCollection retrievedModelliEvento of ModelloEvento = ModelloEvento.getSearchedModelliBasedOnRoles(Immissione, this, searchString)
   
  return retrievedModelliEvento
}


// ──────────────────────────────────

// *************************************************************************
// In this particular method mainmodule can either be Event or Modello Event
// this method consider ambito roles and ambito/classe links 
// *************************************************************************
private Recordset Utente.getVisibleAmbiti(
  MainModule mainModule             // 
  optional string searchString = "" // 
)
{
   
   
  Recordset visibleAmbitiAsRecordset = null
  if (!((Evento.isMyInstance(mainModule) or ModelloEvento.isMyInstance(mainModule))))
  {
    return visibleAmbitiAsRecordset
  }
  string queryString = nullValue(searchString, "")
  if (queryString == "*")
    queryString = ""
   
  int idClasse = mainModule.getTypeID()
   
  visibleAmbitiAsRecordset = AmbitoEvento.getVisibleAmbitiRecordset(idClasse, IDUTENTE, queryString)
  return visibleAmbitiAsRecordset
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public Recordset Utente.getVisibileAmbitiAsRecordset(
  MainModule mainModule             // 
  optional string searchString = "" // 
)
{
  Recordset visibleAmbiti = this.getVisibleAmbiti(mainModule, searchString)
   
  return visibleAmbiti
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public IDCollection Utente.getVisibileAmbitiAsCollection(
  MainModule mainModule             // 
  optional string searchString = "" // 
)
{
  IDCollection visibleAmbitiCollection of AmbitoEvento = new()
  Recordset visibleAmbiti = this.getVisibleAmbiti(mainModule, searchString)
   
  visibleAmbiti.moveFirst()
  while (!(visibleAmbiti.EOF()))
  {
    int idAmbito = toInteger(visibleAmbiti.getFieldValue("IDAMBITO"))
    AmbitoEvento ambito = AmbitoEvento.GetAmbito(idAmbito)
    visibleAmbitiCollection.add(ambito)
     
    visibleAmbiti.moveNext()
  }
  return visibleAmbitiCollection
}


// ──────────────────────────────────

// **********************************************************
// Checks if the user has Pers1 Privilege in the current QApp
// **********************************************************
public boolean Utente.hasPers1OnCurrentQapp()
{
  return PrivilegeForCurrentQapp.PRIVPERS1 == Yes
}


// ──────────────────────────────────

// **********************************************************
// Checks if the user has Pers2 Privilege in the current QApp
// **********************************************************
public boolean Utente.hasPers2OnCurrentQapp()
{
  return PrivilegeForCurrentQapp.PRIVPERS2 == Yes
}


// ──────────────────────────────────

// **********************************************************
// Checks if the user has Pers3 Privilege in the current QApp
// **********************************************************
public boolean Utente.hasPers3OnCurrentQapp()
{
  return PrivilegeForCurrentQapp.PRIVPERS3 == Yes
}


// ──────────────────────────────────

// **********************************************************
// Checks if the user has Pers4 Privilege in the current QApp
// **********************************************************
public boolean Utente.hasPers4OnCurrentQapp()
{
  return PrivilegeForCurrentQapp.PRIVPERS4 == Yes
}


// ──────────────────────────────────

// **********************************************************
// Checks if the user has Pers5 Privilege in the current QApp
// **********************************************************
public boolean Utente.hasPers5OnCurrentQapp()
{
  return PrivilegeForCurrentQapp.PRIVPERS5 == Yes
}


// ──────────────────────────────────

// **********************************************************
// Checks if the user has DatiP Privilege in the current QApp
// **********************************************************
public boolean Utente.hasDatiPersOnCurrentQapp()
{
  return PrivilegeForCurrentQapp.PRIVDATIP == Yes
}


// ──────────────────────────────────

// *****************************************************************
// Loads in the instance variable the privilege for the current QApp
// *****************************************************************
public void Utente.loadPrivilegeForCurrentQapp(
  optional inout boolean qAppNotInserted = 0 // refer to onlogin for this
)
{
  if (PrivilegeForCurrentQapp.loaded)
  {
     
    // since we call loadPrivilegeForCurrentQapp more times we return immediately if already called (this is a small optimization)
    DevTools.ToBeReviewed("in fact when starting a QappCore session with ForwardCommand it is the only case in which we do not load privilege twice because handleCustompostLoginCOdeIfNeeded calls it, while in 
          doLogin or processsLoginCommand we call the method plus handleCustomPostLoginCodeIfNeeeded calls it")
    QappCore.DTTLogMessage("no need to reload since method already called", ..., DTTInfo)
    return 
  }
   
  PrivilegeForCurrentQapp.IDUTENTE = IDUTENTE
   
  int idApp = findQAppID()
   
   
  PrivilegeForCurrentQapp.IDAPPLICAZIONE = idApp
   
  qAppNotInserted = (idApp == 0)
   
  if (qAppNotInserted)
  {
    QappCore.DTTLogMessage("not loading privilege since QApp not yet inserted", ..., DTTInfo)
  }
  else 
  {
    try 
    {
      PrivilegeForCurrentQapp.loadFromDB(0)
    }
    catch 
    {
      QappCore.DTTLogMessage("cannot load privilege", ..., DTTError)
    }
  }
   
}


// ──────────────────────────────────

// ***********************************************
// Checks if the user can Execute the current QApp
// ***********************************************
public boolean Utente.canExecuteCurrentQapp()
{
  return PrivilegeForCurrentQapp.PRIVEXECUTE == Yes
}


// ──────────────────────────────────

// *************************************************
// Checks if the user can Insert in the current QApp
// *************************************************
public boolean Utente.canInsertCurrentQapp()
{
  return PrivilegeForCurrentQapp.PRIVINSERT == Yes
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean Utente.hasSpecificPrivilege(
  int:kordapp kordApp             // 
  string:privilegeTypes privilege // 
)
{
  boolean specificPrivilege = false
   
  NGTAPPLICAZIONI ngtapplicazioni = NGTAPPLICAZIONI.getFromKordApp(kordApp)
   
  specificPrivilege = this.hasSpecificPrivilegeForApplicazione(ngtapplicazioni, privilege)
   
  return specificPrivilege
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean Utente.hasSpecificPrivilegeForApplicazione(
  NGTAPPLICAZIONI applicazione    // 
  string:privilegeTypes privilege // 
)
{
  boolean specificPrivilege = this.HasSpecificPrivilegeForIDApplicazione(applicazione.IDAPPLICAZIONE, privilege)
   
   
  return specificPrivilege
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean Utente.HasSpecificPrivilegeForIDApplicazione(
  int idApplicazione              // 
  string:privilegeTypes privilege // 
)
{
  boolean specificPrivilege = false
   
   
  Privilegio p = new()
  p.IDUTENTE = IDUTENTE
  p.IDAPPLICAZIONE = idApplicazione
   
  try 
  {
    p.loadFromDB(...)
  }
  catch 
  {
    QappCore.DTTLogMessage(formatMessage("utente.hasSpecificPrivilege is not able to load a privilege for ID_UTENTE '|1' and ID_APPLICAZIONE '|2'", p.IDUTENTE, p.IDAPPLICAZIONE, ...), ..., DTTWarning)
     
    return false
  }
   
  switch (privilege)
  {
    case Read:
      specificPrivilege = p.PRIVREAD == Yes
    break
    case Execute:
      specificPrivilege = p.PRIVEXECUTE == Yes
    break
    case Insert:
      specificPrivilege = p.PRIVINSERT == Yes
    break
    case Update:
      specificPrivilege = p.PRIVUPDATE == Yes
    break
    case Delete:
      specificPrivilege = p.PRIVDELETE == Yes
    break
    case Print:
      specificPrivilege = p.PRIVPRINT == Yes
    break
    case Export:
      specificPrivilege = p.PRIVEXPORT == Yes
    break
    case Email:
      specificPrivilege = p.PRIVEMAIL == Yes
    break
    case Pers1:
      specificPrivilege = p.PRIVPERS1 == Yes
    break
    case Pers2:
      specificPrivilege = p.PRIVPERS2 == Yes
    break
    case Pers3:
      specificPrivilege = p.PRIVPERS3 == Yes
    break
    case Pers4:
      specificPrivilege = p.PRIVPERS4 == Yes
    break
    case Pers5:
      specificPrivilege = p.PRIVPERS5 == Yes
    break
    case DatiP:
      specificPrivilege = p.PRIVDATIP == Yes
    break
  }
   
  return specificPrivilege
}


// ──────────────────────────────────

// ****************************************************************************
// wrapper of hasSpecificPrivilegeForApplicazione so it can be used for kordapp
// ****************************************************************************
public boolean Utente.hasSpecificPrivilegeForKordApp(
  int:kordapp kordApp             // 
  string:privilegeTypes privilege // 
)
{
  NGTAPPLICAZIONI ngtapplicazioni = NGTAPPLICAZIONI.getFromKordApp(kordApp)
   
  boolean specificPrivilege = this.hasSpecificPrivilegeForApplicazione(ngtapplicazioni, privilege)
   
  return specificPrivilege
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean Utente.isAdministrator()
{
  boolean userIsAdminiatratore = TIPOUTENTE == Administrator
  return userIsAdminiatratore
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean Utente.isPasswordCorrect()
{
  if (length(Password) == 0)
    QappCore.DTTLogMessage("Impossible to check an empty password, set it before calling isPasswordCorrect", ..., DTTError)
   
  boolean validPassword = checkCredentials(Username, Password, ...)
   
  return validPassword
}


// ──────────────────────────────────

// ***********************************************************************************************
// the values of the properties Password and NewPassword are used to change the password in the DB
// all properties should be set before calling the method
// 
// the error Message param is an inout parameter to hold the error message
// ***********************************************************************************************
public boolean Utente.SetNewPasswordInDatabase(
  inout string errorMessage // 
)
{
  if (NewPassword != ConfirmNewPassword)
  {
    QappCore.DTTLogMessage("New Password and Confirm New Password do not match", ..., DTTError)
    return false
  }
   
  boolean newPasswordIsComplexEnough = this.newPasswordSatisfiesComplexiyCriteria()
  if (!(newPasswordIsComplexEnough))
  {
    errorMessage = "La nuova password non soddisfa i criteri di complessità"
    return false
  }
   
  boolean success = true
  string changePasswordScript = formatMessage("EXEC sp_password '|1', '|2', '|3'", Password, NewPassword, Username, ...)
  try 
  {
    QappCore.DTTLogMessage(formatMessage("Executing command: "|1", changePasswordScript, ...), ..., DTTInfo)
    QualibusDB.SQLExecute(changePasswordScript)
  }
  catch 
  {
    success = false
  }
   
  if (success)
  {
    FORCECHANGEPASSWORD = No
    PASSWORDCHANGEDATETIME = now()
    this.skipValidation()
    this.saveToDB(...)
  }
   
  return success
}


// ──────────────────────────────────

// ************************************************************************************
// returns true if the password is complex enough given the settings in Opzionigenerali
// ************************************************************************************
private boolean Utente.newPasswordSatisfiesComplexiyCriteria()
{
  boolean passwordIsComplexEnough = false
   
  string applyPasswordCriteria = ""
  int minimumPasswordLength = 0
  select into variables (found variable)
    set applyPasswordCriteria = LUNPWDOBBL
    set minimumPasswordLength = LUNPWD
  from 
    TABPARAMETRI1 // master table
   
  if (applyPasswordCriteria == Yes)
  {
    boolean oneDigit = SH.RegExMatches(NewPassword, atLeastOneDigit)
    boolean oneLowecase = SH.RegExMatches(NewPassword, atLeastLowercase)
    boolean oneUppercase = SH.RegExMatches(NewPassword, atLeastUppercase)
    boolean lengthIsOk = length(NewPassword) >= minimumPasswordLength
     
    if (!(oneDigit and oneLowecase and oneUppercase and lengthIsOk))
      passwordIsComplexEnough = false
    else 
      passwordIsComplexEnough = true
  }
  else 
    passwordIsComplexEnough = true
   
  return passwordIsComplexEnough
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static Recordset Utente.getOnSmartLookupRecordset(
  IDDocument callerDocument // 
  string queryString        // The recordset to be filled if the queries run during the Smart Lookup event are customized.
)
{
  Recordset computedRecordset = new()
  boolean runCommonCode = false
   
   
  if (Evento.isMyInstance(callerDocument))
  {
    Evento e = (Evento)callerDocument
    int idAmbito = e.IDAMBITO
     
    TabParametri tp = TabParametri.getInstance()
    boolean considerScopeBasedVisibility = tp.ENABLESCOPEBASEDUSERVISIBILITY == Yes
     
     
    // we show function user only when when responsible is funzione AND ambito based visibility NOT considered
    // case of responsabile funzione...
    if (e.IdFunzioneResponsible > 0 and !(considerScopeBasedVisibility))
    {
       
      Recordset funzioniMembers = new()
      select into recordset (funzioniMembers)
         VUTENTIDIPENDENTI.IDUTENTE as IDUTENTE
         VUTENTIDIPENDENTI.DESCRUTENTE as DESCRUTENTE
      from 
         VUTENTIDIPENDENTI // master table
         Funzioni          // joined with Funzione Members using key FK_MSQ_PERS_FUNZIONI01
         FunzioneMembers   // manually joined, see where clauses
      where
         VUTENTIDIPENDENTI.ATTIVO == Yes
         Funzioni.IDFUNZIONE == e.IdFunzioneResponsible
         FunzioneMembers.IDDIPENDENTE == VUTENTIDIPENDENTI.IDDIPENDENTE
         VUTENTIDIPENDENTI.DESCRUTENTE like "%" + queryString + "%"
      order by
         VUTENTIDIPENDENTI.DESCRUTENTE
       
       
       
      computedRecordset.copyFrom(funzioniMembers)
    }
    else 
    {
      // case1 - it will return all the users can see the passed Ambtio if ScopeBasedVisibility = true
      // case2 - it will show all the active users if ScopeBasedVisibility = False
       
      computedRecordset = this.getActiveUtentiVisibleOnAmbitoAsRecordset(idAmbito, considerScopeBasedVisibility, ...)
       
    }
  }
  else if (Personale.isMyInstance(callerDocument))
  {
     
    Personale callerPersonale = cast(callerDocument)
     
    Recordset lookupRecordset = callerPersonale.getSmartLookupRecordsetForUtente(queryString)
     
    computedRecordset.copyFrom(lookupRecordset)
  }
  else if (ModelloEvento.isMyInstance(callerDocument))
  {
    ModelloEvento me = (ModelloEvento)callerDocument
     
    Recordset r = me.getSmartLookupForResponsabile(queryString)
    computedRecordset.copyFrom(r)
     
  }
  else 
  {
     
    runCommonCode = true
  }
  if (runCommonCode)
  {
    Recordset allActiveUsers = new()
    select into recordset (allActiveUsers)
      IDUTENTE as IDUTENTE
      DESCRUTENTE as DESCRUTENTE
    from 
      VUTENTIDIPENDENTI // master table
    where
      ATTIVO == Yes
      DESCRUTENTE like "%" + queryString + "%"
    order by
      DESCRUTENTE
     
    computedRecordset.copyFrom(allActiveUsers)
  }
  return computedRecordset
}


// ──────────────────────────────────

// ********************************************************************************************
// True when the utente has a row in EVA_RUOLI_AMBITO for the given ambito with VISUALIZZA=Yes.
// Mirrors the inner visibility check of sqlUtentiAttivi in the Delphi reference.
// ********************************************************************************************
public boolean Utente.canSeeAmbito(
  int idAmbito // 
)
{
  int count = 0
  select into variables (found variable)
    set count = count(...)
  from 
    EventoAmbitoRoles // master table
  where
    IDUTENTE == IDUTENTE
    IDAMBITO == idAmbito
    VISUALIZZA == Yes
   
  return count > 0
}


// ──────────────────────────────────

// **************************************************************************************
//  Returns all active utenti who have visibility on the given ambito, mirroring Delphi's
//  sqlUtentiAttivi. Used by the ambito-mode picker (cases 2, 3b/3c/3d, 6).
// 
// Parameters:
//   idAmbitoEvento         : the ambito for which we need visible utenti.
//   considerAmbitoBasedVisibilty : when Yes, list the users can see the given ambito
// 
// **************************************************************************************
public static IDCollection Utente.getActiveUtentiVisibleOnAmbitoAsCollection(
  int idAmbitoEvento                   // 
  boolean considerScopeBasedVisibility // 
  optional string querystring = ""     // 
)
{
  IDCollection result of Utente = new()
   
  if (!(considerScopeBasedVisibility))
  {
    for each row (readonly)
    {
      select
         IDUTENTE = IDUTENTE
      from 
         Utenti // master table
      where
         ATTIVO == Yes
         DESCRUTENTE like "%" + querystring + "%"
      order by
         DESCRUTENTE
       
      Utente u = Utente.get(IDUTENTE)
      if (u)
         result.add(u)
       
    }
  }
  else 
  {
    for each row (readonly)
    {
      select
         IDUTENTE = Utenti.IDUTENTE
      from 
         Utenti            // master table
         EventoAmbitoRoles // joined with Utenti using key FK_EVA_RUOLI_AMBITO_NGT_UTENTI
      where
         Utenti.ATTIVO == Yes
         Utenti.DESCRUTENTE like "%" + querystring + "%"
         EventoAmbitoRoles.VISUALIZZA == Yes
         EventoAmbitoRoles.IDAMBITO == idAmbitoEvento
      order by
         Utenti.DESCRUTENTE
       
      Utente u = this.get(IDUTENTE)
      if (u)
         result.add(u)
    }
  }
  return result
}


// ──────────────────────────────────

// **********************************************************************************
//  use "getActiveUtentiVisibleOnAmbitoAsCollection" and prepare recordset to return
// used in method "getOnSmartLookupRecordset" for eventi case
// 
// Parameters:
//   idAmbitoEvento         : the ambito for which we need visible utenti.
//   considerAmbitoBasedVisibilty : when Yes, list the users can see the given ambito
// 
// **********************************************************************************
public static Recordset Utente.getActiveUtentiVisibleOnAmbitoAsRecordset(
  int idAmbitoEvento                   // 
  boolean considerScopeBasedVisibility // 
  optional string querystring = ""     // 
)
{
  IDCollection utenti of Utente = this.getActiveUtentiVisibleOnAmbitoAsCollection(idAmbitoEvento, considerScopeBasedVisibility, querystring)
   
  Recordset r = new()
  RecordsetMetaData rmd = new()
  rmd.setColumnCount(2)
  rmd.setFieldName(1, "IDUTENTE")
  rmd.setFieldName(2, "DESCRUTENTE")
  rmd.setFieldType(1, Integer)
  rmd.setFieldType(2, Character)
  r.setMetaData(rmd)
   
  for each Utente u in utenti
  {
    Tools.addRowToRecordset(r, toString(u.IDUTENTE), u.DESCRUTENTE)
  }
   
  return r
   
}


// ──────────────────────────────────

// ***************************************
// Descrivi lo scopo di questa funzione...
// ***************************************
public int Utente.getMainID()
{
  return IDUTENTE
}


// ──────────────────────────────────

// ***************************************
// Descrivi lo scopo di questa funzione...
// ***************************************
public int Utente.getTypeID()
{
   
  // this is a bit odd but since we need a type and TIPOUTENTE is integer it works
  return TIPOUTENTE
}


// ──────────────────────────────────

// ***************************************
// Descrivi lo scopo di questa funzione...
// ***************************************
public int Utente.getKordApp()
{
  return Utenti
}


// ──────────────────────────────────

// ****************************************
// Describe the purpose of this function...
// ****************************************
public string Utente.getDescription()
{
  return DESCRUTENTE
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event Utente.OnInit()
{
  int nextId = Sequence.getNextSequence(NGTN_UTENTI, ...)
  IDUTENTE = nextId
  PROFID = 0
  CANLOGIN = Yes
  TEMPLATES = No
  CANLOGINTAGAPP = No
  LIGHTLOGIN = No
  SHAREDABSOLUTE = No
  ATTIVO = Yes
  PASSWORDCHANGEDATETIME = now()
}


// ──────────────────────────────────

// ************************************************************
// Event raised to the document during the validation procedure
// ************************************************************
event Utente.OnValidate(
  int:validateReasons Reason // An integer specifying the reason the document is being validated. The most common values are 0 to indicate complete validation before saving, or 1 to indicate a quick validati...
  inout boolean Error        // A boolean output parameter. If set to True, specifies an error condition and stops the validation immediately.
  inout boolean Skip         // A boolean output parameter. If set to True, does not check the rules for automatic validation of the document (see notes).
)
{
  boolean skipValidation = getTag("SkipValidation")
  if (skipValidation)
  {
    return 
  }
   
  if (deleted == false)
  {
    int otherUsersWithSameUserNameCount = 0
    select into variables (found variable)
      set otherUsersWithSameUserNameCount = count(...)
    from 
      Utenti // master table
    where
      IDUTENTE != IDUTENTE
      Username == Username
     
    if (otherUsersWithSameUserNameCount > 0)
    {
      Error = true
      this.setPropertyError("Il nome utente deve essere univoco.", Username)
    }
     
    boolean usernameIsValid = this.UsernameIsValid()
    if (!(usernameIsValid))
    {
      Error = true
      this.setPropertyError("Il nome utente non può contenere spazi, iniziare per numero o contenere caratteri non consentiti", Username)
    }
  }
}


// ──────────────────────────────────

// **************************************************************************
// Event called to customize the Smart Lookup procedure at the document level
// **************************************************************************
event Utente.OnGetSmartLookup(
  Recordset RecordSet       // The recordset to be filled if the queries run during the Smart Lookup event are customized.
  inout int Level           // The Smart Lookup procedure performs multiple attempts, increasingly widening the search field. This integer parameter specifies the number of attempts made.
  inout boolean NullValue   // A boolean output parameter. If set to True, then NULL (no results found) will be returned to the panel.
  inout boolean Skip        // A boolean output parameter. If set to True, then the standard queries for the current level will not be executed.
  inout boolean Cancel      // A boolean output parameter. If set to True, the Smart Lookup procedure will be stopped.
  IDDocument CallerDocument // The document for which the smart lookup procedure is launched. It can be null.
)
{
  // Canceling for Level > 0 means disabling the fact that smart lookup fires several times to
  // "smartly" find a better result sets using the 0-4 levels
  if (Level > 0)
  {
    Cancel = true
  }
  Skip = true
  string queryString = nullValue(DESCRUTENTE, "")
  if (queryString == "*")
    queryString = ""
   
  Recordset computedUtentiRecordset = this.getOnSmartLookupRecordset(CallerDocument, queryString)
   
  NullValue = computedUtentiRecordset.recordCount() == 0
  RecordSet.copyFrom(computedUtentiRecordset)
}


// ──────────────────────────────────

// **************************************************************************
// Event raised to the document when a panel requests execution of a decoding
// **************************************************************************
event Utente.BeforeLookup(
  inout boolean Skip   // A boolean output parameter. If set to True, the framework will not execute the lookup via a query.
  inout boolean Cancel // A boolean output parameter. If set to True, the decoding procedure ends immediately. The AfterLookup event will not be called.
)
{
   
  // basic protection: at the moment of writing this code is used only in insertion evento from web
  if (!(inserted))
  {
    return 
  }
   
  Skip = true
   
  string vDESCRUTENTE = ""
  select into variables (found variable)
    set vDESCRUTENTE = DESCRUTENTE
  from 
    Utenti // master table
  where
    IDUTENTE == IDUTENTE
   
  DESCRUTENTE = vDESCRUTENTE
}


// ──────────────────────────────────

// ************************************************************
// Event raised by the document after loading from the database
// ************************************************************
event Utente.AfterLoad(
  boolean AlreadyLoaded     // A boolean parameter that specifies whether the document was previously loaded.
  IDArray LoadedCollections // IDArray type parameter that contains the collections loaded by the framework. If the value of the Already Loaded parameter is False, the Loaded Collections parameter is NULL.
)
{
  this.updateAdditionalProperties()
  this.setOriginal()
}


// ──────────────────────────────────

// *************************************************************
// Event raised to the document at the end of the save procedure
// *************************************************************
event throws exception Utente.AfterSave(
  inout boolean Cancel // Boolean output parameter that makes it possible to cancel the entire save. It is equivalent to the Cancel parameter of the BeforeSave event.
)
{
  if (!(deleted))
  {
    // for inserted and updated object we run sql server user creation script
    if (this.sqlServerUserCreationIsNeeded())
    {
      this.performSqlServerUserCreation()
      if (!(SqlServerCreationScriptsHaveBeenExecuted))
      {
         DevTools.ToBeReviewed("we set a document error to inform the panel that a message must be displayed anyway the document will stay in error mode")
         this.addDocumentError("Errore fatale nella creazione utente, impossibile salvare, contattare un amministratore")
         Cancel = true
      }
    }
    BackgroundWorker.executeCommand(createMissingPrivilegi)
  }
   
  if (inserted)
  {
    this.initializeOpzioniGeneraliUtente()
    OpzioniGeneraliUtente.saveToDB(...)
     
    // create the initial calendar Filter
    CalendarFilters initialDefaultCalendarFilter = CalendarFilters.create(this)
    //  
    // BEWARE: since in create above we call setOriginal from here on the after save event will not affect anymore the utente being saved, for this reason any code who affects utente MUST put above, as for the
    // !deleted case!!!
     
    initialDefaultCalendarFilter.saveToDB(...)
  }
}


// ──────────────────────────────────

// **************************************************************************************
// Event raised to the document during the initial steps of the document saving procedure
// **************************************************************************************
event throws exception Utente.BeforeSave(
  inout boolean Skip         // A boolean output parameter. If set to True, tells the framework not to perform operations on the database, because they will be managed independently.
  inout boolean Cancel       // A boolean output parameter. If set to True, tells the framework to cancel the save procedure due to errors that occurred. Since the entire document is saved in the same transa...
  int:beforeSavePhases Phase // An integer from 0 to 3 that specifies the progress status of the save procedure as detailed at the beginning of the article.
)
{
  if (deleted)
  {
    if (!(OpzioniGeneraliUtente))
    {
      this.loadOpzioniGenerali()
    }
    OpzioniGeneraliUtente.deleted = true
    OpzioniGeneraliUtente.saveToDB(...)
    this.deleteAllCalendarFilters()
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static OpzioniGeneraliUtente OpzioniGeneraliUtente.create(
  Utente utente // 
)
{
  OpzioniGeneraliUtente ogu = new()
  ogu.init()
  ogu.IDUTENTE = utente.IDUTENTE
  return ogu
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event OpzioniGeneraliUtente.OnInit()
{
  MSGCHECKMINUTES = 5
  MSGNOTIFYNEW = Yes
  AUTOREFRESH = Yes
  AUTOREFRESHINTERVAL = 30
  MAXCLOSEDDISCUSSION = 50
  NOTIFYDISCUSSION = Yes
  DISCUSSIONDISPLAYTIME = 1000
  MENUAUTOSIZE = Yes
  MENUHEIGHT = 500
  SPELLCHECKACTIVE = Yes
  SPELLCHECKIGNOREEMAILS = Yes
  SPELLCHECKIGNOREMIXEDCASEWORDS = Yes
  SPELLCHECKIGNOREREPEATEDWORDS = No
  SPELLCHECKIGNOREUPPERCASEWORDS = Yes
  SPELLCHECKIGNOREWORDSWITHNUMBERS = Yes
  SPELLCHECKIGNOREURLS = Yes
}


// ──────────────────────────────────

// *************************************************
// Returns a string with the list of Pers privileges
// *************************************************
public string Privilegio.getPrivilegeDescription()
{
  string privilegeDescription = ""
  if (PRIVPERS1 == Yes)
    privilegeDescription = SH.Concat(privilegeDescription, decode(NeededPrivilegePers1, NeededPrivilegeTypes), ", ")
  if (PRIVPERS2 == Yes)
    privilegeDescription = SH.Concat(privilegeDescription, decode(NeededPrivilegePers2, NeededPrivilegeTypes), ", ")
  if (PRIVPERS3 == Yes)
    privilegeDescription = SH.Concat(privilegeDescription, decode(NeededPrivilegePers3, NeededPrivilegeTypes), ", ")
  if (PRIVPERS4 == Yes)
    privilegeDescription = SH.Concat(privilegeDescription, decode(NeededPrivilegePers4, NeededPrivilegeTypes), ", ")
   
  return privilegeDescription
}


// ──────────────────────────────────

// *****************************************************************************
// returns a collection of privilegi
// it works with any one param null, if both are set only one record is returned
// if non are set it returns all
// *****************************************************************************
public static IDCollection Privilegio.getCollectionOfPrivilegi(
  int idUtente       // 
  int idApplicazione // 
)
{
  boolean idUtenteIsNull = isNull(idUtente) or idUtente <= 0
  boolean idApplicazioneIsNull = isNull(idApplicazione) or idApplicazione <= 0
   
  IDCollection filteredCollection of Privilegio = new()
  select into collection (filteredCollection)
  from 
    Privilegio // master table
  where
    (!(idUtenteIsNull) and idApplicazioneIsNull and IDUTENTE == idUtente) or ((!(idApplicazioneIsNull) and idUtenteIsNull and IDAPPLICAZIONE == idApplicazione)) or (!(idUtenteIsNull) and !
       (idApplicazioneIsNull) and IDUTENTE == idUtente and IDAPPLICAZIONE == idApplicazione) or (idUtenteIsNull and idApplicazioneIsNull and 1 == 1)
   
  return filteredCollection
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static Privilegio Privilegio.create(
  int idUtente      // 
  int idApplication // 
)
{
  Privilegio p = new()
  p.init()
   
  p.IDUTENTE = idUtente
  p.IDAPPLICAZIONE = idApplication
   
  return p
}


// ──────────────────────────────────

// ******************************************************************************************************************************************************************
// This method creates all the privilegi (rows of NGT_PRIVILEGI) for every user, for all the application that are missing them, if not in unit test we also save them
// we retun the collection of all the created privilegi
// ******************************************************************************************************************************************************************
public static IDCollection Privilegio.createMissingPrivilegesAndGetResultsCollection(
  optional string:executionModes mode = "normalMode" // 
)
{
  IDCollection allCreatedPrivilegi of Privilegio = new()
//   
  for each row (readonly)
  {
    select
      IDUTENTE = Utenti.IDUTENTE
      IDAPPLICAZIONE = NGTAPPLICAZIONI.IDAPPLICAZIONE
    from 
      Utenti          // master table
      NGTAPPLICAZIONI // manually joined, see where clauses
    where
      1 == 1
      !(exists(subquery))
         select top 1 // 
           1
         from 
           NGTPRIVILEGI // master table
         where
           NGTPRIVILEGI.IDUTENTE == Utenti.IDUTENTE
           NGTPRIVILEGI.IDAPPLICAZIONE == NGTAPPLICAZIONI.IDAPPLICAZIONE
//          
     
    Privilegio p = Privilegio.create(IDUTENTE, IDAPPLICAZIONE)
    allCreatedPrivilegi.addRef(p)
    if (mode == normalMode)
    {
      p.saveToDB(...)
    }
  }
   
  return allCreatedPrivilegi
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event Privilegio.OnInit()
{
  PRIVILEGI = null
  PRIVREAD = No
  PRIVEXECUTE = No
  PRIVINSERT = No
  PRIVUPDATE = No
  PRIVDELETE = No
  PRIVPRINT = No
  PRIVEXPORT = No
  PRIVEMAIL = No
  PRIVPERS1 = No
  PRIVPERS2 = No
  PRIVPERS3 = No
  PRIVPERS4 = No
  PRIVPERS5 = No
  PRIVDATIP = No
}


// ──────────────────────────────────

// *******************************************
// computes if the current LoginToken is valid
// Valid means both:
// non expired
// non already used (consumed)
// *******************************************
private boolean LoginToken.isValid()
{
  // We decide that the token stays valid from one hour after creation, in priinciple 5 seconds should be enough but we start with 1 hour since there could be minor time differences between the clocks of the PC
  // where DB runs and where the Webapp runs
  // For permanent tokens we consider one year
  QappRuntimeBehavior qrb = getQappRuntimeBehavior()
  date time expirationMoment = dateAdd(Hour, 1, TIMESTAMP)
  if (qrb.PermanentTokens)
    expirationMoment = dateAdd(Year, 1, TIMESTAMP)
  else 
    expirationMoment = dateAdd(Hour, 1, TIMESTAMP)
   
  boolean alreadyExpired = now() > expirationMoment
  boolean alreadyConsumed = (CONSUMED == Yes)
  boolean invalidToken = alreadyExpired or alreadyConsumed
   
  return !(invalidToken)
}


// ──────────────────────────────────

// ********************************************************************************************
// returns the utente associated with the token
// 
// If a user is returned it means that authentication authentication with the user can proceed.
// ********************************************************************************************
public Utente LoginToken.getAuthenticatedUser()
{
  Utente returnedUtente = null
   
  boolean loginTokenIsValid = this.isValid()
   
  if (loginTokenIsValid)
  {
     
    Utente user = new()
    user.IDUTENTE = IDUTENTE
    try 
    {
      user.loadFromDB(0)
      returnedUtente = user
    }
    catch 
    {
      returnedUtente = null
    }
  }
  else 
  {
    returnedUtente = null
    QappCore.DTTLogMessage("Token not valid (get auth user)", ...)
  }
   
  return returnedUtente
}


// ──────────────────────────────────

// ************************************************************************************************************************************************************************************
// consumes the token
// 
// to avoid DB deadlocks that can occur in the DO Db transaction we save to DB a copy of the object so a timer will later reconcile this, by updating this object and deleting the copy
// ************************************************************************************************************************************************************************************
public void LoginToken.consume()
{
  CONSUMED = Yes
   
  // saving the copy will not make the deadlock occur (I do not remembre why at the moment of writing but for sure it is like this)
  LoginToken ltToBeREconciled = this.createToBeReconciledCopy()
  ltToBeREconciled.saveToDB(...)
   
  // we now tell the QAPP_CORE_SESSION to reconcile the token
  QappCore.sendSessionMessage(QappCoreSession, "RECONCILE_TOKENS", ...)
}


// ──────────────────────────────────

// ********************************************************************************************************************************************
// given a token an Utente is authenticated and returned as result, the boolean parameter isValidToken keeps whether the token was valid or not
// ********************************************************************************************************************************************
public static Utente LoginToken.authenticateUserWithToken(
  string token                       // 
  boolean commandContainsAUrlCommand // to be set to true if che url contains a QAppCommand
  inout boolean isValidToken         // output parameter
)
{
  // if QAPP_CMD is not in the URL commandContainsAUrlCommand should be false (so we still consume token whrn the qapp is opened from the Qualibus menu)
   
  LoginToken lt = new()
  lt.CONSUMED = No
  lt.VALUE = token
   
  boolean badToken = false
  Utente authenticatedUser = new()
   
  try 
  {
    // isolationLevel 1 allows to do dirty reads (reading uncommited changes), this is useful when loading the token occurs during an after save event and we are in a DB transaction, in this way we are able to
    // read the token
    QualibusDB.closeConnection()
    QualibusDB.isolationLevel = 1
     
    lt.loadFromDB(0)
     
    // since isolationLevel 1 makes sense only for a specific workaround we immediately go back to normal (default is 2)
    QualibusDB.closeConnection()
    QualibusDB.isolationLevel = 2
     
    authenticatedUser = lt.getAuthenticatedUser()
     
     
     
     
    // there is only one case where we do not consume the token (and therefore it becomes reusable several times):
    // when PermanentTokens is true and the qapp has been called with a Qapp Command (=the Qapp has not been called from Qualibus menu)
     
    DevTools.ToBeReviewed("In fact also when the call comes from postman the token is not consumed")
     
    boolean startedFromQualibusMenu = !(commandContainsAUrlCommand)
    QappRuntimeBehavior qrb = getQappRuntimeBehavior()
    boolean permanentTokens = qrb.PermanentTokens
     
    if (startedFromQualibusMenu or (!(startedFromQualibusMenu) and !(permanentTokens)))
      lt.consume()
     
  }
  catch 
  {
    badToken = true
  }
  isValidToken = !(badToken)
  return authenticatedUser
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static LoginToken LoginToken.create(
  Utente utente // 
)
{
  if (!(utente))
  {
    QappCore.DTTLogMessage("Utente non passed", ..., DTTError)
  }
   
  LoginToken lt = new()
  lt.init()
  lt.IDUTENTE = utente.IDUTENTE
   
  return lt
}


// ──────────────────────────────────

// ********************************************************************************
// it returns a copy of the curent object, with _RECONCILE_ME appended to the VALUE
// ********************************************************************************
private LoginToken LoginToken.createToBeReconciledCopy()
{
  LoginToken ltToBeREconciled = new()
  ltToBeREconciled.init()
  ltToBeREconciled.VALUE = VALUE + this.getReconciliationString()
  ltToBeREconciled.IDUTENTE = IDUTENTE
  ltToBeREconciled.CONSUMED = CONSUMED
  ltToBeREconciled.TIMESTAMP = TIMESTAMP
  return ltToBeREconciled
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static string LoginToken.getReconciliationString()
{
  return "_RECONCILE_ME"
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static void LoginToken.reconcileTokens()
{
  string reconcileMeText = LoginToken.getReconciliationString()
  IDCollection tokensToBeReconciled of LoginToken = new()
  select into collection (tokensToBeReconciled)
  from 
    LoginToken // master table
  where
    right(VALUE, 13) == reconcileMeText
   
  for each LoginToken lt in tokensToBeReconciled
  {
    string pureToken = replace(lt.VALUE, reconcileMeText, "")
     
    IDCollection loginTokenWithMatchingValue of LoginToken = new()
    select into collection (loginTokenWithMatchingValue)
    from 
      LoginToken // master table
    where
      VALUE == pureToken
     
    LoginToken originalToken = new()
    originalToken.VALUE = pureToken
     
    // note: the case of count >= 2 has been observed while manually inserting tokens in DB and by change a token with the same value was already inesrted in DB, it is basically impossible to reproduce in
    // production with runtime generated GUIDs but anyway now the code is safer since a duplication is fixed
    if (loginTokenWithMatchingValue.count() >= 2 or loginTokenWithMatchingValue.count() == 0)
    {
      // handle duplicates (the delete query makes sense only for count >= 0...
       
      delete from NGTTOKENS
      where
         VALUE == pureToken
       
      // ...while the creation of the new token makes sense also for count == 0
      originalToken.init()
      originalToken.TIMESTAMP = lt.TIMESTAMP
      originalToken.IDUTENTE = lt.IDUTENTE
    }
    else 
    {
      originalToken.loadFromDB(...)
    }
     
    originalToken.CONSUMED = lt.CONSUMED
     
     
    originalToken.saveToDB(...)
    lt.deleted = true
    lt.saveToDB(...)
  }
   
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event LoginToken.OnInit()
{
  CONSUMED = No
  TIMESTAMP = now()
  VALUE = docIDToGuid(newDocID())
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event Reparto.OnInit()
{
  IDREPARTO = Sequence.getNextSequence(NGTN_ID_REPARTO, ...)
}


// ──────────────────────────────────

// ************************************************************
// Event raised to the document during the validation procedure
// ************************************************************
event Reparto.OnValidate(
  int:validateReasons Reason // An integer specifying the reason the document is being validated. The most common values are 0 to indicate complete validation before saving, or 1 to indicate a quick validati...
  inout boolean Error        // A boolean output parameter. If set to True, specifies an error condition and stops the validation immediately.
  inout boolean Skip         // A boolean output parameter. If set to True, does not check the rules for automatic validation of the document (see notes).
)
{
  if (deleted)
  {
    int numberOfUsersWithCurrentReparto = this.countNumberOfUsersAssigned()
     
    if (numberOfUsersWithCurrentReparto > 0)
    {
      this.addDocumentError("Il reparto  è in uso, impossibile eliminarlo")
      Error = true
    }
  }
  if (right(DESCRREPARTO, 1) == "k")
  {
    this.setPropertyError("non può finire con k", DESCRREPARTO)
  }
}


// ──────────────────────────────────

// ****************************************************
// it returns how many usesrs are linked to the reparto
// ****************************************************
private int Reparto.countNumberOfUsersAssigned()
{
  int numberOfUsersWithCurrentReparto = 0
  select into variables (found variable)
    set numberOfUsersWithCurrentReparto = count(...)
  from 
    Utenti // master table
  where
    IDREPARTO == IDREPARTO
  return numberOfUsersWithCurrentReparto
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static Reparto Reparto.get(
  int idReparto // 
)
{
  Reparto r = new()
  r.IDREPARTO = idReparto
  try 
  {
    r.loadFromDB(...)
  }
  catch 
  {
    QappCore.DTTLogMessage(formatMessage("Reparto with id '|1' not found", idReparto, ...), ..., DTTError)
    r = null
  }
  return r
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static Reparto Reparto.create(
  string descrReparto // 
)
{
  Reparto r = new()
  r.init()
   
  r.DESCRREPARTO = descrReparto
   
  return r
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static UserCreation UserCreation.create(
  string:userCreationModes mode // 
)
{
  UserCreation uc = new()
  uc.init()
  uc.CreationMode = mode
   
  return uc
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void UserCreation.createObjects()
{
  Reparto r = Reparto.get(IDREPARTO)
   
  string userDescription = this.capitalizeFirstLetterOnly(COGNOME) + this.capitalizeFirstLetterOnly(NOME)
   
  CreatedUtente = Utente.create(r, Username, userDescription, NormalUser)
   
  try 
  {
    CreatedUtente.saveToDB(...)
    UtenteJustBeenSuccesfullyInsertedInDb = true
  }
  catch 
  {
    QappCore.DTTLogMessage("impossible to save utente in DB", ...)
  }
   
   
  Personale involvedPersonale = null
  if (CreationMode == createNewEmployee)
  {
    CreatedPersonale = Personale.create(COGNOME, NOME, Tipo)
    involvedPersonale = CreatedPersonale
  }
  else 
  {
    involvedPersonale = ExistingPersonale
  }
  involvedPersonale.IDUTENTE = CreatedUtente.IDUTENTE
   
  // save with no validation (the second parameter of SaveToDB is FALSE) since we want to create a persoale even if there are mandatory custom data (as C/S does)
  involvedPersonale.saveToDB(999, false)
  DevTools.ToBeReviewed("ideally we should not save without validating, but we should prompt the user to fill the mandatory data, anyway this is how c/s works so as initial dev it is done like this")
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string UserCreation.capitalizeFirstLetterOnly(
  string value // 
)
{
  return upper(left(value, 1)) + lower(right(value, length(value) - 1))
}


// ──────────────────────────────────

// ***************************************************************
// this method is intended for unit tests to clean created objects
// deletion is called with no validation
// ***************************************************************
public void UserCreation.deleteObjectsFromDb()
{
   
  // if null it means never created
  if (CreatedPersonale)
  {
    CreatedPersonale.deleted = true
    CreatedPersonale.saveToDB(999, false)
  }
  if (CreatedUtente)
  {
    CreatedUtente.deleted = true
    CreatedUtente.saveToDB(999, false)
  }
}


// ──────────────────────────────────

// ***********************************************************
// Event raised to the document when the transaction is closed
// ***********************************************************
event UserCreation.OnEndTransaction()
{
   
  // load corresponding Personale from db if a personale has been chosen
  if (wasModified(IdExistingPersonale) and IdExistingPersonale != null)
  {
    Personale p = Personale.getFromDB(IdExistingPersonale, ...)
     
    // capitalizing first letter here it is just for aesthetics (since in this case personale won't be created since it is alerady inserted
    NOME = this.capitalizeFirstLetterOnly(p.NOME)
    COGNOME = this.capitalizeFirstLetterOnly(p.COGNOME)
    ExistingPersonale = p
  }
   
  // then compute username if it is still empty
  if (Username == "")
  {
    Username = lower(left(NOME, 1) + COGNOME)
  }
}


// ──────────────────────────────────

// ************************************************************
// Event raised to the document during the validation procedure
// ************************************************************
event UserCreation.OnValidate(
  int:validateReasons Reason // An integer specifying the reason the document is being validated. The most common values are 0 to indicate complete validation before saving, or 1 to indicate a quick validati...
  inout boolean Error        // A boolean output parameter. If set to True, specifies an error condition and stops the validation immediately.
  inout boolean Skip         // A boolean output parameter. If set to True, does not check the rules for automatic validation of the document (see notes).
)
{
   
  // we do not want quick validation to avoid red fields before submit
  if (Reason == Quick)
  {
    Skip = true
    return 
  }
   
  if (Reason == Complete)
  {
    string commonErrorMsg = "è necessario definire un valore"
    if (COGNOME == "")
    {
      this.setPropertyError(commonErrorMsg, COGNOME)
      Error = true
    }
    if (NOME == "")
    {
      this.setPropertyError(commonErrorMsg, NOME)
      Error = true
    }
    if (Tipo == null)
    {
      this.setPropertyError(commonErrorMsg, Tipo)
      Error = true
    }
    if (Username == "")
    {
      this.setPropertyError(commonErrorMsg, Username)
      Error = true
    }
    if (IDREPARTO == null)
    {
      this.setPropertyError(commonErrorMsg, IDREPARTO)
      Error = true
    }
     
    // test for uniqueness of username only if the user has not just been inserted (this is done because validation is called both from the panel, and in this case it will check for uniquenss, and manual, in
    // this case we do not need it
    if (!(UtenteJustBeenSuccesfullyInsertedInDb))
    {
      if (Utente.userExists(Username))
      {
         this.setPropertyError("il valore deve essere univoco", Username)
         Error = true
      }
    }
  }
}


// ──────────────────────────────────



// ──────────────────────────────────

// *************************************************************************************************************************************************************************************************************
// From a string such as (103,125) the utenti collection is populated with the corresponding Utente objects, this method must be called manyally because if called in after load it generates a utenti load loop
// *************************************************************************************************************************************************************************************************************
public void CalendarFilters.DecodeVisibileUsers()
{
  string visibileUsersString = VISIBLEUSERS
   
  // remove spaces
  visibileUsersString = replace(visibileUsersString, " ", "")
   
   
  // remove parenthesis
  visibileUsersString = replace(visibileUsersString, "(", "")
  visibileUsersString = replace(visibileUsersString, ")", "")
   
  IDArray ida = SH.tokenizeToArray(visibileUsersString, ",", ...)
  for (int i = 0; i < ida.length(); i = i + 1)
  {
    int currentIdUtente = ida.getValue(i)
    Utente u = Utente.get(currentIdUtente)
    if (u)
    {
      this.addUtente(u)
    }
  }
}


// ──────────────────────────────────

// ************************************************************************************************************
// VISIBLEUSERS and VISIBERUSERSORDER strings are computed from the utenti collection
// VISIBLEUSERS is enclosed in parenthesis, both fields have a comma separated string like this "1, 2, 3, 4, 5"
// VISIBILEUSERS example: "( 231, 21, 123 )"
// VISIBERUSERSORDER  example: " 1, 2, 3"
// 
// note: these odds spaces in the examples above come from Delphi code, we keep the same!
// ************************************************************************************************************
public void CalendarFilters.computeVisibileUsers()
{
  string visibileUsersString = null
  string visibileUsersOrderString = null
  int counter = 0
  if (Utenti.count() == 0)
  {
    this.DecodeVisibileUsers()
  }
  if (Utenti.count() > 0)
  {
    for each Utente u in Utenti
    {
      counter = counter + 1
      if (visibileUsersString == "")
      {
         visibileUsersString = "( " + toString(u.IDUTENTE)
          
         // orderString has no parenthesis!
         visibileUsersOrderString = " " + toString(counter)
      }
      else 
      {
         visibileUsersString = visibileUsersString + ", " + toString(u.IDUTENTE)
         visibileUsersOrderString = visibileUsersOrderString + ", " + toString(counter)
      }
    }
     
    // Close parenthesis only to VISIBLEUSERS
    if (visibileUsersString != "")
    {
      visibileUsersString = visibileUsersString + " )"
    }
     
  }
   
  VISIBLEUSERS = visibileUsersString
  VISIBLEUSERSORDER = visibileUsersOrderString
}


// ──────────────────────────────────

// ******************
// add user to filter
// ******************
public void CalendarFilters.addUtente(
  Utente utente // 
)
{
  Utenti.add(utente)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static CalendarFilters CalendarFilters.create(
  Utente utente // 
)
{
  CalendarFilters co = new()
  QappCore.DTTLogMessage(co.IDOPTION, 777, ...)
  co.init()
  QappCore.DTTLogMessage(co.IDOPTION, 777, ...)
  co.IDUTENTE = utente.IDUTENTE
   
  // 2 users are added to the utenti collection: the user for which we are just creating the object...
  co.addUtente(utente)
   
  // setOriginal is called so the currently being inserted user is not seen as to be inserted and in the savoing phase it will not be insertd: transient collections are just without auto loading, but the
  // fraemwork will try to save them anymore. This is how inde works and we must stay with it.
  co.Utenti.setOriginal()
   
  Utente utenteBacheca = TabParametri.getBachecaUser()
   
  if (utenteBacheca)
  {
     
    // ... and the Bacheca ("notice board") user
    co.addUtente(utenteBacheca)
  }
   
   
  return co
}


// ──────────────────────────────────

// **************************************************************************************
// Event raised to the document during the initial steps of the document saving procedure
// **************************************************************************************
event throws exception CalendarFilters.BeforeSave(
  inout boolean Skip         // A boolean output parameter. If set to True, tells the framework not to perform operations on the database, because they will be managed independently.
  inout boolean Cancel       // A boolean output parameter. If set to True, tells the framework to cancel the save procedure due to errors that occurred. Since the entire document is saved in the same transa...
  int:beforeSavePhases Phase // An integer from 0 to 3 that specifies the progress status of the save procedure as detailed at the beginning of the article.
)
{
  if (Phase = PreSave)
  {
    this.computeVisibileUsers()
  }
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event CalendarFilters.OnInit()
{
  IDOPTION = Sequence.getNextSequence(DIS_ID_OPTION, ...)
  CURRENTVIEW = WeekView
  TIMEGRIDON = No
  PROMSHOW = Yes
  PROMNOESEC = No
  PROMESECNONATT = Yes
  PROMESECATT = No
  PROMSTATOAPERTO = Yes
  PROMCOPIARESP = No
  MEMOSSHOW = Yes
  MEMOSNORMALI = Yes
  MEMOSRICCORRENTI = Yes
  EVENTIDISPSHOW = Yes
  EVENTIDISPAPERTE = Yes
  EVENTIDISPCOPIARESP = No
  INTERSHOW = Yes
  INTERPRGAPERTI = Yes
  INTERPRGCHIUSI = No
  INTERSTRAPERTI = Yes
  INTERSTRCHIUSI = No
  INTERCOPIARESP = No
  PRGATTSHOW = Yes
  PRGATTPREV = Yes
  PRGATTCONS = Yes
  PRGATTCOPIARESP = Yes
  PRGCOSTISHOW = Yes
  PRGCOSTI = No
  PRGCOSTICOPIARESP = No
  NOTIFICHESHOW = Yes
  NOTIFICHEPRG = Yes
  NOTIFICHEVAL = No
  INTERVALLOTEMP = 0
  IMPEGNISCADUTI = 0
  MESSIPASSATI = 2
  MESSIFUTURI = 4
  DATADA = toDate(year(today()), 1, 1)
  DATAA = toDate(year(today()), 12, 31)
  ISFILTER = No
  WORKTIMEONLY = No
  CALENDARSETTINGS = null
  SCADUTISETTINGS = null
  EVENTINOTIFICASHOW = Yes
  EVENTINOTIFICAAPERTE = Yes
  EVENTINOTIFICACHIUSE = No
  PROFID = 0
  GROUPINGKIND = groupByUser
}


// ──────────────────────────────────

// ************************************************************
// Event raised by the document after loading from the database
// ************************************************************
event CalendarFilters.AfterLoad(
  boolean AlreadyLoaded     // A boolean parameter that specifies whether the document was previously loaded.
  IDArray LoadedCollections // IDArray type parameter that contains the collections loaded by the framework. If the value of the Already Loaded parameter is False, the Loaded Collections parameter is NULL.
)
{
   
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event NGTUSERSMTP.OnInit()
{
   
  SMTPAUTHTYPE = "satDefault"
  SMTPUSETLS = "utUseExplicitTLS"
  SMTPSSLOPTIONSMETHOD = "sslvTLSv1"
  USESMTPONLY = Yes
  IMAPUSETLS = "utUseImplicitTLS"
  IMAPSSLOPTIONSMETHOD = "sslvSSLv23"
  IMAPAUTHTYPE = "iatUserPass"
  ISANONYMOUS = No
  SECURETYPES = SSL/TLS
  IMAPSENTFOLDER = "Sent"
   
   
}


// ──────────────────────────────────

// **************************************************************************************
// Event raised to the document during the initial steps of the document saving procedure
// **************************************************************************************
event throws exception NGTUSERSMTP.BeforeSave(
  inout boolean Skip         // A boolean output parameter. If set to True, tells the framework not to perform operations on the database, because they will be managed independently.
  inout boolean Cancel       // A boolean output parameter. If set to True, tells the framework to cancel the save procedure due to errors that occurred. Since the entire document is saved in the same transa...
  int:beforeSavePhases Phase // An integer from 0 to 3 that specifies the progress status of the save procedure as detailed at the beginning of the article.
)
{
  this.enryptPassword()
}


// ──────────────────────────────────



// ──────────────────────────────────

// ************************************************************
// Event raised to the document during the validation procedure
// ************************************************************
event NGTUSERSMTP.OnValidate(
  int:validateReasons Reason // An integer specifying the reason the document is being validated. The most common values are 0 to indicate complete validation before saving, or 1 to indicate a quick validati...
  inout boolean Error        // A boolean output parameter. If set to True, specifies an error condition and stops the validation immediately.
  inout boolean Skip         // A boolean output parameter. If set to True, does not check the rules for automatic validation of the document (see notes).
)
{
   
  if (Reason == Complete)
  {
    string commonErrorMsg = "è necessario definire un valore"
    if (HOST == "" or isNull(HOST))
    {
      this.setPropertyError(commonErrorMsg, HOST)
      Error = true
    }
    if (UnencryptedPassword == "" or isNull(UnencryptedPassword))
    {
      this.setPropertyError(commonErrorMsg, UnencryptedPassword)
      Error = true
    }
    if (SMTPUSERNAME == "" or isNull(SMTPUSERNAME))
    {
      this.setPropertyError(commonErrorMsg, SMTPUSERNAME)
      Error = true
    }
     
     
    if (PORT == 0 or PORT == isNull(""))
    {
      this.setPropertyError(commonErrorMsg, PORT)
      Error = true
    }
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string NGTUSERSMTP.retrievePassword()
{
  string decrypted = ""
  SimpleCrypter sc = new()
  sc.compatibilityMode = true
  string base64decodedPassword = base64Decode(EMAILPASSWORD)
  decrypted = sc.decrypt(Key, base64decodedPassword)
  return decrypted
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void NGTUSERSMTP.enryptPassword()
{
  string encryptedPassword = ""
  SimpleCrypter sc = new()
  sc.compatibilityMode = true
  encryptedPassword = sc.encrypt(Key, UnencryptedPassword)
  string base64EncodedEncryptedPassword = base64Encode(encryptedPassword)
  EMAILPASSWORD = base64EncodedEncryptedPassword
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static UtenteVisibility UtenteVisibility.create(
  Utente utente // 
)
{
  UtenteVisibility uv = new()
  uv.Utente = utente
  return uv
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public IDCollection UtenteVisibility.getIdsUserCanSee(
  int:kordapp kordApp    // 
  optional int subId = 0 // 
  optional string:utenteVisibilityGetResultsTypes getResultsType = "consultazione" // 
)
{
  IDCollection results of IntDataType = null
   
  switch (kordApp)
  {
    case ClientiFornitori:
      results = this.getResultsForClifor()
    break
    case Personale:
      results = this.getResultsForPersonale()
    break
    case Eventi:
      results = this.getResultsForEvento(subId)
    break
    case ModelliEventi:
      results = this.getResultsForModelloEvento(subId, getResultsType)
    break
    case Progetti:
      results = this.getResultsForProgetto()
    break
    case Funzioni:
      results = this.getResultsForFunzione()
    break
    case Articoli:
      results = this.getResultsForArticolo()
    break
    case Privati:
      results = this.getResultsForPrivato()
    break
    case AltreAnagrafiche:
      results = this.getResultsForAltreAnagrafiche(subId)
    break
    case Documenti:
      results = this.getResultsForDocumento()
    break
    case Indicatori:
      results = this.getResultForIndicatori()
    break
    default:
      // we dont have results in case of modules like applications manager (that has kordapp), if kordapp is not existing we handle it after
      results = new()
    break
  }
  return results
}


// ──────────────────────────────────

private IDCollection UtenteVisibility.getResultsForAltreAnagrafiche(
  optional int singleClassModeId = 0 // 
)
{
  IDCollection allVisibleIdCespite of IntDataType = new()
  boolean userIsPers1InAltreAnagrafiche = Utente.hasSpecificPrivilegeForKordApp(AltreAnagrafiche, Pers1)
  Personale personale = Utente.getLinkedPersonale()
   
   
//  // filtering of cespite records based on visiblity done in two ways, later we decided which way to go
//  // One way: query fired in loop to filter visible cespite records + 1 more DO based query
//  for each row (readonly)
//  {
//    select
//      IDCespite = AltreAnagrafiche.ID
//      idUtenteReferente = AltreAnagrafiche.IDUTENTEREFERENTE
//      idDipendenteResponsible = AltreAnagrafiche.IDRESPONSABILE
//    from 
//      AltreAnagrafiche // master table
//      CESTIPICESPITE   // manually joined, see where clauses
//    where
//      AltreAnagrafiche.IDTIPOCESPITE == CESTIPICESPITE.IDTIPIINFRSTR
//      (CESTIPICESPITE.IDTIPOCESPITE < 3 and singleClassModeId in "1,2") or (CESTIPICESPITE.IDTIPOCESPITE == singleClassModeId)
//     
//     
//    if (userIsPers1InAltreAnagrafiche)
//    {
//      // filter 1A : filter based on Pers1
//      allVisibleIdCespite.add(IntDataType.createInteger(IDCespite))
//    }
//    else 
//    {
//      // filter 1B : filter based on CES_PERMESSI or Referente or Responsible
//       
//       
//      boolean currentDipendenteIsResponsible = idDipendenteResponsible == personale.IDDIPENDENTE
//      boolean currentUserIsReferente = idUtenteReferente == Utente.IDUTENTE
//       
//      AltreAnagrafiche aa = null
//      boolean userHasOpenRoleOnCespite = false
//      if (!(currentUserIsReferente or currentDipendenteIsResponsible))
//      {
//         aa = AltreAnagrafiche.getFromDB(IDCespite, quickLoad)
//         userHasOpenRoleOnCespite = CESPERMESSI.userHasSpecificPriviledge(aa, Utente, Open)
//      }
//       
//      boolean filterConditionMatches = currentDipendenteIsResponsible or currentUserIsReferente or userHasOpenRoleOnCespite
//       
//      if (filterConditionMatches)
//      {
//         allVisibleIdCespite.add(IntDataType.createInteger(IDCespite))
//      }
//    }
//     
//  }
   
  // Another way: query fired in loop to filter visible cespite records considering all join, typical c/s way (less preferred but perticular in this query not many conditions so we can consider this too, it is
  // quite fast)
  for each row (readonly)
  {
    select
      IDCespite = AltreAnagrafiche.ID
    from 
      AltreAnagrafiche // master table
      CESTIPICESPITE   // manually joined, see where clauses
    where
      AltreAnagrafiche.IDTIPOCESPITE == CESTIPICESPITE.IDTIPIINFRSTR
      (CESTIPICESPITE.IDTIPOCESPITE < 3 and singleClassModeId in "1,2") or (CESTIPICESPITE.IDTIPOCESPITE == singleClassModeId)
      (userIsPers1InAltreAnagrafiche == true) or (AltreAnagrafiche.IDUTENTEREFERENTE == Utente.IDUTENTE) or (AltreAnagrafiche.IDRESPONSABILE == personale.IDDIPENDENTE) or (exists(subquery))
         select top 1 // 
           ID
         from 
           CESPERMESSI // master table
         where
           CESPERMESSI.ID == AltreAnagrafiche.ID
           CESPERMESSI.IDUTENTE == Utente.IDUTENTE
           CESPERMESSI.V == Yes and CESPERMESSI.O == Yes
//          
    // 
    allVisibleIdCespite.add(IntDataType.createInteger(IDCespite))
  }
   
  return allVisibleIdCespite
}


// ──────────────────────────────────

private IDCollection UtenteVisibility.getResultsForArticolo()
{
  IDCollection allVisibleIdArticolo of IntDataType = new()
  boolean userHasArticoliExecutePrivilege = Utente.hasSpecificPrivilegeForKordApp(Articoli, Execute)
   
  if (!(userHasArticoliExecutePrivilege))
  {
    allVisibleIdArticolo.add(IntDataType.createInteger(0))
  }
  else 
  {
    QappCore.DTTLogMessage("we do not need to add anything to the collection because we assume empty collection = ALL to avoid to return a long list of all articoli", ..., DTTInfo)
    QappCore.DTTLogMessage("in the method initializePanelSearchQueriesRuntimeFilters we use "-1" approach to handle "see all"", ..., DTTInfo)
  }
   
  return allVisibleIdArticolo
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private IDCollection UtenteVisibility.getResultsForClifor()
{
  IDCollection allVisibleIdConto of IntDataType = new()
  boolean userHasPers1InClifor = Utente.hasSpecificPrivilegeForKordApp(ClientiFornitori, Pers1)
   
  for each row (readonly)
  {
    select
      idConto = ClientiFornitori.IDCONTO
    from 
      ClientiFornitori // master table
      GCFTIPICONTO     // joined with Clienti Fornitori using key FK_GCF_ANAGRAFICA_ID_TIPO_CONTO
    where
      (userHasPers1InClifor == true) or (exists(subquery))
         select top 1 // 
           IDCLIFORROLE
         from 
           TipoCliforRoles // master table
         where
           TipoCliforRoles.IDTIPICLIFOR == GCFTIPICONTO.IDTIPICLIFOR
           TipoCliforRoles.IDUTENTE == Utente.IDUTENTE
           TipoCliforRoles.VISUALIZZA == Yes
     
    allVisibleIdConto.add(IntDataType.createInteger(idConto))
  }
  return allVisibleIdConto
}


// ──────────────────────────────────

private IDCollection UtenteVisibility.getResultsForDocumento()
{
  IDCollection allVisibleIdDocumento of IntDataType = new()
  boolean userIsPers1InDocumenti = Utente.hasSpecificPrivilegeForKordApp(Documenti, Pers1)
   
  // query based filter of all visible documents - not preferred way but is fast
  for each row (readonly)
  {
    select distinct
      IDDOCUMENTO = Documenti.IDDOCUMENTO
    from 
      Documenti   // master table
      DOCCARTELLE // joined with Documenti using key FK_DOC_DOCUMENTI01
      Revisions   // joined with Documenti using key FK_DOC_REVISIONI01
      DOCPERMESSI // joined with DOC CARTELLE using key FK_DOC_PERMESSI01
    where
      isNull(Revisions.DATACONSERVAZIONE)
      !(isNull(Revisions.DATAAPPROVAZIONE))
      (userIsPers1InDocumenti == true) or (DOCPERMESSI.IDUTENTE == Utente.IDUTENTE and DOCPERMESSI.LETTURA == Yes)
     
     
    allVisibleIdDocumento.add(IntDataType.createInteger(IDDOCUMENTO))
  }
   
  return allVisibleIdDocumento
}


// ──────────────────────────────────

// ************************************************************************************************
// // Returns the collection of IDEVENTO visible to the current user.
// //
// // Optional singleClassModeId (!= 0) restricts results to a specific IDCLASSE;
// // otherwise all classes are considered.
// //
// // An evento is visible when ALL of the following conditions satisfied:
// //   1. Class scope matches (single class or all).
// //   2. User has VISUALIZZA == Yes on the evento's ambito (EventoAmbitoRoles).
// //   3. At least ONE of:
// //        - User has Pers1 privilege on Eventi (global override), OR
// //        - User inserted the evento (IDUTENTEINS), OR
// //        - User is the evento responsible (IDUTENTERESP), OR
// //        - User is the direct executor of a disposizione,  OR
// //        - User belongs to a function assigned as disposizione executor (ISFUNCTION == Yes), OR
// //        - User has VISIBILITA == Yes on the evento's classe (EventoClasseRoles).
// //
// // 
// ************************************************************************************************
private IDCollection UtenteVisibility.getResultsForEvento(
  optional int singleClassModeId = 0 // 
)
{
  IDCollection allVisibleIdEvento of IntDataType = new()
  boolean singleClassMode = false
  if (singleClassModeId != 0)
  {
    singleClassMode = true
  }
   
  boolean userHasPers1InEventi = Utente.hasSpecificPrivilegeForKordApp(Eventi, Pers1)
   
  // another way (less preferred) to collect all evento (with all conditions in query)- but is super fast(20x faster) later we decide which one to keep
  for each row (readonly)
  {
    select
      IDEVENTO = IDEVENTO
    from 
      Eventi // master table
    where
      (IDCLASSE == singleClassModeId and singleClassMode == true) or (singleClassMode == false)
      exists(subquery)
         select top 1 // user has visulizza Role on Ambito
           Eventi.IDAMBITO
         from 
           EventoAmbitoRoles // master table
         where
           EventoAmbitoRoles.IDAMBITO == Eventi.IDAMBITO
           EventoAmbitoRoles.IDUTENTE == Utente.IDUTENTE
           EventoAmbitoRoles.VISUALIZZA == Yes
      (userHasPers1InEventi == true) or (IDUTENTEINS == Utente.IDUTENTE) or (IDUTENTERESP == Utente.IDUTENTE) or exists(subquery) or exists(subquery1) or exists(subquery2)
         select top 1 // utente is disposizione executor
           Disposizioni.IDATTIVITA
         from 
           Disposizioni // master table
           Personale    // manually joined, see where clauses
         where
           Disposizioni.ISFUNCTION == No
           Disposizioni.IDEVENTO == Eventi.IDEVENTO
           Disposizioni.IDRESPATTIVITA == Personale.IDDIPENDENTE
           Personale.IDUTENTE == Utente.IDUTENTE
         select top 1 // function is disposizione executor and utente belongs to function
           Disposizioni1.IDATTIVITA
         from 
           Disposizioni1   // master table
           Funzioni        // manually joined, see where clauses
           FunzioneMembers // joined with Funzioni using key FK_MSQ_PERS_FUNZIONI01
           Personale1      // joined with Funzione Members using key FK_MSQ_PERS_FUNZIONI02
         where
           Disposizioni1.ISFUNCTION == Yes
           Disposizioni1.IDEVENTO == Eventi.IDEVENTO
           Disposizioni1.IDRESPATTIVITA == Funzioni.IDFUNZIONE
           FunzioneMembers.IDDIPENDENTE == Personale1.IDDIPENDENTE
           Personale1.IDUTENTE == Utente.IDUTENTE
         select top 1 // user has visibilta role on classe
           IDCLASSE
         from 
           EventoClasseRoles // master table
         where
           EventoClasseRoles.VISIBILITA == Yes
           EventoClasseRoles.IDUTENTE == Utente.IDUTENTE
           EventoClasseRoles.IDCLASSE == Eventi.IDCLASSE
     
    allVisibleIdEvento.add(IntDataType.createInteger(IDEVENTO))
  }
   
  return allVisibleIdEvento
   
//  // slow version for reference (but less performant)
//  {slow
//  {
//    boolean userIsPers1InEvento = Utente.hasSpecificPrivilegeForKordApp(Eventi, Pers1)
//     
//    // document based collection query not used here to avoid loading of all child collection in after load
//    for each row (readonly)
//    {
//      select
//         idEvento = IDEVENTO
//         idUtenteInsertedBy = IDUTENTEINS
//         idUtenteResponsible = IDUTENTERESP
//         idClasseEvento = IDCLASSE
//         idAmbitoEvento = IDAMBITO
//      from 
//         Eventi // master table
//      where
//         (IDCLASSE == singleClassModeId and singleClassMode == true) or (singleClassMode == false)
//       
//       
//      boolean idEventoToBeConsidered = this.isIdEventoVisible(idEvento, idUtenteInsertedBy, idUtenteResponsible, idClasseEvento, idAmbitoEvento, userIsPers1InEvento)
//       
//      if (idEventoToBeConsidered)
//         allVisibleIdEvento.add(IntDataType.createInteger(idEvento))
//    }
//  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean UtenteVisibility.isIdEventoVisible(
  int idEvento                // 
  int idUtenteInsertedBy      // 
  int idUtenteResponsible     // 
  int idClasse                // 
  int idAmbito                // 
  boolean userIsPers1InEvento // 
)
{
  boolean isVisible = false
  boolean insertedByUserMatching = idUtenteInsertedBy == Utente.IDUTENTE
  boolean responsibleUserMatching = idUtenteResponsible == Utente.IDUTENTE
  boolean userIsdisposizioniExecutor = false
  boolean userHasRoleToSeeTheClass = false
  boolean userHasRoleToSeeTheAmbito = false
   
  // case 1- high Privilege - user has Role to See the ambito
  userHasRoleToSeeTheAmbito = this.userHasRoleToSeeTheAmbito(idAmbito)
  if (userHasRoleToSeeTheAmbito)
  {
    if (userIsPers1InEvento)
      isVisible = true
    else if (insertedByUserMatching or responsibleUserMatching)
    {
      // case 3 - evento is inserted by user or user is responsible of evento
      isVisible = true
    }
    else 
    {
      // case 4 - user is executor in one of the disposizioni of evento or user is part of function defined as executor of one of the disposizioni
      userIsdisposizioniExecutor = this.userIsDisposizioniExecutor(idEvento)
      if (userIsdisposizioniExecutor)
         isVisible = true
      else 
      {
         // case 5 - user has Role to see the class 
         userHasRoleToSeeTheClass = this.userHasRoleToSeeTheClass(idClasse)
         if (userHasRoleToSeeTheClass)
           isVisible = true
      }
    }
  }
   
   
  return isVisible
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean UtenteVisibility.userIsDisposizioniExecutor(
  int idEvento // 
)
{
  boolean userIsDisposizioniExecutor = false
  Personale p = Utente.getLinkedPersonale()
  if (p)
  {
    boolean personaleFoundAsExecutor = false
    int vIDATTIVITADisposizione = 0
    select into variables (personaleFoundAsExecutor)
      set vIDATTIVITADisposizione = IDATTIVITA
    from 
      Disposizioni // master table
    where
      ISFUNCTION == No
      IDEVENTO == idEvento
      IDRESPATTIVITA == p.IDDIPENDENTE
     
    userIsDisposizioniExecutor = personaleFoundAsExecutor
  }
   
  if (!(userIsDisposizioniExecutor))
  {
    userIsDisposizioniExecutor = this.userIsInFunctionDefinedAsDisposizioniExecutor(idEvento)
  }
  return userIsDisposizioniExecutor
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean UtenteVisibility.userIsInFunctionDefinedAsDisposizioniExecutor(
  int idEvento // 
)
{
  boolean userIsDisposizioniExecutor = false
  Personale p = Utente.getLinkedPersonale()
  if (p)
  {
    boolean personaleInFunzioniFoundAsExecutor = false
    int vIDATTIVITADisposizione = 0
    select into variables (personaleInFunzioniFoundAsExecutor)
      set vIDATTIVITADisposizione = Disposizioni.IDATTIVITA
    from 
      Disposizioni    // master table
      Funzioni        // manually joined, see where clauses
      FunzioneMembers // joined with Funzioni using key FK_MSQ_PERS_FUNZIONI01
    where
      Disposizioni.ISFUNCTION == Yes
      Disposizioni.IDRESPATTIVITA == Funzioni.IDFUNZIONE
      Disposizioni.IDEVENTO == idEvento
      FunzioneMembers.IDDIPENDENTE == p.IDDIPENDENTE
     
    userIsDisposizioniExecutor = personaleInFunzioniFoundAsExecutor
  }
  return userIsDisposizioniExecutor
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean UtenteVisibility.userHasRoleToSeeTheClass(
  int idClasse // 
)
{
  boolean userHasClasseVisibilty = false
   
  int vIDCLASSEEventoClasseRole = 0
  select into variables (userHasClasseVisibilty)
    set vIDCLASSEEventoClasseRole = IDCLASSE
  from 
    EventoClasseRoles // master table
  where
    IDCLASSE == idClasse
    IDUTENTE == Utente.IDUTENTE
    VISIBILITA == Yes
   
   
  return userHasClasseVisibilty
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean UtenteVisibility.userHasRoleToSeeTheAmbito(
  int idAmbito // 
)
{
   
   
  boolean userHasAmbitoVisibility = false
   
  int vIDAMBITOEventoAmbitoRole = 0
  select into variables (userHasAmbitoVisibility)
    set vIDAMBITOEventoAmbitoRole = IDAMBITO
  from 
    EventoAmbitoRoles // master table
  where
    IDAMBITO == idAmbito
    IDUTENTE == Utente.IDUTENTE
    VISUALIZZA == Yes
   
   
   
  return userHasAmbitoVisibility
}


// ──────────────────────────────────

private IDCollection UtenteVisibility.getResultsForFunzione()
{
  IDCollection allVisibleIdFunzione of IntDataType = new()
  boolean userHasFunzioneExecutePrivilege = Utente.hasSpecificPrivilegeForKordApp(Funzioni, Execute)
  if (userHasFunzioneExecutePrivilege)
  {
    for each row (readonly)
    {
      select
         IDFUNZIONE = IDFUNZIONE
      from 
         Funzioni // master table
       
      allVisibleIdFunzione.add(IntDataType.createInteger(IDFUNZIONE))
    }
  }
   
   
  return allVisibleIdFunzione
}


// ──────────────────────────────────

private IDCollection UtenteVisibility.getResultsForModelloEvento(
  optional int singleClassModeId = 0                                     // 
  optional string:utenteVisibilityGetResultsTypes type = "consultazione" // 
)
{
  IDCollection allVisibleIdModelloEvento of IntDataType = new()
  boolean singleClassMode = false
  if (singleClassModeId > 0)
  {
    singleClassMode = true
  }
   
  boolean userHasExecutePriviledgeOnModelliEventi = Utente.hasSpecificPrivilegeForKordApp(ModelliEventi, Execute)
  boolean userHasPers1OnEventi = Utente.hasSpecificPrivilegeForKordApp(Eventi, Pers1)
   
  // with pers1 on eventi you see all classi attive
  // ambito visualizza is true or modello evento has not selected ambito
  // execute on modelli eventi
  // classe insertion (immissione) is true and classe is attiva
   
  for each row (readonly)
  {
    select
      idModelloVisible = IDTEMPLATEEVENTO
    from 
      ModelliEvento // master table
    where
      ((IDCLASSE == singleClassModeId) and (singleClassMode == true)) or (singleClassMode == false)
      userHasExecutePriviledgeOnModelliEventi == true
      ((exists(subquery) or nullValue(IDAMBITO, 0) == 0) and ((exists(subquery1) or (userHasPers1OnEventi == true)) and exists(subquery2)))
         select top 1 // ambito visualizza role is true
           IDAMBITO
         from 
           EventoAmbitoRoles // master table
         where
           EventoAmbitoRoles.IDAMBITO == ModelliEvento.IDAMBITO
           EventoAmbitoRoles.VISUALIZZA == Yes
           EventoAmbitoRoles.IDUTENTE == Utente.IDUTENTE
         select top 1 // classe immissione role is true and classe is active
           IDCLASSE
         from 
           EventoClasseRoles // master table
         where
           EventoClasseRoles.IDCLASSE == ModelliEvento.IDCLASSE
           EventoClasseRoles.IMMISSIONE == Yes
           EventoClasseRoles.IDUTENTE == Utente.IDUTENTE
         select top 1 // classe is active
           IDCLASSE
         from 
           EVACLASSIEVENTO // master table
         where
           EVACLASSIEVENTO.IDCLASSE == ModelliEvento.IDCLASSE
           EVACLASSIEVENTO.ATTIVA == Yes
     
    // if we are on the special case we need to see only the modelli evento attivi with ambito attivo
    switch (type)
    {
      case Special:
         for each row (readonly)
         {
           select
             idModelloEventoFiltered = IDTEMPLATEEVENTO
           from 
             ModelliEvento // master table
           where
             IDTEMPLATEEVENTO == idModelloVisible
             ATTIVO == Yes
             (exists(subquery) or nullValue(IDAMBITO, 0) == 0)
                select top 1 // 
                  IDAMBITO
                from 
                  EVAAMBITIEVENTO // master table
                where
                  EVAAMBITIEVENTO.IDAMBITO == ModelliEvento.IDAMBITO
                  EVAAMBITIEVENTO.ATTIVO == Yes
            
           allVisibleIdModelloEvento.add(IntDataType.createInteger(idModelloEventoFiltered))
         }
      break
      case Consultazione:
         allVisibleIdModelloEvento.add(IntDataType.createInteger(idModelloVisible))
      break
      default:
         QappCore.DTTLogMessage("Case not handled", ..., DTTError)
      break
    }
  }
   
  return allVisibleIdModelloEvento
}


// ──────────────────────────────────

private IDCollection UtenteVisibility.getResultsForPersonale()
{
  IDCollection allVisibleIdDipendente of IntDataType = new()
  IDCollection tipiDipendentUserCanSee of TipoPersonale = new()
  boolean userIsPers1InPersonale = Utente.hasSpecificPrivilegeForKordApp(Personale, Pers1)
   
  // filtering of personle records based on visiblity done in two ways, later we decide which way to go
  // One way: Query is fired in loop to collect all records for all visible tipo personale 
   
//  if (userIsPers1InPersonale)
//  {
//    IDCollection allTipiPersonale of TipoPersonale = new()
//     
//    // return all existing tipi
//    select into collection (allTipiPersonale)
//    from 
//      TipoPersonale // master table
//     
//    tipiDipendentUserCanSee.addAll(allTipiPersonale, true)
//  }
//  else 
//  {
//    IDCollection tipoPersonaleRolesForUser of TipoPersonaleRoles = TipoPersonaleRoles.getTipiPersonaleUserCanSee(Utente)
//    for each TipoPersonaleRoles pertipiruoli in tipoPersonaleRolesForUser
//    {
//      TipoPersonale tp = TipoPersonale.get(pertipiruoli.IDTIPOANAGR)
//      tipiDipendentUserCanSee.add(tp)
//    }
//  }
//   
//  for each TipoPersonale tp in tipiDipendentUserCanSee
//  {
//    for each row (readonly)
//    {
//      select
//         idDipendente = IDDIPENDENTE
//      from 
//         Personale // master table
//      where
//         IDTIPOANAGR == tp.IDTIPOANAGR
//       
//      int currentIdDipendente = toInteger(idDipendente)
//      allVisibleIdDipendente.add(IntDataType.createInteger(currentIdDipendente))
//    }
//  }
   
  // another way: query fired in loop to filter visible personale records considering all join, typical c/s way
  for each row (readonly)
  {
    select
      idDipendente = IDDIPENDENTE
    from 
      Personale // master table
    where
      (userIsPers1InPersonale == true) or exists(subquery)
         select top 1 // 
           IDTIPOANAGR
         from 
           TipoPersonaleRoles // master table
         where
           TipoPersonaleRoles.IDTIPOANAGR == Personale.IDTIPOANAGR
           TipoPersonaleRoles.IDUTENTE == Utente.IDUTENTE
           TipoPersonaleRoles.VISUALIZZA == Yes
     
    allVisibleIdDipendente.add(IntDataType.createInteger(idDipendente))
  }
   
  return allVisibleIdDipendente
}


// ──────────────────────────────────

private IDCollection UtenteVisibility.getResultsForPrivato()
{
  IDCollection allVisibleIdPrivato of IntDataType = new()
  boolean userHasPrivatoExecutePrivilege = Utente.hasSpecificPrivilegeForKordApp(Privati, Execute)
   
  if (userHasPrivatoExecutePrivilege)
  {
    for each row (readonly)
    {
      select
         IDCITTADINICITANAGRAFICA = IDCITTADINI
      from 
         Privati // master table
       
      allVisibleIdPrivato.add(IntDataType.createInteger(IDCITTADINICITANAGRAFICA))
    }
  }
   
  return allVisibleIdPrivato
}


// ──────────────────────────────────

private IDCollection UtenteVisibility.getResultsForProgetto()
{
  IDCollection allVisibleIdProgetto of IntDataType = new()
  boolean userIsPers1InProgetti = Utente.hasSpecificPrivilegeForKordApp(Progetti, Pers1)
  boolean userIsPers2InProgetti = Utente.hasSpecificPrivilegeForKordApp(Progetti, Pers2)
  boolean canViewAll = userIsPers1InProgetti or userIsPers2InProgetti
   
//  // filtering of progetti records based on visiblity done in two ways, later we decided which way to go
//  // One way: query fired in loop + another query is fired to find out project interface in case user is not pers1/pers2 or project respoinsible
//  for each row (readonly)
//  {
//    select
//      idProgetto = IDPROGETTO
//      idUtenteProgettoResponsabile = IDUTENTERESPONSABILE
//    from 
//      Progetti // master table
//     
//    boolean IdProgettoToBeConsidered = false
//    if (canViewAll)
//    {
//      // filter1A : filter based on pers1 or pers2
//      IdProgettoToBeConsidered = true
//    }
//    else 
//    {
//      if (idUtenteProgettoResponsabile == Utente.IDUTENTE)
//      {
//         // filter1B : filter based on project responsible
//         IdProgettoToBeConsidered = true
//      }
//      else 
//      {
//         // filter1C : filter based on project interface
//         int idUtenteInterfacce = 0
//         select into variables (found variable)
//           set idUtenteInterfacce = IDUTENTE
//         from 
//           VPRGINTERFACCE // master table
//         where
//           IDPROGETTO == idProgetto and IDUTENTE == Utente.IDUTENTE
//          
//         if (idUtenteInterfacce > 0)
//         {
//           IdProgettoToBeConsidered = true
//         }
//      }
//       
//    }
//    if (IdProgettoToBeConsidered)
//    {
//      allVisibleIdProgetto.add(IntDataType.createInteger(idProgetto))
//    }
//  }
   
  // another way: query fired in loop to filter visible project records considering all join, typical c/s way
  for each row (readonly)
  {
    select
      IDPROGETTO = IDPROGETTO
    from 
      Progetti // master table
    where
      (canViewAll == true or IDUTENTERESPONSABILE == Utente.IDUTENTE or exists(subquery))
         select top 1 // 
           IDPROGETTO
         from 
           VPRGINTERFACCE // master table
         where
           VPRGINTERFACCE.IDPROGETTO == Progetti.IDPROGETTO
           VPRGINTERFACCE.IDUTENTE == Utente.IDUTENTE
     
    allVisibleIdProgetto.add(IntDataType.createInteger(IDPROGETTO))
     
  }
   
  return allVisibleIdProgetto
}


// ──────────────────────────────────

private IDCollection UtenteVisibility.getResultForIndicatori()
{
  IDCollection allVisibleIdIndicators of IntDataType = new()
   
  // query based filter of all visible indicators
   
  for each row (readonly)
  {
    select
      IdIndicator = IdIndicator
    from 
      INDICATORS // master table
     
    allVisibleIdIndicators.add(IntDataType.createInteger(IdIndicator))
  }
   
  return allVisibleIdIndicators
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static string CookieHelper.encodeUserIdForCookie(
  int idUtente // 
)
{
  return base64Encode("QUALIBUS_UID:" + toString(idUtente))
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static int CookieHelper.DecodeUserIdFromCookie(
  string encoded // 
)
{
  int result = 0
  if (encoded != "")
  {
    string decocded = base64Decode(encoded)
    if (find(decocded, "QUALIBUS_UID:", ...) > 0)
    {
      int decodedIdUtente = toInteger(replace(decocded, "QUALIBUS_UID:", ""))
      result = decodedIdUtente
    }
  }
  return result
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static void CookieHelper.SaveLastLoggedUsrIdCookie(
  int idUtente // 
)
{
  QappCore.saveSetting("qualibus", "lastloggeduserid", CookieHelper.encodeUserIdForCookie(idUtente))
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static int CookieHelper.ReadLastLoggedUserIdFromCookie()
{
  string retrievedCookie = CookieHelper.safelyReadCookies("qualibus", "lastloggeduserid")
  string encoded = retrievedCookie
   
  return CookieHelper.DecodeUserIdFromCookie(encoded)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static void CookieHelper.savePendingCommand(
  string command      // 
  Utente utente       // 
  int:kordapp kordapp // 
  int mainId          // 
)
{
  QappCore.saveSetting("qualibus", "pendingcmd_command", command)
   
  LoginToken lt = LoginToken.create(utente)
  lt.saveToDB(...)
   
  QappCore.saveSetting("qualibus", "pendingcmd_token", lt.VALUE)
  QappCore.saveSetting("qualibus", "pendingcmd_kordapp", toString(kordapp))
  QappCore.saveSetting("qualibus", "pendingcmd_mainid", toString(mainId))
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static IDMap CookieHelper.readPendingCommand()
{
  IDMap result = null
  string retrievedCookie = this.safelyReadCookies("qualibus", "pendingcmd_command")
  string command = retrievedCookie
  if (command != "")
  {
    result = new()
    result.setValue("command", command)
    result.setValue("token", this.safelyReadCookies("qualibus", "pendingcmd_token"))
    result.setValue("kordapp", this.safelyReadCookies("qualibus", "pendingcmd_kordapp"))
    result.setValue("mainid", this.safelyReadCookies("qualibus", "pendingcmd_mainid"))
  }
   
  return result
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static void CookieHelper.clearPendingCommand()
{
  QappCore.saveSetting("qualibus", "pendingcmd_command", "")
  QappCore.saveSetting("qualibus", "pendingcmd_token", "")
  QappCore.saveSetting("qualibus", "pendingcmd_kordapp", "")
  QappCore.saveSetting("qualibus", "pendingcmd_mainid", "")
}


// ──────────────────────────────────

// *************************************************************************************************************************************************************
// makes sure only one value is returned when reading cookies
// since we want one only value given a section in case multiple values are found getSetting returns comma separated values, so this method removes this proble,
// NOTE: this might occur when cookie multiplicate on a dev machine because an app is renamed
// beter to always use this method instead of getSetting when reading a cookie for which we expect a single value is stored
// 
// the parameters are the same of getSetting (section and key)
// *************************************************************************************************************************************************************
public static string CookieHelper.safelyReadCookies(
  string section // 
  string key     // 
)
{
  string retrievedValue = QappCore.getSetting(section, key)
  string firstValue = SH.leftUpToDelimiter(retrievedValue, ",", ...)
  return firstValue
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static EventoAfterSaveSilentHookFirerer EventoAfterSaveSilentHookFirerer.create(
  Evento evento                        // 
  optional boolean simulateFailure = 0 // 
)
{
  EventoAfterSaveSilentHookFirerer ehf = new()
  ehf.setEvento(evento)
  ehf.SimulateFailure = simulateFailure
  return ehf
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void EventoAfterSaveSilentHookFirerer.setEvento(
  Evento evento // 
)
{
  Evento = evento
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string EventoAfterSaveSilentHookFirerer.callApi(
  string commandUrl // 
)
{
  boolean simulateFailure = SimulateFailure
  string token = ""
  int idEvento = Evento.IDEVENTO
   
  if (!(simulateFailure))
  {
    LoginToken lt = LoginToken.create(QappCore.Loggeduser)
    lt.saveToDB(...)
    token = lt.VALUE
  }
  else 
  {
    token = "DUMMY"
  }
   
  string commandUrlReadyToBeCalled = formatMessage(commandUrl, idEvento, token, ...)
   
  IDMap headers = new()
//  headers.setValue("ID_TYPE", "ForwardCommand")
  headers.setValue("ID_HEADERS", "X-HTTP-Method-Override: ForwardCommand")
   
  string response = ""
  boolean errorInApi = false
  try 
  {
    if (!(SimulateFailure))
    {
      response = getHTTP(commandUrlReadyToBeCalled, headers, ...)
    }
  }
  catch 
  {
    errorInApi = true
  }
  if (errorInApi or simulateFailure)
  {
    response = formatMessage("{"message":"Errore nella chiamata |1"}", commandUrlReadyToBeCalled, ...)
  }
   
   
  QappCore.DTTLogMessage(formatMessage("API Response: |1", response, ...), ...)
  return response
}


// ──────────────────────────────────

// **************************************************************************************************************************************
// returns info on the api calls that Evento from web should call, an array is returned, each element represents one command to be called
// 
// every array element is a map with all we need to compose an API Call: 
// the cMD string that identifies a Command
// the base url of the qapp
// 
// **************************************************************************************************************************************
public IDArray EventoAfterSaveSilentHookFirerer.getInfoOnApiCallsToBeDone()
{
   
  IDArray ida = new()
   
  string idClasseAsString = toString(Evento.IDCLASSE)
   
  string filterConfitionIdClasseAlone = "%" + idClasseAsString + "%"
  string filterConfitionIdClasseWithSemiColonBefore = "%;" + idClasseAsString + "%"
  string filterConditionIdClasseWithSemiColonAfter = "%" + idClasseAsString + ";%"
   
  int loggedUserIdUtente = QappCore.Loggeduser.IDUTENTE
  for each row (readonly)
  {
    select
      IDCOMMAND = NGTQAPPSCOMMANDS.IDQAPPCOMMAND
      IDAPPLICAZIONE = NGTAPPLICAZIONI.IDAPPLICAZIONE
    from 
      NGTQAPPSCOMMANDS // master table
      NGTAPPLICAZIONI  // joined with NGT QAPPS COMMANDS using key FK NGT QAPPS COMMANDS NGT APPLICAZIONI
    where
      NGTQAPPSCOMMANDS.CMDPARAMS like "%"destination":"CH"%"
      NGTQAPPSCOMMANDS.CMDPARAMS like "%"customHook":"AfterSave"%"
      NGTQAPPSCOMMANDS.CMDPARAMS like "%"popup":false%"
      NGTQAPPSCOMMANDS.CMDPARAMS like "%"executeAlways":true%" or exists(subquery)
         select top 1 // 
           PRIVEXECUTE
         from 
           NGTPRIVILEGI // master table
         where
           NGTPRIVILEGI.IDUTENTE == loggedUserIdUtente
           NGTPRIVILEGI.PRIVEXECUTE == Yes
           NGTPRIVILEGI.IDAPPLICAZIONE == NGTQAPPSCOMMANDS.IDAPPLICAZIONE
//          
      NGTQAPPSCOMMANDS.CMDPARAMS like "%"supportedApp":"qualibus"%" or NGTQAPPSCOMMANDS.CMDPARAMS like "%"supportedApp":"both"%"
      NGTQAPPSCOMMANDS.KORDID == Eventi
      NGTQAPPSCOMMANDS.ACTIVE == Yes
      NGTQAPPSCOMMANDS.FILTER like filterConfitionIdClasseWithSemiColonBefore or NGTQAPPSCOMMANDS.FILTER like filterConditionIdClasseWithSemiColonAfter or NGTQAPPSCOMMANDS.FILTER == "" or NGTQAPPSCOMM­
         ANDS.FILTER like filterConfitionIdClasseAlone
     
    int idCommand = toInteger(IDCOMMAND)
     
    boolean userHasEnoughPrivilegesToExecuteCommand = this.userHasEnoughPrivilegesToExecuteTheCommand(idCommand, loggedUserIdUtente)
    if (!(userHasEnoughPrivilegesToExecuteCommand))
    {
      continue 
    }
     
    QappCommand qc = new()
    qc.IDQAPPCOMMAND = idCommand
    qc.loadFromDB(...)
    string commandUrl = qc.getCommandUrl("|1", "|2", "N")
     
    ida.addValue(commandUrl)
  }
   
   
  return ida
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void EventoAfterSaveSilentHookFirerer.perform(
  inout string qappResponses // 
)
{
  IDArray ida = this.getInfoOnApiCallsToBeDone()
   
  string concatenatedMessage = ""
  for (int i = 0; i < ida.length(); i = i + 1)
  {
    string commandUrl = ida.getValue(i)
    string response = this.callApi(commandUrl)
     
    if (response != "")
    {
      IDMap responseMap = cast(JSON.parse(response))
      string messageText = responseMap.getValue("message")
      concatenatedMessage = SH.Concat(concatenatedMessage, messageText, "; ")
    }
  }
   
  string additionalInfo = this.prepareUIMessage(concatenatedMessage)
  qappResponses = additionalInfo
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private string EventoAfterSaveSilentHookFirerer.prepareUIMessage(
  string concatenetedMessage // 
)
{
  string resultString = ""
  if (!(QappCore.Loggeduser.isAnonymous()) and concatenetedMessage != "")
  {
    resultString = formatMessage("(info: |1)", concatenetedMessage, ...)
  }
   
  return resultString
}


// ──────────────────────────────────

// ******************************************************************************************************************************************************
// it checks if the user passed has parameter has enough privileges to execute the QappCommand
// 
// if the command requires None it returns true
// it the command requires PersN and user has not PersN it returns false
// 
// (this method does not check for execute privilege, because it was designed to encapsulate a logic that is fired after checking for execute privilege!)
// ******************************************************************************************************************************************************
private boolean EventoAfterSaveSilentHookFirerer.userHasEnoughPrivilegesToExecuteTheCommand(
  int idCommand // 
  int idUtente  // 
)
{
  DevTools.RefactoringOpportunity("This method could be moved to utente class, being so much query based it is worth refactoring")
   
  boolean userHasEnoughPrivilegesForCurrentCommand = false
   
  // retrieve idApplicazione of the Command
  int idApplicazione = 0
  select into variables (found variable)
    set idApplicazione = IDAPPLICAZIONE
  from 
    NGTQAPPSCOMMANDS // master table
  where
    IDQAPPCOMMAND == idCommand
   
   
  string:neededPrivilegeTypes neededPrivilegeForCurrentCommand = ""
  select into variables (found variable)
    set neededPrivilegeForCurrentCommand = NEEDEDPRIVILEGE
  from 
    NGTQAPPSCOMMANDS // master table
  where
    IDQAPPCOMMAND == idCommand
   
  // if the command requries a specific privilege we check that the user has it
  if (neededPrivilegeForCurrentCommand != NeededPrivilegeNone)
  {
    boolean pers1IsNeeded = neededPrivilegeForCurrentCommand == NeededPrivilegePers1
    boolean pers2IsNeeded = neededPrivilegeForCurrentCommand == NeededPrivilegePers2
    boolean pers3IsNeeded = neededPrivilegeForCurrentCommand == NeededPrivilegePers3
    boolean pers4IsNeeded = neededPrivilegeForCurrentCommand == NeededPrivilegePers4
    boolean pers5IsNeeded = neededPrivilegeForCurrentCommand == NeededPrivilegePers5
     
    int numberOfFoundRecords = 0
    //  
    // Pers1
    select into variables (found variable)
      set numberOfFoundRecords = count(...)
    from 
      NGTPRIVILEGI // master table
    where
      IDUTENTE == idUtente
      (PRIVPERS1 == Yes and (-1 == pers1IsNeeded)) or PRIVPERS2 == Yes and (-1 == pers2IsNeeded) or PRIVPERS3 == Yes and (-1 == pers3IsNeeded) or PRIVPERS4 == Yes and (-1 == pers4IsNeeded) or
         PRIVPERS5 == Yes and (-1 == pers5IsNeeded)
      IDAPPLICAZIONE == idApplicazione
     
     
     
    if (numberOfFoundRecords == 0)
    {
      userHasEnoughPrivilegesForCurrentCommand = false
    }
    else 
    {
      userHasEnoughPrivilegesForCurrentCommand = true
    }
  }
  else 
  {
    userHasEnoughPrivilegesForCurrentCommand = true
  }
  return userHasEnoughPrivilegesForCurrentCommand
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static void UIParameters.performUiCustomizationWithJavascript()
{
  QappCore.executeOnClient("*attachHomeLink();")
}


// ──────────────────────────────────

// ***************************************
// retrieves the user selected date format
// ***************************************
public static string UIParameters.getDateFormat()
{
  DevTools.ToBeReviewed("this should be a user preference, for now it is hardcoded here toa void to write constants in the code")
  return "dd/mm/yyyy"
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public static void PanelUiTools.moveToLastRow(
  IDPanel idPanel // 
)
{
  int rowCount = idPanel.totalRows()
  if (rowCount > 0)
  {
    idPanel.activeRow = rowCount
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static Diagram Diagram.create(
  ModelloEvento modelloEvento // Write a comment for this parameter or press backspace to delete this comment
)
{
  Diagram dv = new()
   
  dv.ModelloEvento = (ModelloEvento)modelloEvento
   
  // we load the diagram collection (DG STEP and DG FLOW directly from db because we pass the modelloEvento.WKF_STEP and modelloEvento.WKF_FLOW to Diagram collections is inconsistent and returns many errors.
  dv.loadNeededWorkflowCollectionsFromDb(modelloEvento)
  return dv
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Diagram.loadNeededWorkflowCollectionsFromDb(
  ModelloEvento modelloEvento // 
)
{
  IDCollection flowCollection of WkfFlow = new()
  select into collection (flowCollection)
    set IDFLOW = IDFLOW
    set IDTEMPLATEEVENTO = IDTEMPLATEEVENTO
    set IDEVENTO = IDEVENTO
    set IDSTEP = IDSTEP
    set IDRESULT = IDRESULT
    set IDNEXTSTEP = IDNEXTSTEP
    set DELAYDAYS = DELAYDAYS
    set IDGRAVITA = IDGRAVITA
    set SEQUENCE = SEQUENCE
    set ISSTART = ISSTART
    set ISSTOP = ISSTOP
    set ISDATEEDITABLE = ISDATEEDITABLE
    set DATECALCULATIONTYPE = DATECALCULATIONTYPE
    set CLOSURERESULTTYPE = CLOSURERESULTTYPE
    set IdEventoPublicStato = IdEventoPublicStato
    set PUBLICSTATUSSELECTIONTYPE = PUBLICSTATUSSELECTIONTYPE
    set POINTS = POINTS
    set FROMPOINTINTEX = FROMPOINTINTEX
    set TOPOINTINTEX = TOPOINTINTEX
  from 
    WKFFLOW // master table
  where
    IDTEMPLATEEVENTO = modelloEvento.IDTEMPLATEEVENTO
  DGFLOW.clear()
  DGFLOW.addAll(flowCollection, true)
  DGFLOW.setOriginal()
  QappCore.DTTLogMessage(DGFLOW.saveToXML(false, null, 0, JSON, ...), 852, ...)
   
  IDCollection stepCollection of WkfStep = new()
  select into collection (stepCollection)
    set IDSTEP = IDSTEP
    set IDTEMPLATEEVENTO = IDTEMPLATEEVENTO
    set IDEVENTO = IDEVENTO
    set STEPDESCRIPTION = STEPDESCRIPTION
    set IDTIPOATTIVITA = IDTIPOATTIVITA
    set IDDIPENDENTE = IDDIPENDENTE
    set NOTIFYRESPONSIBLE = NOTIFYRESPONSIBLE
    set NOTIFYEXECUTOR = NOTIFYEXECUTOR
    set NOTIFYOTHERS = NOTIFYOTHERS
    set NOTIFYINADVANCE = NOTIFYINADVANCE
    set NOTIFYADVANCEDAYS = NOTIFYADVANCEDAYS
    set NOTIFYONEXECUTION = NOTIFYONEXECUTION
    set NOTIFYONCLOSE = NOTIFYONCLOSE
    set NOTIFYDELAYS = NOTIFYDELAYS
    set NOTIFYDELAYSDAYS = NOTIFYDELAYSDAYS
    set ISFUNCTION = ISFUNCTION
    set BLOCKTYPE = BLOCKTYPE
    set ORARIOINIZIO = ORARIOINIZIO
    set ORARIOFINE = ORARIOFINE
    set NOTIFICA = NOTIFICA
    set NOTIFYINSUSER = NOTIFYINSUSER
    set NOTIFYDELAYSCONTINUE = NOTIFYDELAYSCONTINUE
    set NOTIFYDELAYSCONTINUEDAYS = NOTIFYDELAYSCONTINUEDAYS
    set EXECUTORTYPE = EXECUTORTYPE
    set MULTIPLE = MULTIPLE
    set NOTIFYPERREFERENCE = NOTIFYPERREFERENCE
    set NOTIFYCLIFORREFERENCE = NOTIFYCLIFORREFERENCE
    set WIDTH = WIDTH
    set HEIGHT = HEIGHT
    set X = X
    set Y = Y
  from 
    WKFSTEPS // master table
  where
    IDTEMPLATEEVENTO = modelloEvento.IDTEMPLATEEVENTO
   
  DGSTEPS.clear()
  DGSTEPS.addAll(stepCollection, true)
  DGSTEPS.setOriginal()
  QappCore.DTTLogMessage(DGSTEPS.saveToXML(false, null, 0, JSON, ...), 852, ...)
   
   
  for each WkfStep step in DGSTEPS
  {
    IDCollection stepResults of WkfResult = new()
    select into collection (stepResults)
      set IDRESULT = IDRESULT
      set RESULTDESCRIPTION = RESULTDESCRIPTION
      set IDSTEP = IDSTEP
      set IDTEMPLATEEVENTO = IDTEMPLATEEVENTO
    from 
      WKFRESULTS // master table
    where
      IDSTEP == step.IDSTEP
     
    step.WKFRESULTS.clear()
    step.WKFRESULTS.addAll(stepResults, true)
     
    for each WkfResult wr in step.WKFRESULTS
    {
      wr.computeAdditionalProperties()
    }
     
    step.WKFRESULTS.setOriginal()
     
    IDCollection disposizioniStep of MultipleStepTemplateDisposizione = new()
    select into collection (disposizioniStep)
      set IDMultipleStepTemplateDisposizione = IDEVATEMPLSTEPATTIVITA
      set IDTEMPLATEEVENTO = IDTEMPLATEEVENTO
      set IDSTEP = IDSTEP
      set SEQ = SEQ
      set DELAYDAYS = DELAYDAYS
      set IDEXECUTORDIPENDENTE = IDEXECUTORDIPENDENTE
      set ISFUNCTION = ISFUNCTION
      set STARTTIME = STARTTIME
      set ENDTIME = ENDTIME
      set DESCRIPTION = DESCRIPTION
    from 
      MultipleStepTemplateDisposizioni // master table
    where
      IDSTEP == step.IDSTEP
     
    step.Disposizioni.clear()
    step.Disposizioni.addAll(disposizioniStep, true)
    step.Disposizioni.setOriginal()
     
    step.ChecklistTemplate = Checklist.factory(WorkflowStep, step, ...)
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean Diagram.isStepOnDiagram(
  WkfStep step // 
)
{
  boolean stepIsPartOfDiagram = false
   
  if (step == null)
  {
    return false
  }
   
  for each WkfStep ws in DGSTEPS
  {
    if (ws.IDSTEP == step.IDSTEP)
    {
      stepIsPartOfDiagram = true
    }
  }
   
  return stepIsPartOfDiagram
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public WkfStep Diagram.getStepFromDiagram(
  int idWkfStep // 
)
{
  if (isNull(idWkfStep) || idWkfStep == 0)
  {
    QappCore.DTTLogMessage("Step ID not valid", ..., DTTWarning)
    return null
  }
   
  if (DGSTEPS == null || DGSTEPS.count() == 0)
  {
    QappCore.DTTLogMessage("DGSTEPS is empty or null", ..., DTTWarning)
    return null
  }
   
  WkfStep foundWkfStep = null
  if (DGSTEPS == null || DGSTEPS.count() == 0)
  {
    QappCore.DTTLogMessage("DGSTEP doesn't contains STEPS", ..., DTTWarning)
    return null
  }
   
  for each WkfStep ws in DGSTEPS
  {
    QappCore.DTTLogMessage("IdStep: " + toString(ws.IDSTEP), 90909090, DTTInfo)
    if (ws.IDSTEP == idWkfStep)
    {
      foundWkfStep = ws
      break 
    }
  }
   
  if (foundWkfStep == null)
  {
    QappCore.DTTLogMessage(formatMessage("Step |1 not found on diagram", idWkfStep, ...), ..., DTTWarning)
    return null
  }
   
  // needed properties
  if (foundWkfStep.ORARIOINIZIO > 0)
    foundWkfStep.StartTime = Tools.FloatToTime(foundWkfStep.ORARIOINIZIO)
   
  if (foundWkfStep.ORARIOFINE > 0)
    foundWkfStep.EndTime = Tools.FloatToTime(foundWkfStep.ORARIOFINE)
   
  return foundWkfStep
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public WkfStep Diagram.getStartStep()
{
   
  for each WkfStep ws in DGSTEPS
  {
    if (ws.isStartStep())
    {
      return ws
    }
  }
   
  return null
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public WkfStep Diagram.getStopStep()
{
  for each WkfStep ws in DGSTEPS
  {
    if (ws.isStopStep())
    {
      return ws
    }
  }
   
  return null
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public IDCollection Diagram.getNextSteps(
  WkfStep step // 
)
{
  IDCollection nextSteps of WkfStep = new()
   
  for each WkfFlow flow in DGFLOW
  {
    if (flow.IDSTEP == step.IDSTEP)
    {
      WkfStep retrievedStep = this.getStepFromDiagram(flow.IDNEXTSTEP)
      nextSteps.addRef(retrievedStep)
    }
  }
   
  return nextSteps
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public IDCollection Diagram.getPreviousSteps(
  WkfStep step // 
)
{
  IDCollection previousSteps of WkfStep = new()
   
  for each WkfFlow flow in DGFLOW
  {
    if (flow.IDNEXTSTEP == step.IDSTEP)
    {
      WkfStep retrievedStep = this.getStepFromDiagram(flow.IDSTEP)
      previousSteps.addRef(retrievedStep)
    }
  }
   
  return previousSteps
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public WkfStep Diagram.createStepForDiagram(
  string description                                      // 
  optional string:stepTypes type = "A"                    // 
  optional string:workflowStepExecutorType executor = "R" // 
  optional int idTipoAttivita = 0                         // 
)
{
  WkfStep ws = WkfStep.create(this, description, type, executor, idTipoAttivita)
  return ws
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean Diagram.addStep(
  WkfStep stepToBeAdded // Write a comment for this parameter or press backspace to delete this comment
  IDArray errorsArray   // 
)
{
  if (stepToBeAdded == null)
  {
    errorsArray.addValue("Non si può aggiungere uno step nullo.")
    return false
  }
   
  boolean isValid = this.ValidateStep(stepToBeAdded, true)
   
  if (!(isValid))
    return false
   
  for each WkfStep ws in DGSTEPS
  {
    if (ws.IDSTEP == stepToBeAdded.IDSTEP)
    {
      errorsArray.addValue("Lo STEP è già presente nel diagramma.")
      return false
    }
  }
   
  if (stepToBeAdded.Errors.length() > 0)
  {
    return false
  }
   
  DGSTEPS.add(stepToBeAdded)
   
  return true
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean Diagram.removeStep(
  WkfStep step   // Write a comment for this parameter or press backspace to delete this comment
  IDArray errors // 
)
{
  if (!(this.isStepOnDiagram(step)))
  {
    errors.addValue("Lo step selezionato non fa parte del diagramma")
    return false
  }
   
//  // because I can't block the JS to remove Start and Stop
//  if (step.isStartStep() or step.isStopStep())
//    errors.addValue("Non si possono eliminare lo START e lo STOP")
   
  if (errors.length() > 0)
    return false
   
   
   
  IDCollection coll of WkfFlow = this.getFlowsOfSpecificStep(step)
  for each WkfFlow wf in coll
  {
    wf.deleted = true
  }
  step.deleted = true
   
  return true
}


// ──────────────────────────────────

// ************************************************
// Return the IDRESULT of the newly added WkfResult
// ************************************************
public WkfResult Diagram.addEsitoToStep(
  WkfStep step   // Write a comment for this parameter or press backspace to delete this comment
  IDArray errors // 
)
{
  if (errors == null)
    errors = new()
   
  if (!(this.isStepOnDiagram(step)))
  {
    errors.addValue("Non puoi aggiungere un esito a uno step che non esiste")
    return null
  }
   
  if (step.isStartStep() or step.isStopStep())
  {
    errors.addValue("Non si può aggiungere un esito ad uno START o STOP")
    return null
  }
   
  WkfResult newResult = step.AddEmptyWkfResult()
  return newResult
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean Diagram.RemoveEsitoFromStep(
  WkfStep step     // 
  WkfResult result // 
  IDArray errors   // 
)
{
  if (!(this.isStepOnDiagram(step)))
  {
    errors.addValue("Step non fa parte del diagramma")
    return false
  }
   
  if (result == null)
  {
    errors.addValue("Esito da cancellare non valido")
    return false
  }
   
  if (this.esitoIsUsedByDisposizioniInAnyEvento(result))
  {
    errors.addValue("Non è possibile eliminare questo esito in quanto è già stato utilizzato.<br>E' necessario eliminare il collegamento dal workflow prima di procedere con la cancellazione.")
    return false
  }
   
  return step.RemoveWkfResult(result.IDRESULT, errors)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean Diagram.RemoveDisposizioneFromStep(
  WkfStep step                                  // Write a comment for this parameter or press backspace to delete this comment
  MultipleStepTemplateDisposizione disposizione // 
  IDArray errors                                // 
)
{
//  if (this.isStepOnDiagram(step))
//  {
//    errors.addValue("Non posso rimuovere una disposizione allo step selezionato")
//    return false
//  }
   
  if (disposizione == null)
  {
    errors.addValue("Disposizione da cancellare non valida")
    return false
  }
   
  boolean removed = step.RemoveDisposizione(disposizione, errors)
   
  return removed
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public IDCollection Diagram.getNonAssignedResultsOfStep(
  WkfStep step                                           // step to check
  optional WkfFlow existingFlowWhoseResultToBeConsidered // when you reopen for editing a flow, you want to display the preview selected option. To do so, we pass the wkflow to give the algoritm to set the r...
)
{
  // always check if an object is null
  if (step == null)
  {
    return null
  }
   
  // to check we use the logic of removing from existing and at the end check what remains
  IDCollection notConnectedResults of WkfResult = new()
   
  notConnectedResults.addAll(step.WKFRESULTS, false)
  QappCore.DTTLogMessage(formatMessage("Result to check for step |1: |2", step.STEPDESCRIPTION, toString(notConnectedResults.count()), ...), ...)
   
  for each WkfFlow flow in DGFLOW
  {
    if (step.IDSTEP == flow.IDSTEP)
    {
      notConnectedResults.moveFirst()
      while (!(notConnectedResults.isEof()))
      {
         WkfResult wr = (WkfResult)notConnectedResults.getAt()
         if (wr.IDRESULT == flow.IDRESULT)
         {
           if (existingFlowWhoseResultToBeConsidered != null && existingFlowWhoseResultToBeConsidered.IDRESULT > 0)
           {
             boolean deleteResult = true
              
             // if is the flow to be excluded, just skip and leave in notConnectedResults
             if (existingFlowWhoseResultToBeConsidered.IDRESULT == flow.IDRESULT)
             {
                deleteResult = false
             }
              
             if (deleteResult)
                notConnectedResults.removeAt()
           }
           else 
           {
             notConnectedResults.removeAt()
           }
         }
         notConnectedResults.moveNext()
      }
    }
  }
   
  QappCore.DTTLogMessage(formatMessage("Not connected results: |1", toString(notConnectedResults.count()), ...), ...)
   
  return notConnectedResults
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public WkfResult Diagram.getSpecificResultInStep(
  WkfStep step // 
  int idResult // 
)
{
  if (step != null and idResult > 0)
  {
    if (this.isStepOnDiagram(step))
    {
      for each WkfResult wr in step.WKFRESULTS
      {
         if (wr.IDRESULT == idResult)
         {
           return wr
         }
      }
    }
  }
   
  return null
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean Diagram.esitoIsUsedByDisposizioniInAnyEvento(
  WkfResult esito // 
)
{
  boolean esitoIsUsed = false
  int count = 0
   
  // check if the result has attività collegate
  select into variables (found variable)
    set count = count(...)
  from 
    Disposizioni // master table
  where
    IDRESULT == esito.IDRESULT
   
  if (count > 0)
  {
    esitoIsUsed = true
  }
   
  return esitoIsUsed
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public WkfResult Diagram.GetResultFromId(
  int idResult // 
)
{
  if (idResult <= 0)
    return null
   
  if (DGSTEPS == null)
    return null
   
  for each WkfStep ws in DGSTEPS
  {
    if (ws == null || ws.WKFRESULTS == null)
      continue 
     
    for each WkfResult wr in ws.WKFRESULTS
    {
      if (wr = null)
         continue 
       
      if (wr.IDRESULT == idResult)
      {
         return wr
      }
    }
  }
   
  return null
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean Diagram.removeFlow(
  WkfFlow flow   // Write a comment for this parameter or press backspace to delete this comment
  IDArray errors // 
)
{
  if (errors == null)
    errors = new()
   
   
  if (!(this.isFlowOnDiagram(flow)))
  {
    errors.addValue("Il collegamento che stai cancellando non fa parte del diagramma")
    return false
  }
   
  flow.deleted = true
  return true
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public WkfFlow Diagram.addFlow(
  WkfStep previousStep      // Write a comment for this parameter or press backspace to delete this comment
  WkfStep nextStep          // 
  IDArray errors            // 
  inout boolean needResult  // 
  optional WkfResult result // 
)
{
  if (errors == null)
    errors = new()
   
  needResult = false
   
  if (previousStep != null and !(previousStep.isStartStep()))
  {
    IDCollection coll of WkfResult = this.getNonAssignedResultsOfStep(previousStep, ...)
     
    if (coll == null or coll.count() == 0)
    {
      errors.addValue("E' necessario definire un esito.")
      return null
    }
     
    if (coll.count() == 1)
    {
      coll.moveFirst()
      result = (WkfResult)coll.getAt()
    }
     
    if (coll.count() > 1)
    {
      if (result == null)
      {
         needResult = true
         return null
      }
      else 
      {
         boolean found = false
         for each WkfResult wr in coll
         {
           if (wr.IDRESULT == result.IDRESULT)
           {
             found = true
           }
         }
         if (found == false)
         {
           errors.addValue("L'esito non è valido")
           return null
         }
      }
    }
  }
   
  boolean isValid = this.ValidateFlow(previousStep, nextStep, errors, ..., true)
   
  if (isValid)
  {
    if (previousStep.isStartStep())
    {
      result = null
    }
    WkfFlow wf = WkfFlow.create(ModelloEvento, previousStep, nextStep, result)
    this.AddFlowToCollection(wf)
     
    return wf
  }
   
  return null
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public IDCollection Diagram.getFlowsOfSpecificStep(
  WkfStep step // 
)
{
  IDCollection coll of WkfFlow = new()
  for each WkfFlow wf in DGFLOW
  {
    if (wf.IDSTEP == step.IDSTEP)
    {
      coll.addRef(wf)
      continue 
    }
    if (wf.IDNEXTSTEP == step.IDSTEP)
    {
      coll.addRef(wf)
    }
  }
  return coll
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public WkfFlow Diagram.getFlowFromDiagram(
  int idFlow // 
)
{
  for each WkfFlow wf in DGFLOW
  {
    if (wf.IDFLOW == idFlow)
    {
      return wf
    }
  }
  return null
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean Diagram.isFlowOnDiagram(
  WkfFlow flow // 
)
{
  if (flow == null)
    return false
   
   
  IDCollection coll of WkfFlow = DGFLOW
  for each WkfFlow wf in coll
  {
    if (wf.IDSTEP == flow.IDSTEP)
    {
      return true
    }
  }
   
  return false
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void Diagram.updateFlow(
  WkfFlow wkfFlow // 
)
{
  for each WkfFlow wf in DGFLOW
  {
    if (wf.IDFLOW == wkfFlow.IDFLOW)
    {
      wf.IDRESULT = wkfFlow.IDRESULT
      wf.DELAYDAYS = wkfFlow.DELAYDAYS
      wf.IDGRAVITA = wkfFlow.IDGRAVITA
      wf.ISDATEEDITABLE = wkfFlow.ISDATEEDITABLE
      wf.ISDATEEDITABLE = wkfFlow.ISDATEEDITABLE
      wf.CLOSURERESULTTYPE = wkfFlow.CLOSURERESULTTYPE
      wf.IdEventoPublicStato = wkfFlow.IdEventoPublicStato
      wf.PUBLICSTATUSSELECTIONTYPE = wkfFlow.PUBLICSTATUSSELECTIONTYPE
      wf.POINTS = wkfFlow.POINTS
//      wf = wkfFlow
    }
  }
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public WkfFlow Diagram.GetFlowForSpecificWkfResult(
  int idResult // 
)
{
  if (idResult <= 0)
  {
    return null
  }
   
  for each WkfFlow wf in DGFLOW
  {
    if (wf.IDRESULT == idResult)
    {
      return wf
    }
  }
   
  return null
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public void Diagram.AddFlowToCollection(
  WkfFlow flow // Scrivi un commento per questo parametro o premi backspace per eliminare questo commento
)
{
  DGFLOW.add(flow)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean Diagram.isValidDiagram(
  IDArray errors // 
)
{
  errors.clear()
   
  if (errors == null)
    errors = new()
   
   
  WkfStep startStep = this.getStartStep()
  WkfStep stopStep = this.getStopStep()
   
  boolean bothTerminalsExist = (startStep != null) and (stopStep != null)
   
  if (!(bothTerminalsExist))
  {
    errors.addValue("Start e stop devono essere entrambi presenti nel diagramma.")
  }
   
   
  for each WkfStep step in DGSTEPS
  {
    this.ValidateStep(step, ...)
    if (length(this.GetStepErrors(step)) > 0)
    {
      errors.addValue(this.GetStepErrors(step))
    }
  }
   
  QappCore.DTTLogMessage("DGFLOW count: " + toString(DGFLOW.count()), ..., DTTInfo)
  for each WkfFlow flow in DGFLOW
  {
    QappCore.DTTLogMessage(JSON.stringify(flow), ..., DTTInfo)
     
    if (flow == null)
    {
      QappCore.DTTLogMessage("Flow in DGFLOW is null", ..., DTTError)
      continue 
    }
     
    if (flow.IDSTEP == null || flow.IDSTEP == 0)
    {
      QappCore.DTTLogMessage(formatMessage("Flow id: |1 collegato a next |2 ha precedente non valido", flow.IDFLOW, flow.IDNEXTSTEP, ...), ...)
      continue 
    }
     
    if (flow.IDNEXTSTEP == null || flow.IDNEXTSTEP == 0)
    {
      QappCore.DTTLogMessage(formatMessage("Flow id: |1 collegato da |2 ha next non valido", flow.IDFLOW, flow.IDSTEP, ...), ...)
      continue 
    }
     
    WkfStep prev = this.getStepFromDiagram(flow.IDSTEP)
    WkfStep next = this.getStepFromDiagram(flow.IDNEXTSTEP)
    this.ValidateFlow(prev, next, errors, flow, ...)
  }
   
  // to identify steps not connected to anything
  boolean allStepsAreConnected = this.allStepsAreConnected(errors)
//  if (!(allStepsAreConnected))
//  {
//    errors.addValue("Non tutti gli step sono connessi.")
//  }
   
  QappCore.DTTLogMessage(formatMessage("gli errori sono: |1, conto: |2", Tools.ArrayToString(errors, ""), errors.length(), ...), ...)
   
  return errors.length() == 0
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean Diagram.allStepsAreConnected(
  IDArray errors // 
)
{
  if (errors == null)
    errors = new()
   
  int stepsWithErrors = 0
   
  for each WkfStep step in DGSTEPS
  {
    QappCore.DTTLogMessage(step.STEPDESCRIPTION, ...)
     
    // map of results not conneted. used to build the error message
    IDMap ResultsNotConnected = new()
     
    boolean currentStepIsFullConnected = false
    boolean currentStepHasIncomingConnections = false
     
    if (step.isStartStep())
    {
      for each WkfFlow wf in DGFLOW
      {
         if (wf.IDSTEP == step.IDSTEP)
         {
           currentStepIsFullConnected = true
           currentStepHasIncomingConnections = true
           break 
         }
      }
    }
    else if (step.isStopStep())
    {
      for each WkfFlow wf in DGFLOW
      {
         if (wf.IDNEXTSTEP == step.IDSTEP)
         {
           currentStepIsFullConnected = true
           currentStepHasIncomingConnections = true
           break 
         }
      }
    }
    else 
    {
      // check if every ESITO is linked with a flow
      IDCollection currentStepEsiti of WkfResult = step.WKFRESULTS
      int stepEsitiCount = currentStepEsiti.count()
      QappCore.DTTLogMessage(formatMessage(" => step '|1' esiti count: |2", step.STEPDESCRIPTION, toString(stepEsitiCount), ...), ..., DTTInfo)
       
      if (currentStepEsiti.count() == 0)
      {
         currentStepIsFullConnected = false
      }
      else 
      {
         // for step with esiti we check that every esito is used by a flow
          
         int isNextStepOfAFlow = 0
         int esitiOkCounter = 0
          
         // fill the list of result to made the missing error
         for each WkfResult wr in currentStepEsiti
         {
           ResultsNotConnected.setValue(wr.IDRESULT, wr.RESULTDESCRIPTION)
         }
          
         for each WkfResult esito in currentStepEsiti
         {
           for each WkfFlow flow in DGFLOW
           {
             if (step.IDSTEP == flow.IDSTEP and esito.IDRESULT == flow.IDRESULT)
             {
                QappCore.DTTLogMessage(formatMessage("  [ ] Prev step: |1 / Result: |2 => esiti count +1", flow.IDSTEP, flow.IDRESULT, ...), ..., DTTInfo)
                esitiOkCounter = esitiOkCounter + 1
                 
                // if a result is used, I removed from the map so it doesn't appear on the error message
                boolean found = ResultsNotConnected.containsKey(esito.IDRESULT)
                if (found)
                  ResultsNotConnected.remove(esito.IDRESULT)
                 
             }
             if (step.IDSTEP == flow.IDNEXTSTEP)
             {
                QappCore.DTTLogMessage(formatMessage("  [ ] Next step: |1 => next step count +1", flow.IDNEXTSTEP, ...), ..., DTTInfo)
                 
                isNextStepOfAFlow = isNextStepOfAFlow + 1
             }
           }
         }
          
         QappCore.DTTLogMessage(formatMessage("  [ ] step esiti count: |3/|1 / next step in a flow: |2", currentStepEsiti.count(), isNextStepOfAFlow, esitiOkCounter, ...), ..., DTTInfo)
         if (esitiOkCounter == currentStepEsiti.count())
         {
           currentStepIsFullConnected = true
         }
         if (isNextStepOfAFlow > 0)
         {
           currentStepHasIncomingConnections = true
         }
      }
    }
     
    if (!(currentStepIsFullConnected) and currentStepHasIncomingConnections)
    {
      // build the error message
      string esitiString = ""
      IDArray resultKeys = ResultsNotConnected.getKeys()
      for (int i = 0; i < resultKeys.length(); i = i + 1)
      {
         esitiString = esitiString + "<br/>" + " - '" + toString(ResultsNotConnected.getValue(resultKeys.getValue(i))) + "'"
      }
      errors.addValue(formatMessage("Non tutti gli esiti dello step <strong>'|1'</strong> sono connessi: |2 |3 <br/>", step.STEPDESCRIPTION, esitiString, Tools.getNewLineChar(), ...))
       
      stepsWithErrors = stepsWithErrors + 1
    }
    else if (!(currentStepHasIncomingConnections))
    {
      errors.addValue(formatMessage("Lo step <strong>'|1'</strong> deve avere almeno una connessione in entrata e una in uscita.", step.STEPDESCRIPTION, ...))
       
      stepsWithErrors = stepsWithErrors + 1
    }
     
  }
   
  if (stepsWithErrors > 0)
  {
    return false
  }
   
  return true
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean Diagram.isStepConnectedToAMultipleStep(
  WkfStep wkfStep // 
)
{
  boolean previousOrNextStepIsMultiple = false
   
  IDCollection previousSteps of WkfStep = this.getPreviousSteps(wkfStep)
  IDCollection nextSteps of WkfStep = this.getNextSteps(wkfStep)
  IDCollection adjacentSteps of WkfStep = new()
   
  adjacentSteps.addAll(previousSteps, ...)
  adjacentSteps.addAll(nextSteps, ...)
   
  for each WkfStep ws in adjacentSteps
  {
    if (ws.MULTIPLE == Yes)
    {
      previousOrNextStepIsMultiple = true
      break 
    }
  }
   
  return previousOrNextStepIsMultiple
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean Diagram.isStepTheLastBeforeStop(
  WkfStep step // 
)
{
  boolean isTheLastBeforeStop = false
   
  for each WkfFlow flow in DGFLOW
  {
    if (step.IDSTEP == flow.IDSTEP)
    {
      WkfStep stopStep = this.getStopStep()
      if (stopStep.IDSTEP == flow.IDNEXTSTEP)
      {
         isTheLastBeforeStop = true
      }
    }
  }
   
  return isTheLastBeforeStop
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean Diagram.isStepTheFirstAfterStart(
  WkfStep step // 
)
{
  boolean isTheFirstAfterStart = false
   
  for each WkfFlow flow in DGFLOW
  {
    if (step.IDSTEP == flow.IDNEXTSTEP)
    {
      WkfStep startStep = this.getStartStep()
      if (startStep.IDSTEP == flow.IDSTEP)
      {
         isTheFirstAfterStart = true
      }
    }
  }
   
  return isTheFirstAfterStart
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean Diagram.ValidateFlow(
  WkfStep previousStep           // 
  WkfStep nextStep               // 
  IDArray errors                 // 
  optional WkfFlow existingFlow  // 
  optional boolean isNewFlow = 0 // 
)
{
  boolean isFlowValid = true
  QappCore.DTTLogMessage("Existing flow c'è", ...)
  if (errors == null)
    errors = new()
   
  // validation on create and edit
  if (previousStep == null or !(this.isStepOnDiagram(previousStep)))
  {
    errors.addValue("Lo step precedente non esiste. " + if(previousStep == null, "", toString(previousStep.IDSTEP)))
    return false
  }
   
  if (nextStep == null or !(this.isStepOnDiagram(nextStep)))
  {
    errors.addValue("Lo step successivo non esiste. " + if(nextStep == null, "", toString(nextStep.IDSTEP)))
    return false
  }
   
  if (nextStep.isStartStep())
  {
    errors.addValue("Non è possibile collegare uno step a START.")
    return false
  }
   
  if (previousStep.isStopStep())
  {
    errors.addValue("Non è possibile collegare uno step da STOP.")
    return false
  }
   
  if (previousStep.isStartStep())
  {
    IDCollection coll of WkfStep = this.getFlowsOfSpecificStep(previousStep)
    if (isNewFlow == true and coll.count() >= 1)
    {
      errors.addValue("Non è possibile collegare più di uno step da START.")
      return false
    }
    else if (isNewFlow == false and coll.count() > 1)
    {
      errors.addValue("Non è possibile collegare più di uno step da START.")
      return false
    }
  }
   
  if (previousStep.isStartStep() and nextStep.isStopStep())
  {
    errors.addValue("Non è possibile collegare START a STOP.")
    return false
  }
   
  if (previousStep.isMultiple() and nextStep.isMultiple())
  {
    errors.addValue("Non è possibile collegare più step multipli.")
    return false
  }
   
  if (previousStep.isStartStep() and nextStep.isMultiple())
  {
    errors.addValue("Il primo step di un Workflow non può essere Multiplo.")
    return false
  }
   
  if (previousStep.isMultiple() and nextStep.isStopStep())
  {
    errors.addValue("L'ultimo step di un Workflow non può essere Multiplo.")
    return false
  }
   
  // validation in edit
  if (existingFlow != null)
  {
    existingFlow.Errors.clear()
    if (!(isNewFlow) and existingFlow.IDRESULT <= 0 and existingFlow.ISSTART == No)
    {
      existingFlow.Errors.setValue(toPropertyIndex(existingFlow.IDRESULT), "E' necessario selezionare un esito.")
      errors.addValue("E' necessario selezionare un esito.")
      isFlowValid = false
    }
     
    if (existingFlow.ISSTART == Yes)
    {
      if (ModelloEvento.SUPPORTPUBLICINFO == Yes and existingFlow.PUBLICSTATUSSELECTIONTYPE == Richiedi)
      {
         existingFlow.Errors.setValue(toPropertyIndex(existingFlow.PUBLICSTATUSSELECTIONTYPE), "Lo stato pubblico deve essere impostato per il primo collegamento.")
         errors.addValue("Lo stato pubblico deve essere impostato per il primo collegamento.")
         isFlowValid = false
      }
    }
     
    if (ModelloEvento.SUPPORTPUBLICINFO == Yes and existingFlow.PUBLICSTATUSSELECTIONTYPE == ImpostaSpecifico)
    {
      if (existingFlow.IdEventoPublicStato == null or existingFlow.IdEventoPublicStato == 0)
      {
         existingFlow.Errors.setValue(toPropertyIndex(existingFlow.PUBLICSTATUSSELECTIONTYPE), "E' necessario impostare lo stato pubblico.")
         errors.addValue("E' necessario impostare lo stato pubblico.")
         isFlowValid = false
      }
    }
     
    if (!(isNewFlow))
    {
      if (existingFlow.ISSTOP == Yes)
      {
         if (existingFlow.CLOSURERESULTTYPE == ImpostaSpecifico)
         {
           if (existingFlow.IDGRAVITA == null or existingFlow.IDGRAVITA == 0)
           {
             existingFlow.Errors.setValue(toPropertyIndex(existingFlow.CLOSURERESULTTYPE), "E' necessario definire un comportamento.")
             errors.addValue("E' necessario definire un comportamento.")
             isFlowValid = false
           }
         }
      }
    }
    existingFlow.validate(...)
  }
   
  if (errors.length() > 0 && existingFlow == null)
  {
    QappCore.DTTLogMessage(formatMessage("New flow errors: |1", Tools.ArrayToString(errors, ...), ...), ...)
  }
  if (existingFlow != null && errors.length() > 0)
  {
    QappCore.DTTLogMessage(formatMessage("Flow |1 |2 errors: |3", existingFlow.IDFLOW, existingFlow.IDRESULT, Tools.ArrayToString(errors, ...), ...), ...)
  }
   
  return isFlowValid
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean Diagram.ValidateStep(
  WkfStep step                   // 
  optional boolean isNewStep = 0 // 
)
{
   
  boolean isStepValid = true
  string multipleErrors = ""
  step.Errors.clear()
   
   
  QappCore.DTTLogMessage("tested step " + toString(step.IDSTEP), ..., DTTInfo)
   
  if (isNewStep)
  {
    if (step.isStartStep())
    {
      WkfStep ws = this.getStartStep()
      if (ws != null)
      {
         step.Errors.setValue(toPropertyIndex(step.IDTIPOATTIVITA), "Lo START è già presente nel diagramma.")
         return false
      }
    }
    if (step.isStopStep())
    {
      WkfStep ws = this.getStopStep()
      if (ws != null)
      {
         step.Errors.setValue(toPropertyIndex(step.IDTIPOATTIVITA), "Lo STOP è già presente nel diagramma.")
         return false
      }
    }
  }
   
  boolean isATerminalStep = step.isStartStep() or step.isStopStep()
  if (!(isATerminalStep))
  {
    if ((isNull(step.IDTIPOATTIVITA) or step.IDTIPOATTIVITA <= 0) and !(isNewStep))
    {
      step.Errors.setValue(toPropertyIndex(step.IDTIPOATTIVITA), "E' necessario impostare un tipo di disposizione.")
      isStepValid = false
    }
     
    if (!(step.ORARIOINIZIO < step.ORARIOFINE))
    {
      step.Errors.setValue(toPropertyIndex(step.EndTime), "L'ora di inizio deve essere minore dell'ora di fine.")
      isStepValid = false
    }
     
    if (step.STEPDESCRIPTION == "")
    {
      step.Errors.setValue(toPropertyIndex(step.STEPDESCRIPTION), "La descrizione non può essere vuota.")
      isStepValid = false
    }
     
    if (step.MULTIPLE == Yes and step.EXECUTORTYPE == RifPersonale)
    {
      step.Errors.setValue(toPropertyIndex(step.EXECUTORTYPE), "Non può essere "Rif.Personale" se lo step è multiplo.")
      isStepValid = false
    }
     
    if (step.WKFRESULTS.loaded)
    {
      if (step.WKFRESULTS.count() > 1 and step.isMultiple())
      {
         multipleErrors = multipleErrors + "È possibile usare un solo esito in uno step multiplo. "
      }
    }
     
    if (step.isMultiple() and this.isStepConnectedToAMultipleStep(step))
    {
      multipleErrors = multipleErrors + "Uno step Multiplo non può essere collegato ad un altro step Multiplo. "
    }
     
    if (step.isMultiple() and this.isStepTheLastBeforeStop(step))
    {
      multipleErrors = multipleErrors + "L'ultimo step di un Workflow non può essere Multiplo. "
    }
     
     
    if (step.isMultiple() and this.isStepTheFirstAfterStart(step))
    {
      multipleErrors = multipleErrors + "Il primo step di un Workflow non può essere Multiplo. "
    }
  }
   
  if (multipleErrors != "")
  {
    step.Errors.setValue(toPropertyIndex(step.MULTIPLE), multipleErrors)
    isStepValid = false
  }
   
  step.validate(...)
   
  boolean stepResultsAreValid = step.validateResults()
  if (!(stepResultsAreValid))
  {
    isStepValid = false
    step.Errors.setValue(step.MULTIPLE, "Sono presenti errori negli esiti di questo step.")
  }
   
  if (step.Errors.length() > 0)
  {
    QappCore.DTTLogMessage(formatMessage("Step |1 |2 errors: |3", step.IDSTEP, step.STEPDESCRIPTION, multipleErrors, ...), ...)
  }
   
  return isStepValid
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string Diagram.GetStepErrors(
  WkfStep step // 
)
{
  IDArray errorsArray = new()
   
  string errorMessage = ""
   
  IDArray keysArray = step.Errors.getKeys()
  for (int i = 0; i < keysArray.length(); i = i + 1)
  {
    string propertyKey = keysArray.getValue(i)
    string error = toString(step.Errors.getValue(propertyKey))
    if (error != "")
    {
      errorsArray.addValue(error)
    }
  }
   
  if (errorsArray.length() > 0)
  {
    errorMessage = Tools.ArrayToString(errorsArray, "<br/>")
  }
   
  return errorMessage
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string Diagram.GetFlowErrors(
  WkfFlow flow // 
)
{
  IDArray errorsArray = new()
   
  string errorMessage = ""
   
  IDArray keysArray = flow.Errors.getKeys()
  for (int i = 0; i < keysArray.length(); i = i + 1)
  {
    string propertyKey = keysArray.getValue(i)
    string error = toString(flow.Errors.getValue(propertyKey))
    if (error != "")
    {
      errorsArray.addValue(error)
    }
  }
   
  if (errorsArray.length() > 0)
  {
    errorMessage = Tools.ArrayToString(errorsArray, "<br/>")
  }
   
  return errorMessage
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public string Diagram.DebugDataToConsole()
{
  string res = ""
   
  Tools.AppendMessageToNewLine(res, "###  STEPS ###")
  for each WkfStep ws in DGSTEPS
  {
    Tools.AppendMessageToNewLine(res, formatMessage("STEPID: |1 - Desc: |2", ws.IDSTEP, ws.STEPDESCRIPTION, ...))
     
    IDCollection resultColl of WkfResult = new()
    resultColl.addAll(ws.WKFRESULTS, ...)
    for each WkfResult wr in resultColl
    {
      Tools.AppendMessageToNewLine(res, formatMessage("    => RESULT - id:|1 desc:|2", wr.IDRESULT, wr.RESULTDESCRIPTION, ...))
    }
  }
   
  Tools.AppendMessageToNewLine(res, "###  FLOWS ###")
  for each WkfFlow wf in DGFLOW
  {
    Tools.AppendMessageToNewLine(res, formatMessage("FLOWID: |1 - From: |2 - To: |3", wf.IDFLOW, wf.IDSTEP, wf.IDNEXTSTEP, ...))
  }
   
  return res
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public IDCollection Diagram.GetSteps()
{
  return DGSTEPS
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public IDCollection Diagram.GetFlows()
{
  return DGFLOW
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public void Diagram.SaveDiagramCollectionsToDb()
{
  QappCore.DTTLogMessage("Saving Diagram tables to DB (DGFLOW, DGSTEPS, DGRESULTS,..", 1118, DTTInfo)
  DGSTEPS.saveToDB(999, ...)
  DGFLOW.saveToDB(999, ...)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static WkfStep WkfStep.create(
  Diagram diagram                              // Write a comment for this parameter or press backspace to delete this comment
  string description                           // Write a comment for this parameter or press backspace to delete this comment
  string:stepTypes blockType                   // 
  string:workflowStepExecutorType executorType // 
  optional int IdTipoAttivita = 0              // 
)
{
  WkfStep step = new()
  step.init()
  step.IDTEMPLATEEVENTO = diagram.ModelloEvento.IDTEMPLATEEVENTO
  step.STEPDESCRIPTION = description
  step.BLOCKTYPE = blockType
  step.EXECUTORTYPE = executorType
  if (IdTipoAttivita != 0)
    step.IDTIPOATTIVITA = IdTipoAttivita
   
  step.ChecklistTemplate = Checklist.factory(WorkflowStep, step, ...)
  step.Errors = new()
   
  return step
}


// ──────────────────────────────────

// ****************************************
// returns true if the step is a MULTI step
// ****************************************
public boolean WkfStep.isMultiple()
{
  return MULTIPLE == Yes
}


// ──────────────────────────────────



// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean WkfStep.isStopStep()
{
  return BLOCKTYPE == Stop
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public WkfResult WkfStep.AddEmptyWkfResult()
{
  WkfResult wr = WkfResult.create(this, "")
   
  if (WKFRESULTS == null)
    WKFRESULTS = new()
   
  WKFRESULTS.add(wr)
   
  return wr
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean WkfStep.RemoveWkfResult(
  int idResult   // 
  IDArray errors // 
)
{
  if (errors == null)
    errors = new()
   
  for each WkfResult wr in WKFRESULTS
  {
    if (wr.IDRESULT == idResult)
    {
      wr.deleted = true
      return true
    }
  }
   
  errors.addValue("Esito da cancellare non trovato")
  return false
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean WkfStep.RemoveDisposizione(
  MultipleStepTemplateDisposizione disposizione // Write a comment for this parameter or press backspace to delete this comment
  IDArray errors                                // Write a comment for this parameter or press backspace to delete this comment
)
{
  if (errors == null)
    errors = new()
   
  for each MultipleStepTemplateDisposizione mstd in Disposizioni
  {
    if (mstd.IDMultipleStepTemplateDisposizione == disposizione.IDMultipleStepTemplateDisposizione)
    {
      mstd.deleted = true
      return true
    }
  }
   
  errors.addValue("Disposizione da cancellare non trovata")
  return false
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public boolean WkfStep.validateResults()
{
  boolean allResultsAreValid = true
  for each WkfResult wr in WKFRESULTS
  {
    if (!(wr.validate(...)))
    {
      allResultsAreValid = false
    }
  }
   
  return allResultsAreValid
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void WkfStep.copyAllPropertiesFromStep(
  WkfStep step // Write a comment for this parameter or press backspace to delete this comment
)
{
  this.IDSTEP = step.IDSTEP
  this.IDTEMPLATEEVENTO = step.IDTEMPLATEEVENTO
  this.IDEVENTO = step.IDEVENTO
  this.STEPDESCRIPTION = step.STEPDESCRIPTION
  this.IDTIPOATTIVITA = step.IDTIPOATTIVITA
  this.IDDIPENDENTE = step.IDDIPENDENTE
  this.NOTIFYRESPONSIBLE = step.NOTIFYRESPONSIBLE
  this.NOTIFYEXECUTOR = step.NOTIFYEXECUTOR
  this.NOTIFYOTHERS = step.NOTIFYOTHERS
  this.NOTIFYINADVANCE = step.NOTIFYINADVANCE
  this.NOTIFYADVANCEDAYS = step.NOTIFYADVANCEDAYS
  this.NOTIFYONEXECUTION = step.NOTIFYONEXECUTION
  this.NOTIFYONCLOSE = step.NOTIFYONCLOSE
  this.NOTIFYDELAYS = step.NOTIFYDELAYS
  this.NOTIFYDELAYSDAYS = step.NOTIFYDELAYSDAYS
  this.ISFUNCTION = step.ISFUNCTION
  this.BLOCKTYPE = step.BLOCKTYPE
  this.ORARIOFINE = step.ORARIOFINE
  this.ORARIOINIZIO = step.ORARIOINIZIO
  this.NOTIFICA = step.NOTIFICA
  this.NOTIFYINSUSER = step.NOTIFYINSUSER
  this.NOTIFYDELAYSCONTINUE = step.NOTIFYDELAYSCONTINUE
  this.NOTIFYDELAYSCONTINUEDAYS = step.NOTIFYDELAYSCONTINUEDAYS
  this.EXECUTORTYPE = step.EXECUTORTYPE
  this.MULTIPLE = step.MULTIPLE
  this.NOTIFYINSUSER = step.NOTIFYINSUSER
  this.NOTIFYPERREFERENCE = step.NOTIFYPERREFERENCE
  this.NOTIFYCLIFORREFERENCE = step.NOTIFYCLIFORREFERENCE
  this.StartTime = step.StartTime
  this.EndTime = step.EndTime
  this.ChecklistTemplate = step.ChecklistTemplate
  this.Errors = step.Errors
   
  NTFRECIPIENTS.clear()
  NTFRECIPIENTS.addAll(step.NTFRECIPIENTS, ...)
   
  WKFRESULTS.clear()
  WKFRESULTS.addAll(step.WKFRESULTS, ...)
   
  Disposizioni.clear()
  Disposizioni.addAll(step.Disposizioni, ...)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public string WkfStep.generateStepJSONDescription()
{
   
  string diagramStepDescription = ""
  if (this.isStartStep() || this.isStopStep())
  {
    QappCore.DTTLogMessage("Start and Stop doesn't have executor", ..., DTTWarning)
    return ""
  }
  else 
  {
    switch (EXECUTORTYPE)
    {
      case Manuale:
         if (IDDIPENDENTE > 0)
         {
           try 
           {
             if (ISFUNCTION == Yes)
             {
                Funzione f = Funzione.getFromDB(IDDIPENDENTE, ...)
                 
                if (f != null)
                {
                  diagramStepDescription = diagramStepDescription + "(" + f.CODFUNZIONE + ")"
                }
             }
             else 
             {
                Personale p = Personale.GetByIdDipendente(IDDIPENDENTE, true)
                if (p != null && p.IDDIPENDENTE > 0)
                {
                  string utenteQualibus = ""
                  Utente u = Utente.get(p.IDDIPENDENTE)
                  if (u != null && length(u.DESCRUTENTE) > 0)
                  {
                    utenteQualibus = "(" + u.DESCRUTENTE + ")"
                  }
                  diagramStepDescription = diagramStepDescription + "(" + p.FullName + utenteQualibus + ")"
                }
             }
           }
           catch 
           {
             QappCore.DTTLogMessage("Personale non valido. ID:" + toString(IDDIPENDENTE), ..., DTTWarning)
             diagramStepDescription = ""
           }
         }
         else 
         {
           QappCore.DTTLogMessage("Dipendente non presente nello step", ..., DTTWarning)
         }
      break
      case Responsabile:
         diagramStepDescription = diagramStepDescription + "(Responsabile)"
      break
      case RifPersonale:
         diagramStepDescription = diagramStepDescription + "(Rif. Personale)"
      break
      case InseritoDa:
         diagramStepDescription = diagramStepDescription + "(Utente inserimento)"
      break
    }
    if (this.isMultiple())
    {
      diagramStepDescription = diagramStepDescription + "MULTI"
    }
  }
   
//  diagramStepDescription = Tools.AddNewLineToLongStrings(diagramStepDescription, 10, "/n")
   
  return diagramStepDescription
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public string WkfStep.generateStepJSONTitle()
{
  string title = ""
   
  title = this.STEPDESCRIPTION
//  title = Tools.AddNewLineToLongStrings(STEPDESCRIPTION, 10, "/n")
   
  return title
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void WkfStep.storeTimePropertiesAsFloat()
{
  ORARIOINIZIO = Tools.TimeToFloat(StartTime)
  ORARIOFINE = Tools.TimeToFloat(EndTime)
}


// ──────────────────────────────────

// ****************************************************************************************************************************
// it return the Excutor based on the executor type of the workflow Step, it requires evento to find responsible and Insby user
// This method is indrectly tested by CreateCollection
// ****************************************************************************************************************************
public int WkfStep.resolveExecutorId(
  int idExecutorInTemplate // Write a comment for this parameter or press backspace to delete this comment
  Evento evento            // Write a comment for this parameter or press backspace to delete this comment
)
{
  int idDipendenteExecutor = idExecutorInTemplate
  switch (EXECUTORTYPE)
  {
    case Manuale:
      idDipendenteExecutor = idExecutorInTemplate
    break
    case Responsabile:
      Personale p = Personale.GetPersonale(evento.IDUTENTERESP)
      idDipendenteExecutor = p.IDDIPENDENTE
    break
    case InseritoDa:
      Personale p = Personale.GetPersonale(evento.IDUTENTEINS)
      idDipendenteExecutor = p.IDDIPENDENTE
    break
  }
  return idDipendenteExecutor
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static WkfStep WkfStep.get(
  int idStep // 
)
{
  WkfStep ws = new()
  ws.IDSTEP = idStep
   
  try 
  {
    ws.loadFromDB(...)
  }
  catch 
  {
    QappCore.DTTLogMessage("Error: step not existing", ..., DTTError)
    ws = null
  }
   
  return ws
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event WkfStep.OnInit()
{
  IDSTEP = Sequence.getNextSequence(WKFN_ID_STEP, ...)
  ORARIOINIZIO = Tools.TimeToFloat(toTime(9, 0, 0))
  ORARIOFINE = Tools.TimeToFloat(toTime(10, 0, 0))
  StartTime = toTime(9, 0, 0)
  EndTime = toTime(10, 0, 0)
  ISFUNCTION = No
  NOTIFYRESPONSIBLE = No
  NOTIFYEXECUTOR = No
  NOTIFYINADVANCE = No
  NOTIFYONEXECUTION = No
  NOTIFYONCLOSE = No
  NOTIFYDELAYS = No
  NOTIFYINSUSER = No
  NOTIFYDELAYS = No
  NOTIFYDELAYSCONTINUE = No
  NOTIFYCLIFORREFERENCE = No
  NOTIFYPERREFERENCE = No
  NOTIFICA = No
  MULTIPLE = No
   
  EXECUTORTYPE = Manuale
   
  // to manage the iddipendende decode (function/personale)
  this.AfterLoadMultiSourceResponsibleCreation()
}


// ──────────────────────────────────

// **************************************************************************************
// Event raised to the document during the initial steps of the document saving procedure
// **************************************************************************************
event throws exception WkfStep.BeforeSave(
  inout boolean Skip         // A boolean output parameter. If set to True, tells the framework not to perform operations on the database, because they will be managed independently.
  inout boolean Cancel       // A boolean output parameter. If set to True, tells the framework to cancel the save procedure due to errors that occurred. Since the entire document is saved in the same transa...
  int:beforeSavePhases Phase // An integer from 0 to 3 that specifies the progress status of the save procedure as detailed at the beginning of the article.
)
{
  this.storeTimePropertiesAsFloat()
}


// ──────────────────────────────────

// ************************************************************
// Event raised to the document during the validation procedure
// ************************************************************
event WkfStep.OnValidate(
  int:validateReasons Reason // An integer specifying the reason the document is being validated. The most common values are 0 to indicate complete validation before saving, or 1 to indicate a quick validati...
  inout boolean Error        // A boolean output parameter. If set to True, specifies an error condition and stops the validation immediately.
  inout boolean Skip         // A boolean output parameter. If set to True, does not check the rules for automatic validation of the document (see notes).
)
{
  this.refreshUserInterface()
   
  IDArray keys = Errors.getKeys()
   
  for (int i = 0; i < keys.length(); i = i + 1)
  {
    string key = keys.getValue(i)
    string value = Errors.getValue(key)
     
    Error = true
    this.setPropertyError(value, toPropertyIndex(key))
    this.refreshUserInterface()
  }
}


// ──────────────────────────────────

// ************************************************************
// Event raised by the document after loading from the database
// ************************************************************
event WkfStep.AfterLoad(
  boolean AlreadyLoaded     // A boolean parameter that specifies whether the document was previously loaded.
  IDArray LoadedCollections // IDArray type parameter that contains the collections loaded by the framework. If the value of the Already Loaded parameter is False, the Loaded Collections parameter is NULL.
)
{
  this.AfterLoadMultiSourceResponsibleCreation()
   
  StartTime = Tools.FloatToTime(ORARIOINIZIO)
  EndTime = Tools.FloatToTime(ORARIOFINE)
   
  this.setOriginal()
}


// ──────────────────────────────────

// ***********************************************************
// Event raised to the document when the transaction is closed
// ***********************************************************
event WkfStep.OnEndTransaction()
{
  if (wasModified(WkfstepResponsibleIDForDecodingInUI))
  {
    MultiSourceResponsible.MultiSourceResponsibleInterface.updateResponsiblePropertiesEndTransactionHandler()
  }
   
  if (wasModified(StartTime) or wasModified(EndTime))
    this.storeTimePropertiesAsFloat()
   
  if (wasModified(EXECUTORTYPE))
  {
    if (EXECUTORTYPE != Manuale)
    {
      IDDIPENDENTE = null
      ISFUNCTION = No
      WkfstepResponsibleIDForDecodingInUI = null
    }
  }
}


// ──────────────────────────────────

// ****************************************
// Describe the purpose of this function...
// ****************************************
public void WkfStep.UpdateResponsiblePropertiesEndTransactionHandler()
{
  if (MultiSourceResponsible.MultiSourceResponsibleInterface)
  {
    if (WkfstepResponsibleIDForDecodingInUI != null && WkfstepResponsibleIDForDecodingInUI != "")
    {
      string:flagYN isFunctionValue = null
      int idResponsibleValue = null
       
      MultiSourceResponsible.decodeKey(WkfstepResponsibleIDForDecodingInUI, isFunctionValue, idResponsibleValue)
       
      ISFUNCTION = isFunctionValue
      IDDIPENDENTE = idResponsibleValue
    }
    else 
    {
      ISFUNCTION = No
      IDDIPENDENTE = null
    }
  }
}


// ──────────────────────────────────

// **************************************************************************************************************************************************************************
// this method must set the string (such as F103) used for decoding in panel, starting from the DB fields (such as IS_FUNCTION and ID_UTENTE RESP, in case of modello evento)
// **************************************************************************************************************************************************************************
public void WkfStep.MultiSourceResponsibleCreationHandler()
{
  if (MultiSourceResponsible != null)
  {
    if (IDDIPENDENTE > 0)
    {
      string computeId = MultiSourceResponsible.prepareDecodingKey(ISFUNCTION, IDDIPENDENTE, "P")
      WkfstepResponsibleIDForDecodingInUI = computeId
      MultiSourceResponsible.ID = computeId
    }
    else 
    {
      WkfstepResponsibleIDForDecodingInUI = ""
      MultiSourceResponsible.ID = ""
    }
  }
}


// ──────────────────────────────────

// ****************************************************************************************************************************************************************************************************************
// this method is intentionally put in the interface to remember the developer that in the after load of the class that implments IMultiSourceResponsible it is mandatory to instantiate a multiSourceResponsbile
// object
// so in this object a line of code like
// multiSourceResponsible = multiSourceResponsible.create(this, funzioneOrDipendente) must be written
// ****************************************************************************************************************************************************************************************************************
public void WkfStep.AfterLoadMultiSourceResponsibleCreation()
{
  MultiSourceResponsible = MultiSourceResponsible.create(this, funzioneOrDipendente)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static WkfFlow WkfFlow.create(
  ModelloEvento modello     // 
  WkfStep step              // 
  WkfStep nextStep          // 
  optional WkfResult result // 
)
{
  if (step == null or nextStep == null)
  {
    QappCore.DTTLogMessage("Flow can't be created with if a step is null", ..., DTTError)
    return null
  }
  WkfFlow wkfflow = new()
  wkfflow.init()
  wkfflow.IDTEMPLATEEVENTO = modello.IDTEMPLATEEVENTO
  wkfflow.IDSTEP = step.IDSTEP
  wkfflow.IDNEXTSTEP = nextStep.IDSTEP
  wkfflow.ModelloEvento = modello
  if (result)
    wkfflow.IDRESULT = result.IDRESULT
   
  if (step.isStartStep())
  {
    wkfflow.ISSTART = Yes
  }
   
  if (nextStep.isStopStep())
  {
    wkfflow.ISSTOP = Yes
  }
   
  return wkfflow
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static WkfFlow WkfFlow.get(
  int idFlow // 
)
{
  WkfFlow flow = new()
  flow.IDFLOW = idFlow
  try 
  {
    flow.loadFromDB(...)
  }
  catch 
  {
    QappCore.DTTLogMessage("flow not retrieved", ..., DTTWarning)
    flow = null
  }
   
   
  return flow
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event WkfFlow.OnInit()
{
  IDFLOW = Sequence.getNextSequence(WKFN_ID_FLOW, ...)
  SEQUENCE = 1
  ISDATEEDITABLE = Yes
  DATECALCULATIONTYPE = 0
  PUBLICSTATUSSELECTIONTYPE = Richiedi
  CLOSURERESULTTYPE = Richiedi
}


// ──────────────────────────────────

// ***********************************************************
// Event raised to the document when the transaction is closed
// ***********************************************************
event WkfFlow.OnEndTransaction()
{
  if (wasModified(PUBLICSTATUSSELECTIONTYPE))
  {
    if (PUBLICSTATUSSELECTIONTYPE == Richiedi)
    {
      IdEventoPublicStato = null
    }
  }
  if (wasModified(CLOSURERESULTTYPE))
  {
    if (CLOSURERESULTTYPE == Richiedi)
    {
      IDGRAVITA = null
    }
  }
  if (wasModified(DELAYDAYS))
  {
    if (DELAYDAYS < 0)
    {
      DELAYDAYS = 0
    }
  }
}


// ──────────────────────────────────

// ************************************************************
// Event raised to the document during the validation procedure
// ************************************************************
event WkfFlow.OnValidate(
  int:validateReasons Reason // An integer specifying the reason the document is being validated. The most common values are 0 to indicate complete validation before saving, or 1 to indicate a quick validati...
  inout boolean Error        // A boolean output parameter. If set to True, specifies an error condition and stops the validation immediately.
  inout boolean Skip         // A boolean output parameter. If set to True, does not check the rules for automatic validation of the document (see notes).
)
{
  this.refreshUserInterface()
   
  IDArray keys = Errors.getKeys()
   
  for (int i = 0; i < keys.length(); i = i + 1)
  {
    string key = keys.getValue(i)
    string value = Errors.getValue(key)
     
    Error = true
    this.setPropertyError(value, key)
    this.refreshUserInterface()
  }
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event WkfResult.OnInit()
{
  IDRESULT = Sequence.getNextSequence(WKFN_ID_RESULT, ...)
   
}


// ──────────────────────────────────

// **************************************************************************
// Event called to customize the Smart Lookup procedure at the document level
// **************************************************************************
event WkfResult.OnGetSmartLookup(
  Recordset RecordSet       // The recordset to be filled if the queries run during the Smart Lookup event are customized.
  inout int Level           // The Smart Lookup procedure performs multiple attempts, increasingly widening the search field. This integer parameter specifies the number of attempts made.
  inout boolean NullValue   // A boolean output parameter. If set to True, then NULL (no results found) will be returned to the panel.
  inout boolean Skip        // A boolean output parameter. If set to True, then the standard queries for the current level will not be executed.
  inout boolean Cancel      // A boolean output parameter. If set to True, the Smart Lookup procedure will be stopped.
  IDDocument CallerDocument // The document for which the smart lookup procedure is launched. It can be null.
)
{
  Skip = true
  Cancel = true
   
  if (WkfFlow.isMyInstance(CallerDocument))
  {
    WkfFlow wf = (WkfFlow)CallerDocument
    Diagram d = (Diagram)wf.parent
     
    Recordset resultsRecordset = this.getRecordset(wf, d)
    RecordSet.copyFrom(resultsRecordset)
  }
  else 
  {
    Cancel = true
  }
   
}


// ──────────────────────────────────

// ************************************************************
// Event raised by the document after loading from the database
// ************************************************************
event WkfResult.AfterLoad(
  boolean AlreadyLoaded     // A boolean parameter that specifies whether the document was previously loaded.
  IDArray LoadedCollections // IDArray type parameter that contains the collections loaded by the framework. If the value of the Already Loaded parameter is False, the Loaded Collections parameter is NULL.
)
{
  this.computeAdditionalProperties()
  this.setOriginal()
}


// ──────────────────────────────────

// ************************************************************
// Event raised to the document during the validation procedure
// ************************************************************
event WkfResult.OnValidate(
  int:validateReasons Reason // An integer specifying the reason the document is being validated. The most common values are 0 to indicate complete validation before saving, or 1 to indicate a quick validati...
  inout boolean Error        // A boolean output parameter. If set to True, specifies an error condition and stops the validation immediately.
  inout boolean Skip         // A boolean output parameter. If set to True, does not check the rules for automatic validation of the document (see notes).
)
{
  if (RESULTDESCRIPTION == "")
  {
    Error = true
    this.setPropertyError("La descrizione dell'esito non può essere vuota.", RESULTDESCRIPTION)
  }
  this.refreshUserInterface()
}


// ──────────────────────────────────

// **************************************************************************************
// Event raised to the document during the initial steps of the document saving procedure
// **************************************************************************************
event throws exception WkfResult.BeforeSave(
  inout boolean Skip         // A boolean output parameter. If set to True, tells the framework not to perform operations on the database, because they will be managed independently.
  inout boolean Cancel       // A boolean output parameter. If set to True, tells the framework to cancel the save procedure due to errors that occurred. Since the entire document is saved in the same transa...
  int:beforeSavePhases Phase // An integer from 0 to 3 that specifies the progress status of the save procedure as detailed at the beginning of the article.
)
{
  if (Phase == PreSave)
  {
    if (deleted)
    {
      for each CdataWkfResultLink cwrl in CdataWkfResultLinks
      {
         cwrl.deleted = true
      }
    }
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static WkfResult WkfResult.create(
  WkfStep step       // 
  string description // 
)
{
  WkfResult result = new()
  result.init()
  result.IDTEMPLATEEVENTO = step.IDTEMPLATEEVENTO
  result.IDSTEP = step.IDSTEP
  result.RESULTDESCRIPTION = description
   
  result.computeAdditionalProperties()
   
  return result
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void WkfResult.computeAdditionalProperties()
{
  if (IDRESULT > 0)
  {
    this.LoadCdataWkfResult(IDRESULT)
  }
   
  HasCdata = false
   
  if (CdataWkfResultLinks && CdataWkfResultLinks.count() > 0)
  {
    HasCdata = true
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void WkfResult.LoadCdataWkfResult(
  int idresult // 
)
{
  if (isNull(idresult) or idresult == 0)
  {
    CdataWkfResultLinks = new()
    return 
  }
   
   
  IDCollection cdata of CdataWkfResultLink = new()
  select into collection (cdata)
    set IDRESULTCD = IDRESULTCD
    set IDRESULT = IDRESULT
    set IDCDATAFLD = IDCDATAFLD
    set OBBLIGATORIO = OBBLIGATORIO
    set ISREADONLY = ISREADONLY
  from 
    CDATAWKFRESULT // master table
  where
    IDRESULT = idresult
   
   
  CdataWkfResultLinks = cdata
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static WkfResult WkfResult.get(
  int idWkfResult // 
)
{
  WkfResult result = new()
  result.IDRESULT = idWkfResult
   
  try 
  {
    result.loadFromDB(...)
  }
  catch 
  {
    result = null
    QappCore.DTTLogMessage("WkfResult not found in db", ..., DTTError)
  }
   
  return result
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public Recordset WkfResult.getRecordset(
  WkfFlow flow          // 
  Diagram parentDiagram // 
)
{
  Recordset resultsRecordset = new()
   
  if (flow.IDFLOW > 0)
  {
    WkfStep ws = parentDiagram.getStepFromDiagram(flow.IDSTEP)
    IDCollection notAssignedResultsOfStep of WkfResult = parentDiagram.getNonAssignedResultsOfStep(ws, ...)
     
    RecordsetMetaData rmd = new()
    rmd.setColumnCount(2)
    rmd.setFieldName(1, "IDRESULT")
    rmd.setFieldName(2, "RESULTDESCRI")
    rmd.setFieldType(1, Character)
    rmd.setFieldType(2, Character)
    resultsRecordset.setMetaData(rmd)
     
    for each WkfResult wr in notAssignedResultsOfStep
    {
      Tools.addRowToRecordset(resultsRecordset, toString(wr.IDRESULT), wr.RESULTDESCRIPTION)
    }
  }
  else 
  {
    QappCore.DTTLogMessage("Flow si null.", ..., DTTError)
  }
   
  return resultsRecordset
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event CdataWkfResultLink.OnInit()
{
  IDRESULTCD = null
  OBBLIGATORIO = No
  ISREADONLY = No
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static CdataWkfResultLink CdataWkfResultLink.create(
  CdataWkfResultDisplayUIClass wkfResultCdataDisplay // 
)
{
  CdataWkfResultLink cwrl = new()
  cwrl.init()
  cwrl.IDRESULTCD = wkfResultCdataDisplay.IDRESULTCD
  cwrl.IDRESULT = wkfResultCdataDisplay.IDRESULT
  cwrl.IDCDATAFLD = wkfResultCdataDisplay.IDCDATAFLD
  cwrl.OBBLIGATORIO = if(wkfResultCdataDisplay.MANDATORY, Yes, No)
  cwrl.ISREADONLY = if(wkfResultCdataDisplay.ISREADONLY, Yes, No)
   
  return cwrl
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event MultipleStepTemplateDisposizione.OnInit()
{
  IDMultipleStepTemplateDisposizione = Sequence.getNextSequence(EVAN_ID_MULTI_STEPS, ...)
  SEQ = 1
  DELAYDAYS = 0
  ISFUNCTION = No
  DESCRIPTION = ""
   
  time nineAM = #09:00:00#
  STARTTIME = Tools.TimeToFloat(nineAM)
   
  time tenAM = #10:00:00#
  ENDTIME = Tools.TimeToFloat(tenAM)
}


// ──────────────────────────────────

// ************************************************************
// Event raised to the document during the validation procedure
// ************************************************************
event MultipleStepTemplateDisposizione.OnValidate(
  int:validateReasons Reason // An integer specifying the reason the document is being validated. The most common values are 0 to indicate complete validation before saving, or 1 to indicate a quick validati...
  inout boolean Error        // A boolean output parameter. If set to True, specifies an error condition and stops the validation immediately.
  inout boolean Skip         // A boolean output parameter. If set to True, does not check the rules for automatic validation of the document (see notes).
)
{
  if (!(deleted))
  {
    if (isNull(STARTTIME))
    {
      Error = true
      this.setPropertyError("L'ora di inizio deve essere impostata. ", STARTTIME)
    }
     
    if (isNull(ENDTIME))
    {
      Error = true
      this.setPropertyError("L'ora di fine deve essere impostata. ", ENDTIME)
    }
     
    if ((ENDTIME - STARTTIME) <= 0)
    {
      Error = true
      this.setPropertyError("L'ora di fine deve essere maggiore rispetto all'ora di inizio. ", ENDTIME)
    }
     
    if (length(DESCRIPTION) = 0)
    {
      Error = true
      this.setPropertyError("E' obbligatorio inserire una descrizione. ", DESCRIPTION)
    }
  }
  this.refreshUserInterface()
}


// ──────────────────────────────────

// ***********************************************************
// Event raised to the document when the transaction is closed
// ***********************************************************
event MultipleStepTemplateDisposizione.OnEndTransaction()
{
  if (isModified(SEQ))
  {
    if (SEQ <= 0)
    {
      SEQ = 1
    }
  }
   
  if (isModified(DELAYDAYS))
  {
    if (DELAYDAYS < 0)
    {
      DELAYDAYS = 0
    }
  }
}


// ──────────────────────────────────

// **************************************************************************************
// Event raised to the document during the initial steps of the document saving procedure
// **************************************************************************************
event throws exception MultipleStepTemplateDisposizione.BeforeSave(
  inout boolean Skip         // A boolean output parameter. If set to True, tells the framework not to perform operations on the database, because they will be managed independently.
  inout boolean Cancel       // A boolean output parameter. If set to True, tells the framework to cancel the save procedure due to errors that occurred. Since the entire document is saved in the same transa...
  int:beforeSavePhases Phase // An integer from 0 to 3 that specifies the progress status of the save procedure as detailed at the beginning of the article.
)
{
  STARTTIME = Tools.TimeToFloat(TimeStartTime)
  ENDTIME = Tools.TimeToFloat(TimeEndTime)
}


// ──────────────────────────────────

// ************************************************************
// Event raised by the document after loading from the database
// ************************************************************
event MultipleStepTemplateDisposizione.AfterLoad(
  boolean AlreadyLoaded     // A boolean parameter that specifies whether the document was previously loaded.
  IDArray LoadedCollections // IDArray type parameter that contains the collections loaded by the framework. If the value of the Already Loaded parameter is False, the Loaded Collections parameter is NULL.
)
{
  this.ComputeAdditionalProperties()
  this.setOriginal()
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static MultipleStepTemplateDisposizione MultipleStepTemplateDisposizione.create(
  ModelloEvento modelloEvento         // 
  WkfStep step                        // 
  optional boolean skipErrorForUT = 0 // 
)
{
  if ((!(modelloEvento) or !(step)) or (modelloEvento.IDTEMPLATEEVENTO <= 0 or step.IDSTEP <= 0))
  {
    if (!(skipErrorForUT))
    {
      QappCore.DTTLogMessage("Modello evento or step not valid", ..., DTTError)
    }
    return null
  }
   
  MultipleStepTemplateDisposizione mesd = new()
  mesd.init()
   
  mesd.IDTEMPLATEEVENTO = modelloEvento.IDTEMPLATEEVENTO
  mesd.IDSTEP = step.IDSTEP
   
  // default times 9AM - 10AM
  mesd.STARTTIME = 0,375
  mesd.ENDTIME = 0,416
  mesd.ComputeAdditionalProperties()
   
  return mesd
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void MultipleStepTemplateDisposizione.ComputeAdditionalProperties()
{
  TimeStartTime = Tools.FloatToTime(STARTTIME)
  TimeEndTime = Tools.FloatToTime(ENDTIME)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void MultipleStepTemplateDisposizione.markAsOrginal(
  int propertyIndex // 
)
{
  time f = getProperty(propertyIndex)
  this.setOriginalValue(propertyIndex, f)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static IDCollection CdataWkfResultDisplayUIClass.getModelloEventoWkfResultCDataCollection(
  ModelloEvento modelloEvento // 
  WkfResult wkfResult         // 
)
{
   
   
  // parameter basic validation
  if (!(modelloEvento) or !(wkfResult) or wkfResult.IDRESULT == 0)
  {
    return null
  }
  IDCollection result of CdataWkfResultDisplayUIClass = new()
  result = this.getModelloEventoInitialWkfResultCdataCollection(modelloEvento, wkfResult)
   
  return result
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static IDCollection CdataWkfResultDisplayUIClass.getModelloEventoInitialWkfResultCdataCollection(
  ModelloEvento modelloEvento // 
  WkfResult wkfResult         // 
)
{
  IDCollection result of CdataWkfResultDisplayUIClass = new()
  for each ModelloEventoCDSection mecds in modelloEvento.ModelloEventoCDSections
  {
    if (mecds.VISIBLE == No)
    {
      continue 
    }
    CdataSection cs = CdataSection.get(mecds.IDCDATASEC)
    cs.loadCollectionFromDB(cs.CDATAFIELDSINFO, ...)
//    if (cs.Active == No)
//    {
//      continue 
//    }
    for each MainModuleDatoPersonalizzatoInfo mmdpi in cs.CDATAFIELDSINFO
    {
      int idCdataFld = mmdpi.IDCDATAFLD
      string sectionName = cs.Name
      string cdataName = mmdpi.Caption
       
      int idResultCd = 0
       
      CdataWkfResultDisplayUIClass c = new()
      c.IDCDATAFLD = idCdataFld
      c.IDRESULT = wkfResult.IDRESULT
      c.IDRESULTCD = idResultCd
      c.SECTIONNAME = sectionName
      c.CUSTOMDATANAME = cdataName
       
      c.SHOW = false
      c.ISREADONLY = false
      c.MANDATORY = false
      c.IDRESULTCD = 0
       
      // set the ui accordingly to the result cdata links
      for each CdataWkfResultLink cd in wkfResult.CdataWkfResultLinks
      {
         if (cd.deleted == true)
         {
           continue 
         }
          
         if (cd.IDCDATAFLD == idCdataFld and cd.IDRESULT == wkfResult.IDRESULT)
         {
           c.SHOW = true
           c.ISREADONLY = cd.ISREADONLY == Yes
           c.MANDATORY = cd.OBBLIGATORIO == Yes
           c.IDRESULTCD = cd.IDRESULTCD
           break 
         }
      }
       
      result.add(c)
    }
     
  }
  return result
}


// ──────────────────────────────────

// ************************************************************
// Event raised to the document during the validation procedure
// ************************************************************
event CdataWkfResultDisplayUIClass.OnValidate(
  int:validateReasons Reason // An integer specifying the reason the document is being validated. The most common values are 0 to indicate complete validation before saving, or 1 to indicate a quick validati...
  inout boolean Error        // A boolean output parameter. If set to True, specifies an error condition and stops the validation immediately.
  inout boolean Skip         // A boolean output parameter. If set to True, does not check the rules for automatic validation of the document (see notes).
)
{
  if (SHOW == false)
  {
    if (MANDATORY == true || ISREADONLY == true)
    {
      Error = true
      this.setPropertyError("Non puoi impostare come non visibile se è obbligatorio o di sola lettura", SHOW)
    }
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static DiagramHandler DiagramHandler.create(
  Diagram diagram      // 
  IDForm callerSubform // 
)
{
  if (diagram == null or callerSubform == null)
  {
    QappCore.DTTLogMessage("we don't like nulls!", ..., DTTError)
  }
   
  DiagramHandler dh = new()
  dh.init()
  dh.Diagram = diagram
  dh.CallerSubform = callerSubform
   
  return dh
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void DiagramHandler.setMessageData(
  MessageData messageData // 
)
{
  MessageData = messageData
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void DiagramHandler.updateStep(
  WkfStep step // 
)
{
  if (step == null)
  {
    QappCore.DTTLogMessage("Can't update a step that is null", ..., DTTError)
    return 
  }
   
  if (MessageData == null)
  {
    MessageData = new()
  }
   
  if (MessageData != null)
  {
     
    MessageData.Content.setValue("id", step.IDSTEP)
    MessageData.Content.setValue("key", step.IDSTEP)
     
     
    string diagramStepText = ""
    string diagramStepDescription = ""
     
    if (length(step.STEPDESCRIPTION) > 0)
      diagramStepText = step.STEPDESCRIPTION
     
    diagramStepDescription = step.generateStepJSONDescription()
     
     
     
    MessageData.Content.setValue("text", diagramStepText)
    MessageData.Content.setValue("description", diagramStepDescription)
    CallerSubform.sendMessage(updateDiagramStep, MessageData, ...)
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void DiagramHandler.UpdateFlow(
  WkfFlow flow // 
)
{
  if (flow == null)
  {
    QappCore.DTTLogMessage("Il flow che vuoi aggiornare non esiste", ..., DTTInfo)
    return 
  }
   
  if (Diagram == null)
  {
    QappCore.messageBox("Diagram non istanziato")
    return 
  }
   
  if (MessageData == null)
  {
    MessageData = new()
    MessageData.Content = new()
  }
   
  MessageData.Content.clear()
  MessageData.Content.setValue("id", flow.IDFLOW)
   
  if (flow.ISSTART == Yes)
  {
    if (flow.DELAYDAYS > 0)
    {
      MessageData.Content.setValue("text", "(" + toString(flow.DELAYDAYS) + ")")
      MessageData.Content.setValue("points", flow.POINTS)
      CallerSubform.sendMessage(updateDiagramFlow, MessageData, ...)
    }
  }
  else 
  {
    WkfResult wr = Diagram.GetResultFromId(flow.IDRESULT)
     
    if (wr == null)
    {
      QappCore.messageBox("Esito non esistente nello step")
      return 
    }
     
    MessageData.Content.setValue("text", formatMessage("|1 |2", wr.RESULTDESCRIPTION, if(flow.DELAYDAYS > 0, "(" + toString(flow.DELAYDAYS) + ")", ""), ...))
    MessageData.Content.setValue("points", flow.POINTS)
    CallerSubform.sendMessage(updateDiagramFlow, MessageData, ...)
  }
   
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void DiagramHandler.deleteStep(
  WkfStep step // 
)
{
  MessageData md = new()
  md.Content.setValue("id", step.IDSTEP)
  CallerSubform.sendMessage(deleteDiagramStep, md, ...)
//   
  IDArray errorsArray = new()
  Diagram.removeStep(step, errorsArray)
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public void DiagramHandler.removeFlow(
  WkfFlow flow // 
)
{
  if (flow == null)
  {
    QappCore.messageBox("Flow non esistente")
    return 
  }
   
  if (Diagram == null)
  {
    QappCore.messageBox("Diagram non istanziato")
    return 
  }
   
   
  MessageData md = new()
  md.Content = new()
  md.Content.setValue("id", flow.IDFLOW)
  CallerSubform.sendMessage(deleteDiagramFlow, md, ...)
   
  IDArray errors = new()
  Diagram.removeFlow(flow, errors)
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public void DiagramHandler.UpdateFlowDescriptionFromWkfResultUpdate(
  int idResult // 
)
{
   
  if (idResult <= 0)
  {
    QappCore.DTTLogMessage("Can't update diagram flow because step or id result is null", ..., DTTWarning)
    return 
  }
   
  WkfFlow wf = Diagram.GetFlowForSpecificWkfResult(idResult)
  if (wf == null)
  {
    QappCore.DTTLogMessage("Can't get flow from idResult " + toString(idResult), ..., DTTWarning)
  }
   
  this.UpdateFlow(wf)
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public ModelloEvento DiagramHandler.GetDiagramModelloEvento()
{
  return Diagram.ModelloEvento
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public int DiagramHandler.GetDiagramIdTemplateEvento()
{
  return Diagram.ModelloEvento.IDTEMPLATEEVENTO
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public Diagram DiagramHandler.GetDiagram()
{
  return Diagram
}


// ──────────────────────────────────

// ************************************************************
// Event raised to the document during the validation procedure
// ************************************************************
event WkfDisposizioneDraft.OnValidate(
  int:validateReasons Reason // An integer specifying the reason the document is being validated. The most common values are 0 to indicate complete validation before saving, or 1 to indicate a quick validati...
  inout boolean Error        // A boolean output parameter. If set to True, specifies an error condition and stops the validation immediately.
  inout boolean Skip         // A boolean output parameter. If set to True, does not check the rules for automatic validation of the document (see notes).
)
{
  if (!(deleted))
  {
    if (isNull(STARTTIME))
    {
      Error = true
      this.setPropertyError("L'ora di inizio deve essere impostata. ", TimeStartTime)
    }
     
    if (isNull(ENDTIME))
    {
      Error = true
      this.setPropertyError("L'ora di fine deve essere impostata. ", TimeEndTime)
    }
     
    if ((ENDTIME - STARTTIME) <= 0)
    {
      Error = true
      this.setPropertyError("L'ora di fine deve essere maggiore rispetto all'ora di inizio. ", TimeEndTime)
    }
     
    if (isNull(IDEXECUTORDIPENDENTE))
    {
      Error = true
      this.setPropertyError("L'esecutore deve essere definito.", EsecutoreIDForDecodingInUI)
    }
    if (isNull(ExecutionDate))
    {
      Error = true
      this.setPropertyError("E’ necessario definire una data per la prossima disposizione.", ExecutionDate)
    }
     
  }
  this.refreshUserInterface()
}


// ──────────────────────────────────

// **************************************************************************************
// Event raised to the document during the initial steps of the document saving procedure
// **************************************************************************************
event throws exception WkfDisposizioneDraft.BeforeSave(
  inout boolean Skip         // A boolean output parameter. If set to True, tells the framework not to perform operations on the database, because they will be managed independently.
  inout boolean Cancel       // A boolean output parameter. If set to True, tells the framework to cancel the save procedure due to errors that occurred. Since the entire document is saved in the same transa...
  int:beforeSavePhases Phase // An integer from 0 to 3 that specifies the progress status of the save procedure as detailed at the beginning of the article.
//)
//{
//  STARTTIME = Tools.TimeToFloat(TimeStartTime)
//  ENDTIME = Tools.TimeToFloat(TimeEndTime)
//}


// ──────────────────────────────────

// ***********************************************************
// Event raised to the document when the transaction is closed
// ***********************************************************
event WkfDisposizioneDraft.OnEndTransaction()
{
  if (wasModified(EsecutoreIDForDecodingInUI))
  {
    this.UpdateResponsiblePropertiesEndTransactionHandler()
  }
  if (wasModified(TimeStartTime))
  {
    STARTTIME = Tools.TimeToFloat(TimeStartTime)
  }
  if (wasModified(TimeEndTime))
  {
    ENDTIME = Tools.TimeToFloat(TimeEndTime)
  }
}


// ──────────────────────────────────

// *********************************************
// Raised when the document is being initialized
// *********************************************
event WkfDisposizioneDraft.OnInit()
{
  STARTTIME = 0,375
  ENDTIME = 0,416666666666667
  this.AfterLoadMultiSourceResponsibleCreation()
}


// ──────────────────────────────────

// ************************************************************
// Event raised by the document after loading from the database
// ************************************************************
event WkfDisposizioneDraft.AfterLoad(
  boolean AlreadyLoaded     // A boolean parameter that specifies whether the document was previously loaded.
  IDArray LoadedCollections // IDArray type parameter that contains the collections loaded by the framework. If the value of the Already Loaded parameter is False, the Loaded Collections parameter is NULL.
)
{
  this.AfterLoadMultiSourceResponsibleCreation()
}


// ──────────────────────────────────

// ***************************************************************************************
// convert StartTime,EndTime (float values) to TimeStartTime and TimeEndTime (Time Values)
// ***************************************************************************************
public void WkfDisposizioneDraft.ComputeAdditionalProperties()
{
  TimeStartTime = Tools.FloatToTime(STARTTIME)
  TimeEndTime = Tools.FloatToTime(ENDTIME)
}


// ──────────────────────────────────

// ****************************************
// Describe the purpose of this function...
// ****************************************
public void WkfDisposizioneDraft.UpdateResponsiblePropertiesEndTransactionHandler()
{
  if (MultiSourceResponsible.MultiSourceResponsibleInterface)
  {
    if (EsecutoreIDForDecodingInUI != null && EsecutoreIDForDecodingInUI != "")
    {
      string:flagYN isFunctionValue = null
      int idExecutorValue = null
       
      MultiSourceResponsible.decodeKey(EsecutoreIDForDecodingInUI, isFunctionValue, idExecutorValue)
       
      ISFUNCTION = isFunctionValue
      IDEXECUTORDIPENDENTE = idExecutorValue
    }
    else 
    {
      ISFUNCTION = No
      IDEXECUTORDIPENDENTE = null
    }
  }
}


// ──────────────────────────────────

// **************************************************************************************************************************************************************************
// this method must set the string (such as F103) used for decoding in panel, starting from the DB fields (such as IS_FUNCTION and ID_UTENTE RESP, in case of modello evento)
// **************************************************************************************************************************************************************************
public void WkfDisposizioneDraft.MultiSourceResponsibleCreationHandler()
{
  if (MultiSourceResponsible != null)
  {
    if (IDEXECUTORDIPENDENTE > 0)
    {
      string computeId = MultiSourceResponsible.prepareDecodingKey(ISFUNCTION, IDEXECUTORDIPENDENTE, "P")
      EsecutoreIDForDecodingInUI = computeId
      MultiSourceResponsible.ID = computeId
    }
    else 
    {
      EsecutoreIDForDecodingInUI = ""
      MultiSourceResponsible.ID = ""
    }
  }
}


// ──────────────────────────────────

// ****************************************************************************************************************************************************************************************************************
// this method is intentionally put in the interface to remember the developer that in the after load of the class that implments IMultiSourceResponsible it is mandatory to instantiate a multiSourceResponsbile
// object
// so in this object a line of code like
// multiSourceResponsible = multiSourceResponsible.create(this, funzioneOrDipendente) must be written
// ****************************************************************************************************************************************************************************************************************
public void WkfDisposizioneDraft.AfterLoadMultiSourceResponsibleCreation()
{
  MultiSourceResponsible = MultiSourceResponsible.create(this, funzioneOrDipendente)
}


// ──────────────────────────────────

// ***************************************************************************************************************************
// shows a form to validate credentials and check if the user has a needed privilege
// the result is boolean and we use modal result to check the success
// 
// true: returned if credentials are ok and needed privilege matches
// false: if user cancels
// 
// SUGGESTED USAGE:
// Call CheckCredentialsForm.showForm when you want to ask for credentials and check if the user has a specific Pers privilege
// 
// Then in the on end modal of the caller form check if result is true to know if the inputed credentials are ok
// 
// ***************************************************************************************************************************
public void CheckCredentialsForm.ShowForm(
  string infoMessage                                         // Write a comment for this parameter or press backspace to delete this comment
  optional string:neededPrivilegeTypes neededPrivilege = "N" // 
)
{
  checkCredentialspanel.usernamecheckCredentialspanel = ""
  checkCredentialspanel.passwordcheckCredentialspanel = ""
   
  checkCredentialspanel.Labelinfo.caption = infoMessage
   
  this.NeededPrivilege = neededPrivilege
   
  this.show(Modal)
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void CheckCredentialsForm.ButtonConnetti()
{
  string userName = checkCredentialspanel.usernamecheckCredentialspanel
  string password = checkCredentialspanel.passwordcheckCredentialspanel
   
  boolean privilegeMatches = false
   
   
  boolean validCredentials = checkCredentials(userName, password, false)
  if (!(validCredentials))
  {
    this.showMessage(ERROR, "Le credenziali specificate non sono corrette", ...)
  }
  else 
  {
    Utente u = new()
    u.Username = checkCredentialspanel.usernamecheckCredentialspanel
    try 
    {
      u.loadFromDB(...)
      u.loadPrivilegeForCurrentQapp(...)
      switch (this.NeededPrivilege)
      {
         case NeededPrivilegePers1:
           privilegeMatches = u.hasPers1OnCurrentQapp()
         break
         case NeededPrivilegePers2:
           privilegeMatches = u.hasPers2OnCurrentQapp()
         break
         case NeededPrivilegePers3:
           privilegeMatches = u.hasPers3OnCurrentQapp()
         break
         case NeededPrivilegePers4:
           privilegeMatches = u.hasPers4OnCurrentQapp()
         break
         case NeededPrivilegePers5:
           privilegeMatches = u.hasPers5OnCurrentQapp()
         break
         default:
           privilegeMatches = true
         break
      }
    }
    catch 
    {
       
      // the only case in with this catch is entered is in principle the one for which the user tried to login with a valid db user (so checkCredentials passes) that has not a matching qualibus user (tipical
      // case is "sa" for which a user does not exist: we want to continue 
      string adminUserName = TabParametri.getDbAdminUsername()
       
       
      // user tried to login with sa or equivalent so we grant full rights
      // note
      if (adminUserName == userName)
      {
         privilegeMatches = true
         QappCore.DTTLogMessage(formatMessage("access granted to AdminDbUser", adminUserName, ...), ..., DTTInfo)
      }
    }
     
    if (!(privilegeMatches))
    {
      this.showMessage(ERROR, formatMessage("L'utente non ha il privilegio |1 richiesto", decode(this.NeededPrivilege, NeededPrivilegeTypes), ...), ...)
      return 
    }
    else 
    {
       
      // here we set the modal response
      this.close(true)
    }
  }
   
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void CheckCredentialsForm.ButtonAnnulla()
{
  this.close(false)
}


// ──────────────────────────────────

// ***********************************************
// Event raised to the form when loading in memory
// ***********************************************
event CheckCredentialsForm.Load()
{
  checkCredentialspanel.setErrorMode(Message, Message, Message, Message)
  checkCredentialspanel.showInfoMessages = true
}


// ──────────────────────────────────

// **********
// G
// **********
public void EditSettingsForm.openFor(
  QappCommandHandler qappCommandHandler // Write a comment for this parameter or press backspace to delete this comment
)
{
  this.QappCommandHandler = qappCommandHandler
  string settingsFileName = this.QappCommandHandler.getSettingsFilename()
  string fullpath = QappCore.path() + FH.getSeparator() + settingsFileName
  int fileNumber = QappCore.freeFile()
   
  QappCore.DTTLogMessage(formatMessage("Opening setting file at |!", fullpath, ...), ..., DTTInfo)
   
  QappCore.openFileForInput(fullpath, fileNumber, "UTF-8")
   
  string jsonContent = ""
   
  while (!(QappCore.EOF(fileNumber)))
  {
    string currentLine = ""
    QappCore.readLine(fileNumber, currentLine)
    QappCore.DTTLogMessage(currentLine, ...)
    jsonContent = jsonContent + currentLine
  }
   
  QappCore.closeFile(fileNumber)
   
  JsonSettings.JsonSettings = jsonContent
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void EditSettingsForm.ButtonOk()
{
  boolean validJson = SH.isValidJson(JsonSettings.JsonSettings)
  if (!(validJson))
  {
    this.showMessage(ERROR, "Non è stato inserito un oggettto json corretto, controllare la sintassi", ...)
    return 
  }
   
   
  string settingsFileName = this.QappCommandHandler.getSettingsFilename()
  string fullpath = QappCore.path() + FH.getSeparator() + settingsFileName
   
  QappCore.deleteFile(fullpath)
  int fileNumber = QappCore.freeFile()
  QappCore.openFileForOutput(fullpath, fileNumber, ...)
   
  QappCore.writeLine(fileNumber, JsonSettings.JsonSettings)
   
  QappCore.closeFile(fileNumber)
   
  this.close(true)
   
  // note: unload event coded too
   
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void EditSettingsForm.ButtonAnnulla()
{
  this.close(false)
  //  
  // note: unload event coded too
   
}


// ──────────────────────────────────

// ********************************************
// Event raised by the form before it is closed
// ********************************************
event EditSettingsForm.Unload(
  inout int Cancel // If set to True, closure of the form is canceled.
  boolean Confirm  // Represents the user's selection for closing modal forms. True if the user confirms their choice, otherwise False.
)
{
  if (this.QappCommandHandler.getTag("loggedIdUtente") == 0)
  {
    Tools.performExit(...)
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void MobileLoginForm.ButtonLogin()
{
  boolean dataValid = false
   
  string errorMessage = ""
   
  // call processDatabase so we can know by the return value if the Qapp has just been inserted in DB or not, based on this we decide the in the doLogin whether to perform or not the PostLoginCode
   
  QappRuntimeBehavior qrb = getQappRuntimeBehavior()
  QappCore.QappCommandHandler.initializeAll()
  QappCore.QappCommandHandler.ProcessDatabase(doNotForce)
  qrb.QappJustInserted = QappCore.QappCommandHandler.getQappInsertedForTheFirstTime()
   
  QappCore.QappCommandHandler.setQappCoreSessionInitiatorType(manualLogin)
   
  // Qapp core normal Qualibus user based login
   
  QappCore.doLogin(loginpanel.Utente, loginpanel.Password, dataValid, errorMessage, ...)
   
  QappCore.DoAfterLogin()
   
  if (dataValid)
  {
    this.close(...)
  }
  if (errorMessage != "")
  {
    this.sendMessage("SET_LOGIN_MESSAGE", null, errorMessage, ...)
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void MobileLoginForm.showForm()
{
  this.setTag("loginTag", true)
  this.show(Modal)
  loginpanel.Utente = ""
  loginpanel.Password = ""
  this.disableAutoInputFeatures()
}


// ──────────────────────────────────

// ************************************************************************
// Event raised when a form sends a message using the SendMessage procedure
// ************************************************************************
event MobileLoginForm.OnSendMessage(
  string Message // Indicates the name of the message
  IDForm Sender  // Identifies the form that sent the message. IDForm type object.
  IDDocument Doc // Optional. Document associated with the message.
  string Par1    // Optional. Parameter associated with the message; can be of any type (except object or generic document).
  string Par2    // Optional. Parameter associated with the message; can be of any type (except object or generic document).
  string Par3    // Optional. Parameter associated with the message; can be of any type (except object or generic document).
  string Par4    // Optional. Parameter associated with the message; can be of any type (except object or generic document).
)
{
  if (Message == "CLOSE_FORM")
  {
    this.close(...)
  }
  else if (Message == "SET_LOGIN_MESSAGE")
  {
    loginpanel.LabelmobileLoginMessage.setVisible(true)
    loginpanel.LabelmobileLoginMessage.caption = Par1
  }
}


// ──────────────────────────────────

// ***********************************************
// Event raised to the form when loading in memory
// ***********************************************
event MobileLoginForm.Load()
{
  // the label is hidden by default and it is made visible by On Send Message when we need to display it
  loginpanel.LabelmobileLoginMessage.setVisible(false)
}


// ──────────────────────────────────

// ******************************************************************************************************************
// makes the username field a email type field so capitalization and auto correction is turned off in a mobile device
// ******************************************************************************************************************
private void MobileLoginForm.disableAutoInputFeatures()
{
  string loginFieldRd3Id = loginpanel.Utente.getRD3ID(...)
   
  string command = formatMessage("\nvar usernameExternalElement=document.getElementById("|1");var usernameInnerInput= usernameExternalElement.querySelector(".text-input");usernameInnerInput.
           setAttribute("type","email");", loginFieldRd3Id, ...)
   
  QappCore.executeOnClient(command)
   
}


// ──────────────────────────────────

// ***********************************************
// Event raised to the form when loading in memory
// ***********************************************
event UploadDocument.Load()
{
  this.hasConfirmButton = false
  uploadpanel.BlobData.setBlobSize(2000000000, 20000000000, ...)
  uploadpanel.BlobData.setEnabled(true)
  uploadpanel.locked = false
}


// ──────────────────────────────────

// **************************************************************************************************************************
// Event raised by the panel after saving the file uploaded by the user to the database or deleting the contents of the blob.
// **************************************************************************************************************************
event UploadDocument.uploadpanel.AfterBLOBUpdate(
  int Column       // An integer specifying which panel field is involved in the update or delete operation. It should be compared with the Me function of the panel fields.
  int Size         // Size of the uploaded file in bytes, or -1 if the contents of the blob have been deleted.
  string Extension // A string containing the extension of the file being loaded.
)
{
  DocumentUploadHelper duh = uploadpanel.document
   
  if (duh)
  {
    duh.setExtension(Extension)
    this.LabelOK()
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void UploadDocument.showForm(
  DocumentUploadHelper doc // 
)
{
  uploadpanel.setDocument(doc, ...)
  uploadpanel.BlobData.enterUploadMode()
  this.show(Modal)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void UploadDocument.LabelOK()
{
  boolean result = false
  DocumentUploadHelper d = uploadpanel.document
  if (d)
  {
    if (d.BlobData != null)
    {
      d.computeBlob()
      result = true
      this.close(result)
    }
    else 
    {
      QappCore.messageBox("Non e' stato caricato nessun file, premere il pulsante {{icon-fa-upload}} per caricare il file e qiuindi premere Conferma, oppure chiudere la finestra se non si vuole procedere al 
            caricamento")
    }
  }
}


// ──────────────────────────────────

// ************************************************************************
// Event raised when a form sends a message using the SendMessage procedure
// ************************************************************************
event OOEditorContainer.OnSendMessage(
  string Message // Indicates the name of the message
  IDForm Sender  // Identifies the form that sent the message. IDForm type object.
  IDDocument Doc // Optional. Document associated with the message.
  string Par1    //  
  string Par2    // Optional. Parameter associated with the message; can be of any type (except object or generic document).
  string Par3    // Optional. Parameter associated with the message; can be of any type (except object or generic document).
  string Par4    // Optional. Parameter associated with the message; can be of any type (except object or generic document).
)
{
  if (Message == OnlyOfficeServiceNotAvailable)
  {
    QappCore.messageBox("Il servizio Onlyoffice non è disponibile")
    Closeform.enabled = true
  }
  else 
  {
    this.FormNotifier.notify(Message, Doc)
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void OOEditorContainer.Closeform()
{
  this.close(false)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void OOEditorContainer.BringToFront()
{
  this.bringToFront()
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void OOEditorContainer.showForm(
  EditorConfiguration configuration // Write a comment for this parameter or press backspace to delete this comment
  string caption                    // 
  FormNotifier notifier             // 
)
{
  this.FormNotifier = notifier
  this.caption = caption
  Editor.sendMessage(setup, configuration, ...)
}


// ──────────────────────────────────



// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void WaitingOoResult.Check()
{
  if (this.isOpen())
  {
    this.Seconds = this.Seconds + 1
  }
  if (this.Seconds == 20)
  {
    this.CloseForm()
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void WaitingOoResult.showForm(
  string filename // Write a comment for this parameter or press backspace to delete this comment
)
{
  this.caption = formatMessage("Attendere", filename, ...)
   
  string htmlDescription = formatMessage("<div class='container'>È in corso la generazione del file <font color=green>|1</font>.<br>Ricorda di <b>archiviare</b> il file per salvare le modifiche.</div>", 
           filename, ...)
  info.Hyperlinkhtmlcontent.text = htmlDescription
   
  this.show(Modal)
  Check.enabled = true
  this.Seconds = 0
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void WaitingOoResult.CloseForm()
{
  this.close(...)
}


// ──────────────────────────────────

// ********************************************
// Event raised by the form before it is closed
// ********************************************
event ProgettiLookup.Unload(
  inout int Cancel // If set to True, closure of the form is canceled.
  boolean Confirm  // Represents the user's selection for closing modal forms. True if the user confirms their choice, otherwise False.
)
{
  LookupHelper.handleUnloadEvent(this.IDForm(), Confirm)
}


// ──────────────────────────────────

// ***********************************************
// Event raised to the form when loading in memory
// ***********************************************
event ProgettiLookup.Load()
{
  LookupHelper.initializeMultiSelectionInLoadEvent(Progetti.IDPanel())
  Ricerca.SearchString = ""
}


// ──────────────────────────────────

// ******************************************************************************
// Event raised by a panel with multiple selection when the selected rows change.
// ******************************************************************************
event ProgettiLookup.Progetti.OnChangeSelection(
  boolean Selected     // A boolean value that is True if the row triggering the event has been selected. The data for the row triggering the event is available, as usual, in the panel's underlying IMDB table.
  boolean Final        // This event is raised for each row whose selection status changes, plus once more at the end of the operation. During this final call, the value of the Final parameter is True.
  inout boolean Cancel // This can be set to True to prevent the selection change (only applies if Final = false).
)
{
  LookupHelper.handleOnChangeSelection(this.IDForm(), Selected, Final, Progetti.IDPROGETTO)
}


// ──────────────────────────────────

// ***********************************************************************
// Event raised by the panel when the data in the active panel row changes
// ***********************************************************************
event ProgettiLookup.Progetti.OnChangeRow()
{
  LookupHelper.handleOnChangeRow(this.IDForm(), Progetti.IDPROGETTO, Progetti.CODPROGETTO + " " + Progetti.DESCRPROGETTO)
}


// ──────────────────────────────────

// **********************************************************************
// Allows the visual properties of individual panel cells to be adjusted.
// **********************************************************************
event ProgettiLookup.Progetti.OnDynamicProperties()
{
  if (Progetti.DATAFINEProgetto == null)
  {
    Progetti.STATO.text = "In corso"
  }
  else 
  {
    Progetti.STATO.text = "Concluso"
  }
}


// ──────────────────────────────────

// ********************************************
// Event raised by the form before it is closed
// ********************************************
event PersonaleLookup.Unload(
  inout int Cancel // If set to True, closure of the form is canceled.
  boolean Confirm  // Represents the user's selection for closing modal forms. True if the user confirms their choice, otherwise False.
)
{
  LookupHelper.handleUnloadEvent(this.IDForm(), Confirm)
}


// ──────────────────────────────────

// ***********************************************
// Event raised to the form when loading in memory
// ***********************************************
event PersonaleLookup.Load()
{
  LookupHelper.initializeMultiSelectionInLoadEvent(Personale.IDPanel())
  Ricerca.SearchString = ""
}


// ──────────────────────────────────

// ******************************************************************************
// Event raised by a panel with multiple selection when the selected rows change.
// ******************************************************************************
event PersonaleLookup.Personale.OnChangeSelection(
  boolean Selected     // A boolean value that is True if the row triggering the event has been selected. The data for the row triggering the event is available, as usual, in the panel's underlying IMDB table.
  boolean Final        // This event is raised for each row whose selection status changes, plus once more at the end of the operation. During this final call, the value of the Final parameter is True.
  inout boolean Cancel // This can be set to True to prevent the selection change (only applies if Final = false).
)
{
  LookupHelper.handleOnChangeSelection(this.IDForm(), Selected, Final, Personale.IDDIPENDENTEEmployee)
}


// ──────────────────────────────────

// ***********************************************************************
// Event raised by the panel when the data in the active panel row changes
// ***********************************************************************
event PersonaleLookup.Personale.OnChangeRow()
{
  LookupHelper.handleOnChangeRow(this.IDForm(), Personale.IDDIPENDENTEEmployee, Personale.COGNOMEEmployee + " " + Personale.NOMEEmployee)
}


// ──────────────────────────────────

// ********************************************
// Event raised by the form before it is closed
// ********************************************
event FunzioniLookup.Unload(
  inout int Cancel // If set to True, closure of the form is canceled.
  boolean Confirm  // Represents the user's selection for closing modal forms. True if the user confirms their choice, otherwise False.
)
{
  LookupHelper.handleUnloadEvent(this.IDForm(), Confirm)
}


// ──────────────────────────────────

// ***********************************************
// Event raised to the form when loading in memory
// ***********************************************
event FunzioniLookup.Load()
{
  LookupHelper.initializeMultiSelectionInLoadEvent(Funzioni.IDPanel())
  Ricerca.SearchString = ""
}


// ──────────────────────────────────

// ******************************************************************************
// Event raised by a panel with multiple selection when the selected rows change.
// ******************************************************************************
event FunzioniLookup.Funzioni.OnChangeSelection(
  boolean Selected     // A boolean value that is True if the row triggering the event has been selected. The data for the row triggering the event is available, as usual, in the panel's underlying IMDB table.
  boolean Final        // This event is raised for each row whose selection status changes, plus once more at the end of the operation. During this final call, the value of the Final parameter is True.
  inout boolean Cancel // This can be set to True to prevent the selection change (only applies if Final = false).
)
{
  LookupHelper.handleOnChangeSelection(this.IDForm(), Selected, Final, Funzioni.IDFUNZIONE)
}


// ──────────────────────────────────

// ***********************************************************************
// Event raised by the panel when the data in the active panel row changes
// ***********************************************************************
event FunzioniLookup.Funzioni.OnChangeRow()
{
  LookupHelper.handleOnChangeRow(this.IDForm(), Funzioni.IDFUNZIONE, Funzioni.CODFUNZIONE + " " + Funzioni.DESCRFUNZIONE)
}


// ──────────────────────────────────

// ********************************************
// Event raised by the form before it is closed
// ********************************************
event EventiLookup.Unload(
  inout int Cancel // If set to True, closure of the form is canceled.
  boolean Confirm  // Represents the user's selection for closing modal forms. True if the user confirms their choice, otherwise False.
)
{
  LookupHelper.handleUnloadEvent(this.IDForm(), Confirm)
}


// ──────────────────────────────────

// ***********************************************
// Event raised to the form when loading in memory
// ***********************************************
event EventiLookup.Load()
{
  LookupHelper.initializeMultiSelectionInLoadEvent(Eventi.IDPanel())
  Ricerca.SearchString = ""
}


// ──────────────────────────────────

// ******************************************************************************
// Event raised by a panel with multiple selection when the selected rows change.
// ******************************************************************************
event EventiLookup.Eventi.OnChangeSelection(
  boolean Selected     // A boolean value that is True if the row triggering the event has been selected. The data for the row triggering the event is available, as usual, in the panel's underlying IMDB table.
  boolean Final        // This event is raised for each row whose selection status changes, plus once more at the end of the operation. During this final call, the value of the Final parameter is True.
  inout boolean Cancel // This can be set to True to prevent the selection change (only applies if Final = false).
)
{
  LookupHelper.handleOnChangeSelection(this.IDForm(), Selected, Final, Eventi.IDEVENTO)
}


// ──────────────────────────────────

// ***********************************************************************
// Event raised by the panel when the data in the active panel row changes
// ***********************************************************************
event EventiLookup.Eventi.OnChangeRow()
{
  LookupHelper.handleOnChangeRow(this.IDForm(), Eventi.IDEVENTO, Eventi.NROEVENTO + " " + Eventi.DESCRTITOLOEvento)
}


// ──────────────────────────────────

// ***************************************************************************************************************************************************************************
// tagValue parameter is a suffix value of object tag (Idmap) associated with this form it will be used on unload event to get the Idmap to populate the results in to map.   
// 
// to retrieve one has to do following  
// 
// IDMap idm = this.getObjectTag("result_" + toString(this.TagIDSuffix))
// ***************************************************************************************************************************************************************************
public void ArticoliLookup.showForm(
  int tagvalue // Write a comment for this parameter or press backspace to delete this comment
)
{
  this.show(...)
  Ricerca.Searchstring = ""
  this.bringToFront()
}


// ──────────────────────────────────

// ********************************************
// Event raised by the form before it is closed
// ********************************************
event ArticoliLookup.Unload(
  inout int Cancel // If set to True, closure of the form is canceled.
  boolean Confirm  // Represents the user's selection for closing modal forms. True if the user confirms their choice, otherwise False.
)
{
  LookupHelper.handleUnloadEvent(this.IDForm(), Confirm)
}


// ──────────────────────────────────

// ***********************************************
// Event raised to the form when loading in memory
// ***********************************************
event ArticoliLookup.Load()
{
  LookupHelper.initializeMultiSelectionInLoadEvent(Articoli.IDPanel())
  Ricerca.SearchString = ""
}


// ──────────────────────────────────

// ******************************************************************************
// Event raised by a panel with multiple selection when the selected rows change.
// ******************************************************************************
event ArticoliLookup.Articoli.OnChangeSelection(
  boolean Selected     // A boolean value that is True if the row triggering the event has been selected. The data for the row triggering the event is available, as usual, in the panel's underlying IMDB table.
  boolean Final        // This event is raised for each row whose selection status changes, plus once more at the end of the operation. During this final call, the value of the Final parameter is True.
  inout boolean Cancel // This can be set to True to prevent the selection change (only applies if Final = false).
)
{
  LookupHelper.handleOnChangeSelection(this.IDForm(), Selected, Final, Articoli.IDARTICOLOARTANAGRAFICA)
}


// ──────────────────────────────────

// ***********************************************************************
// Event raised by the panel when the data in the active panel row changes
// ***********************************************************************
event ArticoliLookup.Articoli.OnChangeRow()
{
  LookupHelper.handleOnChangeRow(this.IDForm(), Articoli.IDARTICOLOARTANAGRAFICA, Articoli.CODARTICOLOARTANAGRAFICA + " " + Articoli.DESCRARTICOLOARTANAGRAFICA)
}


// ──────────────────────────────────

// ********************************************
// Event raised by the form before it is closed
// ********************************************
event PrivatiLookup.Unload(
  inout int Cancel // If set to True, closure of the form is canceled.
  boolean Confirm  // Represents the user's selection for closing modal forms. True if the user confirms their choice, otherwise False.
)
{
  LookupHelper.handleUnloadEvent(this.IDForm(), Confirm)
}


// ──────────────────────────────────

// ***********************************************
// Event raised to the form when loading in memory
// ***********************************************
event PrivatiLookup.Load()
{
  LookupHelper.initializeMultiSelectionInLoadEvent(Privati.IDPanel())
  Ricerca.SearchString = ""
}


// ──────────────────────────────────

// ******************************************************************************
// Event raised by a panel with multiple selection when the selected rows change.
// ******************************************************************************
event PrivatiLookup.Privati.OnChangeSelection(
  boolean Selected     // A boolean value that is True if the row triggering the event has been selected. The data for the row triggering the event is available, as usual, in the panel's underlying IMDB table.
  boolean Final        // This event is raised for each row whose selection status changes, plus once more at the end of the operation. During this final call, the value of the Final parameter is True.
  inout boolean Cancel // This can be set to True to prevent the selection change (only applies if Final = false).
)
{
  LookupHelper.handleOnChangeSelection(this.IDForm(), Selected, Final, Privati.IDCITTADINICITANAGRAFICA)
}


// ──────────────────────────────────

// ***********************************************************************
// Event raised by the panel when the data in the active panel row changes
// ***********************************************************************
event PrivatiLookup.Privati.OnChangeRow()
{
  LookupHelper.handleOnChangeRow(this.IDForm(), Privati.IDCITTADINICITANAGRAFICA, Privati.COGNOMECITANAGRAFICA + " " + Privati.NOMECITANAGRAFICA)
}


// ──────────────────────────────────

// ********************************************
// Event raised by the form before it is closed
// ********************************************
event AltreAnagraficheLookup.Unload(
  inout int Cancel // If set to True, closure of the form is canceled.
  boolean Confirm  // Represents the user's selection for closing modal forms. True if the user confirms their choice, otherwise False.
)
{
  LookupHelper.handleUnloadEvent(this.IDForm(), Confirm)
}


// ──────────────────────────────────

// ***********************************************
// Event raised to the form when loading in memory
// ***********************************************
event AltreAnagraficheLookup.Load()
{
  LookupHelper.initializeMultiSelectionInLoadEvent(AltreAnagrafiche.IDPanel())
  Ricerca.SearchString = ""
}


// ──────────────────────────────────

// ******************************************************************************
// Event raised by a panel with multiple selection when the selected rows change.
// ******************************************************************************
event AltreAnagraficheLookup.AltreAnagrafiche.OnChangeSelection(
  boolean Selected     // A boolean value that is True if the row triggering the event has been selected. The data for the row triggering the event is available, as usual, in the panel's underlying IMDB table.
  boolean Final        // This event is raised for each row whose selection status changes, plus once more at the end of the operation. During this final call, the value of the Final parameter is True.
  inout boolean Cancel // This can be set to True to prevent the selection change (only applies if Final = false).
)
{
  LookupHelper.handleOnChangeSelection(this.IDForm(), Selected, Final, AltreAnagrafiche.IDCespite)
}


// ──────────────────────────────────

// ***********************************************************************
// Event raised by the panel when the data in the active panel row changes
// ***********************************************************************
event AltreAnagraficheLookup.AltreAnagrafiche.OnChangeRow()
{
  LookupHelper.handleOnChangeRow(this.IDForm(), AltreAnagrafiche.IDCespite, AltreAnagrafiche.CodeCespite + " " + AltreAnagrafiche.DescriptionCespite)
}


// ──────────────────────────────────

// ********************************************
// Event raised by the form before it is closed
// ********************************************
event ClientiFornitoriLookup.Unload(
  inout int Cancel // If set to True, closure of the form is canceled.
  boolean Confirm  // Represents the user's selection for closing modal forms. True if the user confirms their choice, otherwise False.
)
{
  LookupHelper.handleUnloadEvent(this.IDForm(), Confirm)
}


// ──────────────────────────────────

// ***********************************************
// Event raised to the form when loading in memory
// ***********************************************
event ClientiFornitoriLookup.Load()
{
  LookupHelper.initializeMultiSelectionInLoadEvent(ClientiFornitori.IDPanel())
  Ricerca.SearchString = ""
}


// ──────────────────────────────────

// ******************************************************************************
// Event raised by a panel with multiple selection when the selected rows change.
// ******************************************************************************
event ClientiFornitoriLookup.ClientiFornitori.OnChangeSelection(
  boolean Selected     // A boolean value that is True if the row triggering the event has been selected. The data for the row triggering the event is available, as usual, in the panel's underlying IMDB table.
  boolean Final        // This event is raised for each row whose selection status changes, plus once more at the end of the operation. During this final call, the value of the Final parameter is True.
  inout boolean Cancel // This can be set to True to prevent the selection change (only applies if Final = false).
)
{
  LookupHelper.handleOnChangeSelection(this.IDForm(), Selected, Final, ClientiFornitori.IDCONTOGCFANAGRAFICA)
}


// ──────────────────────────────────

// ***********************************************************************
// Event raised by the panel when the data in the active panel row changes
// ***********************************************************************
event ClientiFornitoriLookup.ClientiFornitori.OnChangeRow()
{
  LookupHelper.handleOnChangeRow(this.IDForm(), ClientiFornitori.IDCONTOGCFANAGRAFICA, ClientiFornitori.RAGIONESOCIALEGCFANAGRAFICA)
}


// ──────────────────────────────────

// ********************************************
// Event raised by the form before it is closed
// ********************************************
event DocumentiLookup.Unload(
  inout int Cancel // If set to True, closure of the form is canceled.
  boolean Confirm  // Represents the user's selection for closing modal forms. True if the user confirms their choice, otherwise False.
)
{
  LookupHelper.handleUnloadEvent(this.IDForm(), Confirm)
}


// ──────────────────────────────────

// ***********************************************
// Event raised to the form when loading in memory
// ***********************************************
event DocumentiLookup.Load()
{
  LookupHelper.initializeMultiSelectionInLoadEvent(Documenti.IDPanel())
  Ricerca.SearchString = ""
}


// ──────────────────────────────────

// ******************************************************************************
// Event raised by a panel with multiple selection when the selected rows change.
// ******************************************************************************
event DocumentiLookup.Documenti.OnChangeSelection(
  boolean Selected     // A boolean value that is True if the row triggering the event has been selected. The data for the row triggering the event is available, as usual, in the panel's underlying IMDB table.
  boolean Final        // This event is raised for each row whose selection status changes, plus once more at the end of the operation. During this final call, the value of the Final parameter is True.
  inout boolean Cancel // This can be set to True to prevent the selection change (only applies if Final = false).
)
{
  LookupHelper.handleOnChangeSelection(this.IDForm(), Selected, Final, Documenti.IDDOCUMENTOVDOCREVISIONIVALIDE)
}


// ──────────────────────────────────

// ***********************************************************************
// Event raised by the panel when the data in the active panel row changes
// ***********************************************************************
event DocumentiLookup.Documenti.OnChangeRow()
{
  LookupHelper.handleOnChangeRow(this.IDForm(), Documenti.IDDOCUMENTOVDOCREVISIONIVALIDE, Documenti.DESCRDOCUMENTOumento)
}


// ──────────────────────────────────

// ********************************************
// Event raised by the form before it is closed
// ********************************************
event ContattiLookup.Unload(
  inout int Cancel // If set to True, closure of the form is canceled.
  boolean Confirm  // Represents the user's selection for closing modal forms. True if the user confirms their choice, otherwise False.
)
{
  LookupHelper.handleUnloadEvent(this.IDForm(), Confirm)
}


// ──────────────────────────────────

// ***********************************************
// Event raised to the form when loading in memory
// ***********************************************
event ContattiLookup.Load()
{
  LookupHelper.initializeMultiSelectionInLoadEvent(GCFCONTATTI.IDPanel())
  Ricerca.SearchString = ""
}


// ──────────────────────────────────

// ******************************************************************************
// Event raised by a panel with multiple selection when the selected rows change.
// ******************************************************************************
event ContattiLookup.GCFCONTATTI.OnChangeSelection(
  boolean Selected     // A boolean value that is True if the row triggering the event has been selected. The data for the row triggering the event is available, as usual, in the panel's underlying IMDB table.
  boolean Final        // This event is raised for each row whose selection status changes, plus once more at the end of the operation. During this final call, the value of the Final parameter is True.
  inout boolean Cancel // This can be set to True to prevent the selection change (only applies if Final = false).
)
{
  LookupHelper.handleOnChangeSelection(this.IDForm(), Selected, Final, GCFCONTATTI.IDCONTATTOGCFCONTATTI)
}


// ──────────────────────────────────

// ***********************************************************************
// Event raised by the panel when the data in the active panel row changes
// ***********************************************************************
event ContattiLookup.GCFCONTATTI.OnChangeRow()
{
  LookupHelper.handleOnChangeRow(this.IDForm(), GCFCONTATTI.IDCONTATTOGCFCONTATTI, GCFCONTATTI.COGNOMEGCFCONTATTI + " " + GCFCONTATTI.NOMEGCFCONTATTI)
}


// ──────────────────────────────────

// ********************************************
// Event raised by the form before it is closed
// ********************************************
event InterventoLookup.Unload(
  inout int Cancel // If set to True, closure of the form is canceled.
  boolean Confirm  // Represents the user's selection for closing modal forms. True if the user confirms their choice, otherwise False.
)
{
  LookupHelper.handleUnloadEvent(this.IDForm(), Confirm)
}


// ──────────────────────────────────

// ***********************************************
// Event raised to the form when loading in memory
// ***********************************************
event InterventoLookup.Load()
{
  LookupHelper.initializeMultiSelectionInLoadEvent(Interventi.IDPanel())
  Ricerca.SearchString = ""
}


// ──────────────────────────────────

// ******************************************************************************
// Event raised by a panel with multiple selection when the selected rows change.
// ******************************************************************************
event InterventoLookup.Interventi.OnChangeSelection(
  boolean Selected     // A boolean value that is True if the row triggering the event has been selected. The data for the row triggering the event is available, as usual, in the panel's underlying IMDB table.
  boolean Final        // This event is raised for each row whose selection status changes, plus once more at the end of the operation. During this final call, the value of the Final parameter is True.
  inout boolean Cancel // This can be set to True to prevent the selection change (only applies if Final = false).
)
{
  LookupHelper.handleOnChangeSelection(this.IDForm(), Selected, Final, Interventi.IDTESTATAOPIntervento)
}


// ──────────────────────────────────

// ***********************************************************************
// Event raised by the panel when the data in the active panel row changes
// ***********************************************************************
event InterventoLookup.Interventi.OnChangeRow()
{
  LookupHelper.handleOnChangeRow(this.IDForm(), Interventi.IDTESTATAOPIntervento, Interventi.Titolo + " " + if(isNull(Interventi.IDTIPOESITOIntervento), "", "(" + Interventi.
        DESCRTIPOESITOMANTIPIESITOOP + ")"))
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void QvmManagementForm.UpdateUI(
  string:qvmBackendManagerAreas area // 
  int:qvmBackendStatus status        // 
)
{
  string enable = "{{icon-fa-check}} Abilitato"
  string disabled = "{{icon-fa-xmark}} Disabilitato"
  string updating = "{{icon-fa-spinner}}"
   
  switch (area)
  {
    case proxy:
      switch (status)
      {
         case QvmStatus_Enable:
           AdminFunctions.LabelProxyStatus.text = enable
           AdminFunctions.ButtonEnableProxy.setEnabled(false)
           AdminFunctions.ButtonDisableProxy.setEnabled(true)
         break
         case QvmStatus_Disabled:
           AdminFunctions.LabelProxyStatus.text = disabled
           AdminFunctions.ButtonEnableProxy.setEnabled(true)
           AdminFunctions.ButtonDisableProxy.setEnabled(false)
         break
         default:
           AdminFunctions.LabelProxyStatus.text = updating
           AdminFunctions.ButtonEnableProxy.setEnabled(true)
           AdminFunctions.ButtonDisableProxy.setEnabled(true)
         break
      }
    break
    case tunnel:
      switch (status)
      {
         case QvmStatus_Enable:
           AdminFunctions.LabelTunnelStatus.text = enable
           AdminFunctions.ButtonEnableTunnel.setEnabled(false)
           AdminFunctions.ButtonDisableTunnel.setEnabled(true)
         break
         case QvmStatus_Disabled:
           AdminFunctions.LabelTunnelStatus.text = disabled
           AdminFunctions.ButtonEnableTunnel.setEnabled(true)
           AdminFunctions.ButtonDisableTunnel.setEnabled(false)
         break
         default:
           AdminFunctions.LabelTunnelStatus.text = updating
           AdminFunctions.ButtonEnableTunnel.setEnabled(true)
           AdminFunctions.ButtonDisableTunnel.setEnabled(true)
         break
      }
    break
    case updates:
      switch (status)
      {
         case QvmStatus_Enable:
           AdminFunctions.LabelUpdatesStatus.text = enable
           AdminFunctions.ButtonEnableUpdates.setEnabled(false)
           AdminFunctions.ButtonDisableUpdates.setEnabled(true)
         break
         case QvmStatus_Disabled:
           AdminFunctions.LabelUpdatesStatus.text = disabled
           AdminFunctions.ButtonEnableUpdates.setEnabled(true)
           AdminFunctions.ButtonDisableUpdates.setEnabled(false)
         break
         default:
           AdminFunctions.LabelUpdatesStatus.text = updating
           AdminFunctions.ButtonEnableUpdates.setEnabled(true)
           AdminFunctions.ButtonDisableUpdates.setEnabled(true)
         break
      }
    break
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void QvmManagementForm.EnableProxy()
{
  int status = QvmOptionsManager.proxyEnable()
  this.UpdateUI(proxy, status)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void QvmManagementForm.DisableProxy()
{
  int status = QvmOptionsManager.proxyDisable()
  this.UpdateUI(proxy, status)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void QvmManagementForm.StatusProxy()
{
  int status = QvmOptionsManager.proxyStatus()
  this.UpdateUI(proxy, status)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void QvmManagementForm.EnableTunnel()
{
  int status = QvmOptionsManager.tunnelEnable()
  this.UpdateUI(tunnel, status)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void QvmManagementForm.DisableTunnel()
{
  int status = QvmOptionsManager.tunnelDisable()
  this.UpdateUI(tunnel, status)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void QvmManagementForm.StatusTunnel()
{
  int status = QvmOptionsManager.tunnelStatus()
  this.UpdateUI(tunnel, status)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void QvmManagementForm.EnableUpdates()
{
  int status = QvmOptionsManager.updatesEnable()
  this.UpdateUI(updates, status)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void QvmManagementForm.DisableUpdates()
{
  int status = QvmOptionsManager.updatesDisable()
  this.UpdateUI(updates, status)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void QvmManagementForm.StatusUpdates()
{
  int status = QvmOptionsManager.updatesStatus()
  this.UpdateUI(updates, status)
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void QvmManagementForm.UpdateQVMSettings()
{
  IDMap idm = QvmOptionsManager.getQvmStatus()
  this.UpdateUI(proxy, idm.getValue(proxy))
  this.UpdateUI(tunnel, idm.getValue(tunnel))
  this.UpdateUI(updates, idm.getValue(updates))
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void QvmManagementForm.ButtonConnessioniQVM()
{
   
  int i = QappCore.messageConfirmEx("Il controllo delle connessioni potrebbe richiedere fino a 30 secondi, continuare?", "Si;No")
   
  if (i == 1)
  {
    string connections = QvmOptionsManager.getQvmConnections()
    QappCore.messageBox(connections)
  }
}


// ──────────────────────────────────



// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static int QvmOptionsManager.proxyEnable()
{
  string response = QvmBackendManager.executeCommand(proxyEnable)
   
  if (response == null)
    return QvmStatus_Unknown
   
  if (response = "proxy enabled")
    return QvmStatus_Enable
   
  if (response = "proxy disabled")
    return QvmStatus_Disabled
   
  return QvmStatus_Unknown
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static int QvmOptionsManager.proxyDisable()
{
  string response = QvmBackendManager.executeCommand(proxyDisable)
   
  if (response == null)
    return QvmStatus_Unknown
   
  if (response = "proxy enabled")
    return QvmStatus_Enable
   
  if (response = "proxy disabled")
    return QvmStatus_Disabled
   
  return QvmStatus_Unknown
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static int QvmOptionsManager.proxyStatus()
{
  string response = QvmBackendManager.executeCommand(proxyRead)
   
  if (response == null)
    return QvmStatus_Unknown
   
  if (response = "proxy enabled")
    return QvmStatus_Enable
   
  if (response = "proxy disabled")
    return QvmStatus_Disabled
   
  return QvmStatus_Unknown
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static int QvmOptionsManager.tunnelEnable()
{
  string response = QvmBackendManager.executeCommand(tunnelEnable)
   
  if (response == null)
    return QvmStatus_Unknown
   
  if (response = "tunnel enabled")
    return QvmStatus_Enable
   
  if (response = "tunnel disabled")
    return QvmStatus_Disabled
   
  return QvmStatus_Unknown
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static int QvmOptionsManager.tunnelDisable()
{
  string response = QvmBackendManager.executeCommand(tunnelDisable)
   
  if (response == null)
    return QvmStatus_Unknown
   
  if (response = "tunnel enabled")
    return QvmStatus_Enable
   
  if (response = "tunnel disabled")
    return QvmStatus_Disabled
   
  return QvmStatus_Unknown
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static int QvmOptionsManager.tunnelStatus()
{
  string response = QvmBackendManager.executeCommand(tunnelRead)
   
  if (response == null)
    return QvmStatus_Unknown
   
  if (response = "tunnel enabled")
    return QvmStatus_Enable
   
  if (response = "tunnel disabled")
    return QvmStatus_Disabled
   
  return QvmStatus_Unknown
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static int QvmOptionsManager.updatesEnable()
{
  string response = QvmBackendManager.executeCommand(updatesEnable)
   
  if (response == null)
    return QvmStatus_Unknown
   
  if (response = "updates enabled")
    return QvmStatus_Enable
   
  if (response = "updates disabled")
    return QvmStatus_Disabled
   
  return QvmStatus_Unknown
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static int QvmOptionsManager.updatesDisable()
{
  string response = QvmBackendManager.executeCommand(updatesDisable)
   
  if (response == null)
    return QvmStatus_Unknown
   
  if (response = "updates enabled")
    return QvmStatus_Enable
   
  if (response = "updates disabled")
    return QvmStatus_Disabled
   
  return QvmStatus_Unknown
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static int QvmOptionsManager.updatesStatus()
{
  string response = QvmBackendManager.executeCommand(updatesRead)
   
  if (response == null)
    return QvmStatus_Unknown
   
  if (response = "updates enabled")
    return QvmStatus_Enable
   
  if (response = "updates disabled")
    return QvmStatus_Disabled
   
  return QvmStatus_Unknown
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static IDMap QvmOptionsManager.getQvmStatus()
{
  IDMap idm = new()
  idm.setValue(proxy, QvmStatus_Unknown)
  idm.setValue(tunnel, QvmStatus_Unknown)
  idm.setValue(updates, QvmStatus_Unknown)
   
   
  string response = QvmBackendManager.executeCommand(statusRead)
   
  if (find(response, "proxy enabled", ...) > 0)
  {
    idm.remove(proxy)
    idm.setValue(proxy, QvmStatus_Enable)
  }
   
  if (find(response, "proxy disabled", ...) > 0)
  {
    idm.remove(proxy)
    idm.setValue(proxy, QvmStatus_Disabled)
  }
   
   
  if (find(response, "tunnel enabled", ...) > 0)
  {
    idm.remove(tunnel)
    idm.setValue(tunnel, QvmStatus_Enable)
  }
   
  if (find(response, "tunnel disabled", ...) > 0)
  {
    idm.remove(tunnel)
    idm.setValue(tunnel, QvmStatus_Disabled)
  }
   
  if (find(response, "updates enabled", ...) > 0)
  {
    idm.remove(updates)
    idm.setValue(updates, QvmStatus_Enable)
  }
   
  if (find(response, "updates disabled", ...) > 0)
  {
    idm.remove(updates)
    idm.setValue(updates, QvmStatus_Disabled)
  }
   
  return idm
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public static string QvmOptionsManager.getQvmConnections()
{
  string response = QvmBackendManager.executeCommand(connectionsRead)
   
  // to be more readable
  response = replace(response, " | ", "<br/>")
  response = replace(response, " OK", " <span style="color: darkgreen;font-weight: bold;">OK</span>")
  response = replace(response, " FAILED", " <span style="color: darkred;font-weight: bold;">FAILED</span>")
   
  return response
}


// ──────────────────────────────────

// ******************************************************************************
// one per day the Reports folder content is cleared to ensure space is preserved
// ******************************************************************************
public void QappCore.ClearReportsFolderTimer()
{
  int currentHour = hour(now())
  boolean itIs3InTheMorning = (currentHour == 3)
   
  if (itIs3InTheMorning)
    QappCommand.deleteAllFilesInReportsFolder()
}


// ──────────────────────────────────

// ************************************************************
// Spiega quale elaborazione viene eseguita da questa procedura
// ************************************************************
public void QappCore.FilestreamWorkaroundHourlyQuery()
{
  // only at the beginning of each hour this query is executed, itinvolves the creation of a sort of temporary index on the DOC_FILES table to avoid slowdowns in the subsequent use of
  // addDocumentiComunicazionFormFile also just after the first app start it will run
  if (minute(now()) == 0 or !(QappCore.HourlyQueryRunAtLeastOnce))
  {
    // by setting the timeout to zero we are sure that the query will be executed and will not time out
    QualibusDB.timeout = 0
    //  
    // the first time after a restart of the server or Sql Server service this query will take "30 minutes", all other times it will be almost instantaneous and will not give any benefit
    QualibusDB.SQLExecute("select top 1 DOCUMENT from DOC_FILES where DOCUMENT IS NOT NULL")
     
    // set the flag to true so on the next timer event this flag will be ignored (!true is false ==>  minute(now())==0 or false))
    QappCore.HourlyQueryRunAtLeastOnce = true
     
     
  }
  else 
  {
     
    // do notthing
     
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
private void QappCore.InitializeFilestreamWorkaround()
{
  // initialize the global flag to control timer execution
  if (X.inServerSession())
  {
    QappCore.HourlyQueryRunAtLeastOnce = false
  }
}


// ──────────────────────────────────

// ****************************************
// Describe what this procedure is used for
// ****************************************
public void QappCore.OneWayUpgraderTimer()
{
   
  // IMPORTANT: THIS TIMER FIRES TWICE: ONE FOR RTF AND ONE FOR XML
  OneWayUpgraderTimer.enabled = false
   
  // initialize all records so they all appear in the UI too:
  // RTF FIRST...
  if (QappCore.OneWayUpgraderRtf != null)
  {
    if (QappCore.OneWayUpgraderRtf.dataConversionNeedsToBePerfomed())
      QappCore.OneWayUpgraderRtf.initializeProgressElements()
  }
   
  // ...AND XML JUST AFTER
  if (QappCore.OneWayUpgraderXml != null)
  {
    if (QappCore.OneWayUpgraderXml.dataConversionNeedsToBePerfomed())
      QappCore.OneWayUpgraderXml.initializeProgressElements()
  }
   
  // THEN START THE FIRST CONVERSION (FIRST TIMER TICK)
  if (QappCore.OneWayUpgraderRtf != null)
  {
     
    boolean performRtfConversionNow = QappCore.OneWayUpgraderRtf.dataConversionNeedsToBePerfomed()
    if (performRtfConversionNow)
    {
      QappCore.OneWayUpgrader = QappCore.OneWayUpgraderRtf
      QappCore.OneWayUpgrader.ConversionSuccess = true
      OneWayUpgraderPerformConversionTimer.enabled = true
      return 
    }
  }
   
  // THEN THE SECOND TICK (the second tick will not go in "return" above since dataCovnersionNeedsToBePerformed will be false for rtf so it will go below:
  if (QappCore.OneWayUpgraderXml != null)
  {
    boolean performXmlConversionNow = QappCore.OneWayUpgraderXml.dataConversionNeedsToBePerfomed()
    if (performXmlConversionNow)
    {
      QappCore.OneWayUpgrader = QappCore.OneWayUpgraderXml
      QappCore.OneWayUpgrader.ConversionSuccess = true
      OneWayUpgraderPerformConversionTimer.enabled = true
    }
  }
}
