Source: Router/job/utilities.js

import  {query,UUID2hex,HEX2uuid} from '@commtool/sql-query'
import { parseTimestampToSeconds } from '../../utils/parseTimestamp.js'
import {errorLoggerUpdate} from '../../utils/requestLogger.js'
import {renderObject} from '../../utils/renderTemplates.js'
import {Templates} from '../../utils/compileTemplates.js'
import {match as matchAchievement} from '../../utils/objectfilter/filters/achievement.js'

import './../../types.js';

/**
 * Checks if a person is qualified for a job based on their achievements.
 * 
 * @param {Buffer} UIDperson - The unique identifier of the person to check qualifications for.
 * @param {Array|Object} qualifications - The qualifications/achievements required for the job.
 * @param {number} [timestamp] - Optional Unix timestamp to check qualifications at a specific point in time.
 * @returns {Promise<number>} - Returns 1 if the person is qualified, 0 if not qualified, and undefined if an error occurs.
 * @throws {Error} - Logs error to errorLoggerUpdate if query fails.
 */
export const jobQualified=async (UIDperson,qualifications,timestamp)=>
{
    try {
        const asOf=timestamp ? `FOR SYSTEM_TIME AS OF FROM_UNIXTIME(${timestamp})` : ''
        // check if person is qualified for this job
        const fullfilled = await query(`SELECT ObjectBase.UIDBelongsTo, Links.UIDTarget FROM ObjectBase ${asOf}
            INNER JOIN Links ${asOf} ON (Links.UID=ObjectBase.UID)
            WHERE ObjectBase.UIDBelongsTo=?  AND Links.Type IN ('member','memberA','achievement') AND ObjectBase.Type='achievement'
            GROUP BY Links.UIDTarget `,[UIDperson])
        const qualies=fullfilled.map(el=>(HEX2uuid(el.UIDTarget)))

        if(qualifications && matchAchievement(qualies,qualifications))
        {
            return 1
        }
        return 0
    }
    catch(e)
    {
        errorLoggerUpdate(e)
    }

}



/**
 * Recreates job objects in the database for the provieded job UIDs (array)
 * 
 * @async
 * @function recreateJobs
 * @param {Object} req - The request object containing session information.
 * @param {Array} jobs - Array of job objects to be recreated.
 * @param {number} timestamp - The timestamp to use for updating objects.
 * @param {boolean} [requalify=false] - Whether to requalify the jobs.
 * @throws {Error} Logs error if job recreation fails.
 * 
 * @description
 * This function iterates through a list of jobs and updates their corresponding
 * entries in the ObjectBase database table. It processes job qualification status,
 * renders objects from templates, and updates various fields including Title, 
 * qualification index, hierarchie, stage, gender */
export const recreateJobs=async (req,jobs,timestamp,requalify=false)=>
{

    try {
        const root=req.session.root
        const template= Templates[root].job

        for (const job of jobs)
        {
            let qualified= job.qualified ? 1 : 0
            if(requalify)
            {

                const functionData=JSON.parse(job.FunctionData)
                qualified=await jobQualified(job.UIDperson,functionData.qualification ?? {},timestamp)
            }
            let jobData={}
            try
            {
                jobData=JSON.parse(job.JobData)
            }
            catch(e)
            {
                console.error('json parse error recreateJobs in jobs.js',e,job.JobData)
                errorLoggerUpdate(e)
            }
            const Data={
                ...jobData,
                UID:HEX2uuid(job.UID),
                qualified:qualified,
                function:{...JSON.parse(job.FunctionData),functionUID:HEX2uuid(job.UIDfunction)},
            }
            const object=await renderObject(template,{...Data,member:JSON.parse(job.MemberData)},req)
            // get old object dqta and update, if there is a difference
            const oldObject=await query(`SELECT ObjectBase.Title,ObjectBase.dindex, ObjectBase.hierarchie, ObjectBase.stage, ObjectBase.gender
                 FROM ObjectBase WHERE UID=?`,[job.UID])
            if(oldObject.length===0)
            {
                throw new Error('job not found')
            }
            const objectBase=oldObject[0]   
            if(objectBase.Title!==object.Title || objectBase.dindex!==qualified || objectBase.hierarchie!==object.hierarchie || objectBase.stage!==object.stage 
                || objectBase.gender!==object.gender)
            {
                await query( `UPDATE ObjectBase SET 
                            Title=?,dindex=?,hierarchie=?,stage=?,gender=?,Data=?
                            WHERE UID=?`, 
                    [object.Title,qualified,
                            object.hierarchie,object.stage,object.gender,JSON.stringify(Data),job.UID],
                    {backDate:Math.max(timestamp,job.ValidFrom)}) 
            }
        
        }
    }
    catch(e)
    {
        errorLoggerUpdate(e)
    }
  
}



