/**
* Event Job Template Service Layer
*
* Manages job templates for events. Job templates define roles/functions
* that can be assigned to people in events, including qualifications required,
* permissions, and display settings.
*
* Key concepts:
* - Job Templates: Reusable role definitions for events
* - Qualifications: Achievement requirements for job assignments
* - Template hierarchy: Templates belong to event templates
*
* @module EventJobTemplateService
*/
// @ts-check
/**
* @import {ExpressRequestAuthorized} from './../../types.js'
*/
import { query, UUID2hex, HEX2uuid, pool } from '@commtool/sql-query';
import { renderObject } from '../../utils/renderTemplates.js';
import { Templates } from '../../utils/compileTemplates.js';
import { getUID } from '../../utils/UUIDs.js';
import { queueAdd } from '../../tree/treeQueue/treeQueue.js';
import { updateTemplate } from '../../server.ws.js';
/**
* Extract achievement UIDs recursively from qualification filter object
*
* @param {Object|Array|string} filter - Qualification filter object
* @returns {Array<string>} Array of achievement UIDs
*/
export const extractAchievements = (filter) => {
const qualies = [];
const logic = (filters) => {
if (!Array.isArray(filters)) {
match(filters);
} else {
for (const filter of filters) {
match(filter);
}
}
return true;
};
const match = (filter) => {
if (!filter) {
return;
}
if (typeof filter === 'string') {
qualies.push(filter);
return;
}
const [func] = Object.keys(filter);
logic(filter[func]);
};
if (filter) {
try {
match(filter);
} catch (e) {
console.log({ error: e, filter: filter });
}
}
return qualies;
};
/**
* Create or update a job template
*
* @param {Buffer} UIDeventTemplate - Event template UID
* @param {Object} templateData - Template data from request body
* @param {string} rootUID - Organization root UID
* @param {string} userUID - User UID
* @param {Object} req - Request object for renderObject
* @param {boolean} requalify - Whether to requalify existing jobs
* @returns {Promise<Object>} Created/updated template object
* @throws {Error} When event template is invalid
*/
export const createOrUpdateJobTemplate = async (
UIDeventTemplate,
templateData,
rootUID,
userUID,
req,
requalify = false
) => {
const result = await query(
`SELECT UID, Data FROM ObjectBase WHERE UID=? AND Type='eventT'`,
[UIDeventTemplate]
);
if (!result || result.length === 0) {
throw new Error('invalid event Template UID');
}
const eventTemplateData = JSON.parse(result[0].Data);
const template = Templates[rootUID].eventJobTemplate;
const UIDfunction = await getUID(req);
const object = await renderObject(
template,
{ ...templateData, template: eventTemplateData },
req,
'events'
);
object.Data = {
...templateData,
hierarchie: eventTemplateData.hierarchie,
stage: eventTemplateData.stage,
gender: eventTemplateData.gender,
UID: undefined
};
const functions = await query(
`SELECT Data FROM ObjectBase WHERE UID=? AND Type='eventJobT'`,
[UIDfunction]
);
await query(
`INSERT INTO ObjectBase(UID, Type, UIDBelongsTo, Title, Display, SortName, FullTextIndex, Data)
VALUES (?, 'eventJobT', ?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
UIDBelongsTo=VALUE(UIDBelongsTo), Title=VALUE(Title), Display=VALUE(Display),
SortName=VALUE(SortName), FullTextIndex=VALUE(FullTextIndex), dindex=VALUE(dindex), Data=VALUE(Data)`,
[
object.UID,
UIDeventTemplate,
object.Title,
object.Display,
object.SortIndex,
object.FullTextIndex,
JSON.stringify(object.Data)
]
);
// Handle modifications to existing function
if (functions.length > 0) {
// Delete old qualification links
await query(
`DELETE FROM Links WHERE Type='achievement' AND UID=?`,
[UIDfunction]
);
if (requalify) {
// Add requalification job to queue
queueAdd(
UUID2hex(rootUID),
UUID2hex(userUID),
'function',
object.UID,
UUID2hex(rootUID),
null,
UUID2hex(rootUID)
);
}
}
// Create qualification links
const qualies = extractAchievements(templateData.qualification);
if (qualies.length > 0) {
await query(
`INSERT IGNORE INTO Links(UID, Type, UIDTarget) VALUES (?, 'achievement', ?)`,
qualies.map(q => [UIDfunction, UUID2hex(q)]),
{ batch: true }
);
}
return { ...object, UID: HEX2uuid(object.UID) };
};
/**
* Delete a job template
*
* @param {Buffer} UID - Template UID to delete
* @param {string} rootUID - Organization root UID
* @returns {Promise<boolean>} Success status
*/
export const deleteJobTemplate = async (UID, rootUID) => {
await query(
`DELETE FROM ObjectBase WHERE UID=? AND Type='eventJobT'`,
[UID]
);
// Delete qualification links
await query(
`DELETE FROM Links WHERE Type='achievement' AND UIDTarget=?`,
[UID]
);
updateTemplate(rootUID);
return true;
};
/**
* Get all jobs for a function template
*
* @param {Buffer} UID - Function template UID
* @param {Buffer} UIDUser - User UID for visibility check
* @param {Object} options - Query options
* @param {string} [options.timestamp] - Historical timestamp
* @param {Array} [options.dataFields] - Additional data fields to retrieve
* @param {boolean} [options.groupBanner] - Include group banner
* @returns {Promise<Array>} Array of job objects
*/
export const getEventJobs = async (UID, UIDUser, options = {}) => {
const time = options.timestamp
? `FOR SYSTEM_TIME AS OF TIMESTAMP'${options.timestamp}'`
: '';
let dataFields = '';
if (options.dataFields && Array.isArray(options.dataFields)) {
for (const field of options.dataFields) {
dataFields += `,JSON_VALUE(Member.Data,${pool.escape(field.path)}) AS ${pool.escape(field.alias)}`;
}
}
if (options.groupBanner) {
dataFields += `,JSON_VALUE(pgroup.Data,'$.banner') AS groupBanner`;
}
const result = await query(
`SELECT
Job.UID, Job.Type, Job.UIDBelongsTo, Job.Title, Member.Display, Member.SortName,
pgroup.UID AS UIDgroup,
CONCAT(pgroup.Title, ' ', pgroup.Display) AS pGroup,
Job.hierarchie, Job.stage, Job.gender, Job.dindex
${dataFields}
FROM ObjectBase ${time} AS Job
INNER JOIN Member ON (Member.UID=Job.UIDBelongsTo)
LEFT JOIN (ObjectBase AS pgroup
INNER JOIN Links AS GLink ON (GLink.UIDTarget = pgroup.UID)
)
ON (GLink.UID=Job.UID AND GLink.Type='memberA')
INNER JOIN Links ON (Links.UIDTarget=Job.UID AND Links.Type='function')
INNER JOIN Visible ON (Visible.UID=Job.UID)
WHERE Links.UID=? AND Job.Type='eventJob' AND Visible.UIDUser=?
GROUP BY Job.UID
ORDER BY Job.SortName, Member.SortName`,
[UID, UIDUser]
);
return result;
};
/**
* Get all function template names and UIDs for an event template
*
* @param {Buffer} UID - Event template UID
* @returns {Promise<Array>} Array of template objects with UID, Display, SortName
*/
export const getJobTemplates = async (UID) => {
const result = await query(
`SELECT Main.UID, Main.Display, Main.SortName
FROM ObjectBase AS Main
WHERE Main.UIDBelongsTo=? AND Main.Type='eventJobT'
GROUP BY Main.UID
ORDER BY Main.SortName`,
[UID],
{ cast: ['UUID'] }
);
return result;
};
/**
* Get all function template data for an event template
*
* @param {Buffer} UID - Event template UID
* @returns {Promise<Array>} Array of template objects with full data
*/
export const getJobTemplatesWithData = async (UID) => {
const result = await query(
`SELECT Main.UID, Main.Display, Main.SortName, Main.Data
FROM ObjectBase AS Main
WHERE Main.UIDBelongsTo=? AND Main.Type='eventJobT'
GROUP BY Main.UID
ORDER BY Main.SortName`,
[UID],
{ cast: ['UUID', 'json'] }
);
return result;
};