/**
 * Recreates all jobs for an organization based on templates.
 * 
 * This function queries the database for all job objects belonging to the organization,
 * optionally as of a specific timestamp, and then recreates them. It retrieves related 
 * data such as person, function, group, and member information needed for job recreation.
 * 
 * @param {Object} req - Express request object
 * @param {Object} req.query - Query parameters
 * @param {string} [req.query.timestamp] - Optional timestamp in milliseconds for point-in-time retrieval
 * @param {Object} req.session - Session data
 * @param {string} req.session.root - Root UID of the organization
 * @param {Object} res - Express response object
 * @returns {Promise<void>} - JSON response indicating success or failure
 * @throws {Error} - Logs errors to the error logger
 */
export const recreateJobsOrga=  async (req,res)=>
{
    // recreate all objects of the organisation via the template
    let timestamp = parseTimestampToSeconds(req.query.timestamp ? String(req.query.timestamp) : undefined)
    const asOf=timestamp ? `FOR SYSTEM_TIME AS OF FROM_UNIXTIME(${timestamp})` : ''
    const requalify=req.query?.['requalify'] ? req.query['requalify'] === 'true' : true
    try{
        if(!timestamp)
            timestamp=Date.now()/1000
        const jobs=await query(`SELECT ObjectBase.UID,ObjectBase.Data AS JobData,ObjectBase.dindex as qualified, PGroup.Data AS GroupData,
                          Person.UID AS UIDperson,FunctionT.Data AS FunctionData, FunctionT.UID AS UIDfunction,
                          Member.Data AS MemberData ,UNIX_TIMESTAMP(latest.ValidFrom) AS ValidFrom 
                FROM ObjectBase ${asOf}
                INNER JOIN ObjectBase AS latest ON (ObjectBase.UID=latest.UID)
                INNER JOIN Links ${asOf} ON (Links.UID=ObjectBase.UID AND Links.Type IN ('member','memberA')) 
                INNER JOIN Links ${asOf} AS GLink ON (GLink.UID=ObjectBase.UID AND GLink.Type='memberA') 
                INNER JOIN ObjectBase ${asOf} AS PGroup ON (PGroup.UID=GLink.UIDTarget)
                INNER JOIN ObjectBase ${asOf} AS Person ON (ObjectBase.UIDBelongsTo=Person.UID AND Person.Type IN ('person','extern'))
                INNER JOIN Links ${asOf} AS FLink ON (FLink.UIDTarget=ObjectBase.UID AND FLink.Type='function')
                INNER JOIN ObjectBase ${asOf} AS FunctionT ON (FunctionT.UID=FLink.UID)
                INNER JOIN Member ON (Member.UID=ObjectBase.UIDBelongsTo)
                WHERE ObjectBase.Type='job' AND Links.UIDTarget=?
                GROUP BY ObjectBase.UID`,
                [UUID2hex(req.session.root)])
        recreateJobs(req,jobs,timestamp, requalify)
        res.json({success:true})
    }
    catch(e)
    {
        errorLoggerUpdate(e)
    }

}





/**
 * Requalifies all jobs based on the given function template.
 * 
 * This function fetches all jobs related to the specified function template and updates their qualification status.
 * It also recreates the jobs with the updated qualification status.
 * This function should be called, when a function template including its achievement rules is updated.
 * 
 * @async
 * @param {Object} req - The request object.
 * @param {Buffer} functionT - The UID of the function template.
 * @param {number} [timestamp] - Unix timestamp to get data as of a specific point in time. If not provided, current timestamp is used.
 * @returns {Promise<Object>} An object with a success property indicating if the operation was successful.
 * @throws {Error} Logs any errors that occur during the process via errorLoggerUpdate.
 */
export const requalify=async (req,functionT,timestamp=null)=>
{

    // requalify all jobs based on this function template
    try{
        const asOf=timestamp ? `FOR SYSTEM_TIME AS OF FROM_UNIXTIME(${timestamp})` : ''
        if(!timestamp)
            timestamp=Date.now()/1000
        const jobs=await query(`SELECT ObjectBase.UID, ObjectBase.UIDBelongsTo AS UIDperson, 
            FunctionT.Data AS FunctionData, FunctionT.UID AS UIDfunction, ObjectBase.Data AS JobData,
            Member.Data AS MemberData, UNIX_TIMESTAMP(latest.ValidFrom) AS ValidFrom
             FROM ObjectBase  ${asOf}
             INNER JOIN ObjectBase AS latest ON (ObjectBase.UID=latest.UID)
            INNER JOIN Links ${asOf} AS FLink ON (FLink.UIDTarget=ObjectBase.UID AND FLink.Type='function')
            INNER JOIN ObjectBase ${asOf} AS FunctionT ON (FunctionT.UID=FLink.UID)
            INNER JOIN Member ON (Member.UID=ObjectBase.UIDBelongsTo)           
            WHERE ObjectBase.Type='job' AND FunctionT.UID=?`,[functionT])
       
            
        for(const job of jobs)
        {
            const functionData=JSON.parse(job.FunctionData)
            if(functionData.qualification)
            {
                const qualified=await jobQualified(job.UIDperson,functionData.qualification,timestamp)
                const jobData=JSON.parse(job.JobData)
                jobData.qualified=qualified
                query(`UPDATE ObjectBase SET Data=?,dindex=? WHERE UID=?`,
                    [JSON.stringify(jobData),qualified,job.UID]),
                {backDate:Math.max(timestamp,job.ValidFrom)}
            }
        }
        await recreateJobs(req,jobs,timestamp,true)
        return {success:true}
    }
    catch(e)
    {
        errorLoggerUpdate(e)
    }
}


/**
 * Recreates jobs based on a specified function template.
 * 
 * This function retrieves all jobs associated with a given function template
 * and recreates them based on the template using the recreateJobs function.
 * it should be called, when a function template is updated without changed to the achievement rules.
 * @param {Object} req - The request object from the client.
 * @param {string} functionT - The UID of the function template to use for recreating jobs.
 * @param {number|null} [timestamp=null] - Optional UNIX timestamp to retrieve jobs as they were at that time.
 *                                         If null, current time is used.
 * @returns {Promise<Object>} A promise that resolves to an object with a success property.
 * @throws {Error} If the database query fails, the error is logged but not returned.
 */
export const recreateJobsFunction=async (req,functionT,timestamp=null)=>
{
    // recreate all jobs based on this function template of the organisation via the template
  
    try{
        const asOf=timestamp ? `FOR SYSTEM_TIME AS OF FROM_UNIXTIME(${timestamp})` : ''
        if(!timestamp)
             timestamp=Date.now()/1000
        const jobs=await query(`SELECT ObjectBase.UID,ObjectBase.Data  AS JobData,ObjectBase.dindex as qualified, PGroup.Data AS GroupData,PGroup.UID AS UIDGroup,
                                    Person.UID AS UIDperson, Person.dindex AS dindexPerson,FunctionT.Data AS FunctionData, FunctionT.UID AS UIDfunction,
                                    Member.Data AS MemberData, UNIX_TIMESTAMP(latest.ValidFrom) AS ValidFrom
                                    FROM ObjectBase ${asOf}
                                    INNER JOIN ObjectBase AS latest ON (ObjectBase.UID=latest.UID)
                                    INNER JOIN Links ${asOf} ON (Links.UID=ObjectBase.UID AND Links.Type IN ('member','memberA'))
                                    INNER JOIN Links ${asOf} AS GLink ON (GLink.UID=ObjectBase.UID AND GLink.Type='memberA')
                                    INNER JOIN ObjectBase ${asOf} AS PGroup ON (PGroup.UID=GLink.UIDTarget)
                                    INNER JOIN ObjectBase ${asOf} AS Person ON (ObjectBase.UIDBelongsTo=Person.UID AND Person.Type IN('person',extern'))
                                    INNER JOIN Links ${asOf} AS FLink ON (FLink.UIDTarget=ObjectBase.UID AND FLink.Type='function')
                                    INNER JOIN ObjectBase ${asOf} AS FunctionT ON (FunctionT.UID=FLink.UID)
                                    INNER JOIN Member ON (Member.UID=ObjectBase.UIDBelongsTo)
                                    WHERE ObjectBase.Type='job' AND FunctionT.UID=?
                                    GROUP BY ObjectBase.UID`,
                                    [functionT],
                                    {log:true})
        recreateJobs(req,jobs,timestamp,true)
        return {success:true}
    }
    catch(e)
    {
        errorLoggerUpdate(e)
    }
}


/**
 * Recreates jobs for a specific person based on their templates.
 * 
 * @async
 * @function recreateJobsPerson
 * @param {Object} req - Express request object
 * @param {Object} req.params - Request parameters
 * @param {string} req.params.UIDperson - UID of the person
 * @param {Object} req.query - Request query parameters
 * @param {number} [req.query.timestamp] - Optional timestamp for point-in-time query
 * @param {Object} res - Express response object
 * @param {boolean} [requalify=true] - Whether to recalculate job qualification status based on achievements
 * @returns {Promise<void>} - Sends JSON response indicating success or handles error
 * @throws {Error} - Logs error to error logger if query fails
 * @description Retrieves job data for a person at a specific point in time (if timestamp provided),
 *              then recreates the jobs using the recreateJobs function. When requalify is true,
 *              it recalculates job qualification status based on achievements if requalify is true (default).
 *              then recreates the jobs using the recreateJobs function.
 */
/**
 * Recreates jobs for a specific person based on their UID.
 *
 * This function fetches job data from the database for a person, potentially as of a specific timestamp,
 * and then recreates these jobs. It can also requalify the jobs as part of the process.
 *
 * @async
 * @function recreateJobsPerson
 * @param {Object} req - The request object containing query parameters.
 * @param {string|Buffer} UIDperson - The UID (hex string or Buffer) of the person whose jobs need to be recreated.
 * @param {boolean} [requalify=true] - Whether to requalify the jobs during recreation.
 * @returns {Promise<Object>} - A promise that resolves when the jobs have been recreated.
 * @throws {Error} - Any errors that occur during the job recreation process are logged.
 */
export const recreateJobsPerson = async (req, UIDperson, requalify=true)=>
    {

        let timestamp = parseTimestampToSeconds(req.query.timestamp ? String(req.query.timestamp) : undefined)
        const asOf=timestamp ? `FOR SYSTEM_TIME AS OF FROM_UNIXTIME(${timestamp})` : ''
        const person=UUID2hex(UIDperson)
        try{
            if(!timestamp)
                timestamp=Date.now()/1000
            const jobs=await query(`SELECT ObjectBase.UID,ObjectBase.Data AS JobData,ObjectBase.dindex as qualified, PGroup.Data AS GroupData,
                             Person.UID AS UIDperson,FunctionT.Data AS FunctionData, FunctionT.UID AS UIDfunction ,
                             Member.Data AS MemberData,UNIX_TIMESTAMP(latest.ValidFrom) AS ValidFrom  
                    FROM ObjectBase ${asOf}
                    INNER JOIN ObjectBase AS latest ON (ObjectBase.UID=latest.UID)
                    INNER JOIN Links  ${asOf} ON (Links.UID=ObjectBase.UID AND Links.Type IN ('member','memberA'))
                    INNER JOIN Links ${asOf} AS GLink ON (GLink.UID=ObjectBase.UID AND GLink.Type='memberA')
                    INNER JOIN ObjectBase ${asOf} AS PGroup ON (PGroup.UID=GLink.UIDTarget)
                    INNER JOIN ObjectBase ${asOf} AS Person ON (ObjectBase.UIDBelongsTo=Person.UID AND Person.Type IN('person','extern'))
                    INNER JOIN Links ${asOf} AS FLink ON (FLink.UIDTarget=ObjectBase.UID AND FLink.Type='function')
                    INNER JOIN ObjectBase ${asOf} AS FunctionT ON (FunctionT.UID=FLink.UID)
                    INNER JOIN Member ON (Member.UID=ObjectBase.UIDBelongsTo)
                    WHERE ObjectBase.Type='job' AND ObjectBase.UIDBelongsTo=? 
                    GROUP BY ObjectBase.UID`,[person],
                    {log:true}
            )
            recreateJobs(req,jobs,timestamp,requalify)
            return {success:true}
        }
        catch(e)
        {
            errorLoggerUpdate(e)
        }
    }


/**
 * Recreates all jobs within a specified group using templates.
 * 
 * This function fetches all job objects associated with a given group,
 * optionally at a specific point in time (using system versioning),
 * and recreates them based on their templates using the recreateJobs function.
 * this function should be called, when a group is updated.
 * 
 * @param {Object} req - The request object
 * @param {Buffer} group - The UID of the group whose jobs will be recreated
 * @param {number|null} timestamp - Optional Unix timestamp for point-in-time recreation (null for current time)
 * @returns {Promise<Object>} A response object with success status and optional error message
 * @throws {Error} May throw errors which are caught and logged via errorLoggerUpdate
 */
export const recreateJobsGroup=async (req,group,timestamp=null)=>
{
    try {
        // recreate all objects of the group via the template
        const asOf=timestamp ? `FOR SYSTEM_TIME AS OF FROM_UNIXTIME(${timestamp})` : ''
        try{
            if(!timestamp)
                timestamp=Date.now()/1000
            const jobs=await query(`SELECT ObjectBase.UID,ObjectBase.Data  AS JobData,ObjectBase.dindex as qualified, PGroup.Data AS GroupData,PGroup.UID AS UIDGroup,
                    Person.UID AS UIDperson,Person.dindex AS dindexPerson, FunctionT.Data AS FunctionData, FunctionT.UID AS UIDfunction,
                    Member.Data AS MemberData, UNIX_TIMESTAMP(latest.ValidFrom) AS ValidFrom
                    FROM ObjectBase ${asOf}
                    INNER JOIN ObjectBase AS latest ON (ObjectBase.UID=latest.UID)
                    INNER JOIN Links ${asOf} ON (Links.UID=ObjectBase.UID AND Links.Type IN ('member','memberA'))
                    INNER JOIN Links ${asOf} AS GLink ON (GLink.UID=ObjectBase.UID AND GLink.Type='memberA')
                    INNER JOIN ObjectBase ${asOf} AS PGroup ON (PGroup.UID=GLink.UIDTarget)
                    INNER JOIN ObjectBase ${asOf} AS Person ON (ObjectBase.UIDBelongsTo=Person.UID AND Person.Type IN('person','extern'))
                    INNER JOIN Links ${asOf} AS FLink ON (FLink.UIDTarget=ObjectBase.UID AND FLink.Type='function')
                    INNER JOIN ObjectBase ${asOf} AS FunctionT ON (FunctionT.UID=FLink.UID)
                    INNER JOIN Member ON (Member.UID=ObjectBase.UIDBelongsTo)
                    WHERE ObjectBase.Type='job' AND Links.UIDTarget=?
                    GROUP BY ObjectBase.UID `,[group])
            recreateJobs(req,jobs,timestamp,true)
            return {success:true}
        }
        catch(e)
        {
            return {succes:false,message:e}
        }
    }
    catch(e)
    {
        errorLoggerUpdate(e)
    }
    
}